Android消息机制全面解析

Android是基于事件驱动,每一个触摸事件或者是Activity的生命周期都是运行在Looper.looper()的控制之下,理解弄懂消息机制可以让我们在开发的过程中更加得心应手。

Android的消息机制也是Handler机制,主要的作用是用来在不同线程之间的通信,通常使用在子线程执行完成一些耗时操作,需要回到主线程更新界面UI时,通过Handler将有关UI的操作切换到主线程。

工作流程

先来大致梳理下整个流程:

  • 应用程序启动的时候,在主线程中会默认调用了 Looper.preper()方法,初始化Looper对象并绑定到当前线程中,并在Looper内部维护一个MessageQueue
  • 接着调用handler.sendMessage()发送消息,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息
  • 主线程调用Looper.looper()开启循环,不断轮询消息队列,通过MessageQueue.next()取出消息
  • 取出的message不为空则调用msg.target.dispatchMessage()传递分发消息,目标handler收到消息后会执行handler.handlerMessage()方法处理消息

详细分析

Looper

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程 。所谓Looper线程就是执行循环工作的线程

Looper的创建(Looper.prepare())

在应用程序的入口ActivityThread.main()方法中系统已经帮我们创建好Looper对象

	//主线程中不需要自己创建Looper
	public static void main(String[] args) {
	        ......
	        Looper.prepareMainLooper();//为主线程创建Looper,该方法内部又调用 Looper.prepare()
	        ......
	        Looper.loop();//开启消息轮询
	        ......
	    }

子线程中的Looper是需要我们自己手动创建的

public class LooperThread extends Thread {
    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();
        
        // ...其他处理,如实例化handler
        
        // 开始循环处理消息队列
        Looper.loop();
    }
}

这样你的线程就可以成为Looper线程

在Looper的构造方法中还创建了MessageQueue

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
  • 无论是主线程还是子线程,Looper只能被创建一次,即一个Thread只有一个Looper。
  • 所创建的Looper会保存在ThreadLocal(线程本地存储区)中,它不是线程,作用是帮助Handler获得当前线程的Looper。(ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper)

Looper.prepare()方法的作用
1.将当前线程变成Looper线程,在其内部维护一个消息队列MQ
2.创建Looper对象并将Looper对象定义为ThreadLocal

Looper.loop()

调用Looper.loop()后,Looper线程就开始真正的工作了。该方法是一个阻塞性的死循环,它不断轮询自己的MQ,并从中取出队头的消息执行。

public static void loop() {
        final Looper me = myLooper();//得到当前线程Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // 开始循环
        for (;;) {
            Message msg = queue.next(); // might block 从MQ中取出消息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                // 将真正的处理工作交给message的target(handler)处理分发消息
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

Looper.myLooper()得到当前线程looper对象:

 public static @Nullable Looper myLooper() {
       // 在任意线程调用Looper.myLooper()返回的都是那个线程绑定的looper
        return sThreadLocal.get();
    }

Handler

handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,如果当前线程还没有初始化Looper,或者说当前线程还不是looper线程,会报RuntimeException,不过这也是可以set的。
public class handler {

final MessageQueue mQueue;  // 关联的MQ
final Looper mLooper;  // 关联的looper
final Callback mCallback; 
// 其他属性

public Handler() {
    // 没看懂,直接略过,,,
    if (FIND_POTENTIAL_LEAKS) {
        final Class klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    // 默认将关联当前线程的looper
    mLooper = Looper.myLooper();
    // looper不能为空,即该默认的构造方法只能在looper线程中使用
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
    mQueue = mLooper.mQueue;
    mCallback = null;
}

// 其他方法

}
Handler发出的message有如下特点

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码
msg.target.dispatchMessage(msg);
2.post发出的message,其callback为Runnable对象

 // 此方法用于向关联的MQ上发送Runnable对象,它的run方法将在handler关联的looper线程中执行
    public final boolean post(Runnable r)
    {
       // 注意getPostMessage(r)将runnable封装成message
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

Handler处理消息

// 处理消息,该方法由looper调用
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 如果message设置了callback,即runnable消息,处理callback!
            handleCallback(msg);
        } else {
            // 如果handler本身设置了callback,则执行callback
            if (mCallback != null) {
                 /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。 */
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 如果message没有callback,则调用handler的钩子方法handleMessage
            handleMessage(msg);
        }
    }
    
    // 处理runnable消息
    private final void handleCallback(Message message) {
        message.callback.run();  //直接调用run方法!
    }
    // 由子类实现的钩子方法
    public void handleMessage(Message msg) {
    }

重点
1.Handler 可以在任意线程中发送消息,这些消息会被添加到关联的MQ上
2.Handler是在它关联的Looper线程(Looper绑定的线程)中处理消息的
Android消息机制全面解析_第1张图片

总结
1.handler可以在任意线程发送消息,这些消息会被添加到关联的MQ上。
2.handler是在它关联的looper线程中处理消息的。(handlerMessage()方法运行所在的线程是根据handler创建的时候绑定的Looper线程,和绑定的Looper所在的线程一致)
3.Android的主线程也是一个looper线程,我们在其中创建的handler默认将关联主线程MQ

Message

message又叫task,封装了任务携带的信息和处理该任务的handler

注意事项:
1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
3.擅用message.what来标识信息,以便用不同方式处理message。

为了能够更好的理解掌握消息机制,以下有几道面试题

1.为什么一个线程只有一个Looper、只有一个MessageQueue?

因为线程对应的Looper是在ThreadLocal里面存储,它是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。ThreadLocal它的作用是可以在不同的线程之中互不干扰地存储并提供数据(就相当于一个Map集合,键位当前的Thead线程,值为Looper对象)。另外,在looper创建的方法looper.prepare()中,会有一个判断如果当前线程存在Looper对象,就会报RunTimeException,所以一个线程只有一个Looper,而MQ作为Looper的成员变量自然也就只有一个。

2.如何获取当前线程的Looper?是怎么实现的?(理解ThreadLocal)

3.是不是任何线程都可以实例化Handler?有没有什么约束条件?

任何线程都可以实例化Handler,handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,如果当前线程还没有初始化Looper,或者说当前线程还不是looper线程,会报RuntimeException。

4.Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

在应用程序的入口ActivityThread里面的main方法中会创建一个主线程的looper对象和一个大Handler,(这也是为什么直接在主线程拿Handler就有Looper的原因,在其他线程是要自己Looper.prepare())
Android是基于事件驱动的,通过looper.looper()不断接收事件,处理事件,每一个触摸事件或者是Activity的生命周期都是运行在Looper.looper()的控制之下,当收到不同Message时则采用相应措施:在H.handleMessage(msg)方法中,根据接收到不同的msg,执行相应的生命周期。如果它停止了,应用也就停止了。也就是说我们的代码其实就是运行在这个循环里面去执行的,当然就不会阻塞。

而所谓ANR便是Looper.loop没有得到及时处理,一旦没有消息,Linux的epoll机制则会通过管道写文件描述符的方式来对主线程进行唤醒与睡眠,Android里调用了linux层的代码实现在适当时会睡眠主线程。

拓展

消息循环(死循环)的必要性:

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出
ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。如果main方法中没有looper进行循环,那么主线程一运行完毕就会退出。

主线程在没有事件需要处理的时候就是处于阻塞的状态。想让主线程活动起来一般有两种方式:
第一种 :是系统唤醒主线程,并且将点击事件传递给主线程;
第二种 :是其他线程使用主线程的Handler向MessageQueue中存放了一条消息,导致loop被唤醒继续执行。

总结
Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

looper.looper()阻塞会不会消耗大量的cpu资源

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。因此loop的循环并不会对CPU性能有过多的消耗。

5.Handler.sendMessageDelayed()怎么实现延迟的?结合Looper.loop()循环中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。

Handler.sendMessageDelayed()内部调用sendMessageAtTime()把传入的时间转化成绝对时间when(延时的时间加上系统当前的时间),然后调用MessageQueue的enqueueMessage(),采用线程安全的方式将Message插入到消息队列中,消息队列的插入是由when顺序排列,插入的新消息有三种可能成为消息队列的head:
(1)消息队列为空;
(2)参数when为0,因为此时when已经转成绝对时间,所以只有AtFrontOfQueue(sendMessageAtFrontOfQueue直接把消息插入到队列的头部)系列的API才会满足这个条件;
(3)当前的head Message执行时间在when之后,即消息队列中无需要在此Message之前执行的Message。
接着就是Looper.looper()启动消息循环,循环开始调用messageQueue.next()从消息队列中取出一个合理的消息。如果next()返回null,则looper()直接return,本次消息循环结束。如果消息不为空则调用msg.target.dispatchMessage(msg)处理消息(msg.target就是Handler)

.next()取下一个消息的实际执行时间取决于上一个消息什么时候处理完
在MessageQueue.next()中,如果在消息队列中顺序找到了一个消息msg(前文分析过,消息队列的插入是由when顺序排列,所以如果当前的消息没有到执行时间,其后的也一定不会到),当前的系统时间小于msg.when,那么会计算一个timeout,以便在到执行时间时wake up;如果当前系统时间大于或等于msg.when,那么会返回msg给Looper.loop()。所以这个逻辑只能保证在when之前消息不被处理,不能够保证一定在when时被处理。
(1)在Loop.loop()中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了when,消息不可能在when时间点被处理。
(2)即使when的时间点没有被处理其他消息所占用,线程也有可能被调度失去cpu时间片。
(3)在等待时间点when的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。
所以由上述三点可知,Handler提供的指定处理时间的api诸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。

参考
https://blog.csdn.net/zhanglianyu00/article/details/70842494
http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html
https://www.jianshu.com/p/1c79fb5296b6

你可能感兴趣的:(Android,Android,面试)