Android 消息机制详解及实例代码
android 消息机制
1.概述
android应用启动时,会默认有一个主线程(ui线程),在这个线程中会关联一个消息队列(messagequeue),所有的操作都会被封装成消息队列然后交给主线程处理。为了保证主线程不会退出,会将消息队列的操作放在一个死循环中,程序就相当于一直执行死循环,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数(handlermessage),执行完成一个消息后则继续循环,若消息队列为空,线程则会阻塞等待。因此不会退出。如下图所示:
handler 、 looper 、message有啥关系?
在子线程中完成耗时操作,很多情况下需要更新ui,最常用的就是通过handler将一个消息post到ui线程中,然后再在handler的handlermessage方法中进行处理。而每个handler都会关联一个消息队列(messagequeue),looper负责的就是创建一个messagequeue,而每个looper又会关联一个线程(looper通过threadlocal封装)。默认情况下,messagequeue只有一个,即主线程的消息队列。
上面就是android消息机制的基本原理,如果想了解更详细,我们从源码开始看。
2.源码解读
(1)activitythread主线程中启动启动消息循环looper
public final class activitythread { public static void main(string[] args) { //代码省略 //1.创建消息循环的looper looper.preparemainlooper(); activitythread thread = new activitythread(); thread.attach(false); if (smainthreadhandler == null) { smainthreadhandler = thread.gethandler(); } asynctask.init(); //2.执行消息循环 looper.loop(); throw new runtimeexception("main thread loop unexpectedly exited"); } }
activitythread通过looper.preparemainlooper()创建主线程的消息队列,最后执行looper.loop()来启动消息队列。handler关联消息队列和线程。
(2)handler关联消息队列和线程
public handler(callback callback, boolean async) { //代码省略 //获取looper mlooper = looper.mylooper(); if (mlooper == null) { throw new runtimeexception( "can't create handler inside thread that has not called looper.prepare()"); } //获取消息队列 mqueue = mlooper.mqueue; }
handler会在内部通过looper.getlooper()方法来获取looper对象,并且与之关联,并获取消息队列。那么looper.getlooper()如何工作的呢?
public static @nullable looper mylooper() { return sthreadlocal.get(); } public static @nonnull messagequeue myqueue() { return mylooper().mqueue; } public static void prepare() { prepare(true); } //为当前线程设置一个looper private static void prepare(boolean quitallowed) { if (sthreadlocal.get() != null) { throw new runtimeexception("only one looper may be created per thread"); } sthreadlocal.set(new looper(quitallowed)); } //设置ui线程的looper public static void preparemainlooper() { prepare(false); synchronized (looper.class) { if (smainlooper != null) { throw new illegalstateexception("the main looper has already been prepared."); } smainlooper = mylooper(); } }
在looper类中,mylooper()方法,通过sthreadlocal.get()来获取的,在preparemainlooper()中调用prepare()方法,在这个方法中创建了一个looper对象,并将对象设置了sthreadlocal()。这样队列就和线程关联起来了。通过sthreadlocal.get()方法,保证不同的线程不能访问对方的消息队列。
为什么要更新ui的handler必须在主线程中创建?
因为handler要与主线程的消息队列关联上,这样handlermessage才会执行在ui线程,此时ui线程才是安全的。
(3)消息循环,消息处理
消息循环的建立就是通过looper.loop()方法。源代码如下:
/** * run the message queue in this thread. be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final looper me = mylooper(); if (me == null) { throw new runtimeexception("no looper; looper.prepare() wasn't called on this thread."); } //1.获取消息队列 final messagequeue queue = me.mqueue; //2.死循环,即消息循环 for (;;) { //3.获取消息,可能阻塞 message msg = queue.next(); // might block if (msg == null) { // no message indicates that the message queue is quitting. return; } //4.处理消息 msg.target.dispatchmessage(msg); //回收消息 msg.recycleunchecked(); } }
从上述程序我们可以看出,loop()方法的实质上是建立一个死循环,然后通过从消息队列中逐个取出消息,最后处理消息。对于looper:通过looper.prepare()来创建looper对象(消息队列封装在looper对象中),并且保存在sthreadlocal中,然后通过通过looper.loop()进行消息循环,这两步通常成对出现。
public final class message implements parcelable { //target处理 handler target; //runnable类型的callback runnable callback; //下一条消息,消息队列是链式存储的 message next; }
从源码中可以看出,target是handler类型。实际上就是转了一圈,通过handler发送消息给消息队列,消息队列又将消息分发给handler处理。在handle类中:
//消息处理函数,子类覆写 public void handlemessage(message msg) { } private static void handlecallback(message message) { message.callback.run(); } //分发消息 public void dispatchmessage(message msg) { if (msg.callback != null) { handlecallback(msg); } else { if (mcallback != null) { if (mcallback.handlemessage(msg)) { return; } } handlemessage(msg); } }
从上述程序可以看出,dispatchmessage只是一个分发的方法,如果run nable类型的callback为空,则执行handlemessage来处理消息,该方法为空,我们会将更新ui的代码写在该函数中;如果callback不为空,则执行handlecallback来处理,该方法会调用callback的run方法。其实这是handler分发的两种类型,比如post(runnable callback)则callback就不为空,当我们使用handler来sendmessage时通常不设置callback,因此,执行handlermessage。
public final boolean post(runnable r) { return sendmessagedelayed(getpostmessage(r), 0); } public string getmessagename(message message) { if (message.callback != null) { return message.callback.getclass().getname(); } return "0x" + integer.tohexstring(message.what); } public final boolean sendmessagedelayed(message msg, long delaymillis) { if (delaymillis < 0) { delaymillis = 0; } return sendmessageattime(msg, systemclock.uptimemillis() + delaymillis); public boolean sendmessageattime(message msg, long uptimemillis) { messagequeue queue = mqueue; if (queue == null) { runtimeexception e = new runtimeexception( this + " sendmessageattime() called with no mqueue"); log.w("looper", e.getmessage(), e); return false; } return enqueuemessage(queue, msg, uptimemillis); }
从上述程序可以看到,在post(runnable r)时,会将runnable包装成message对象,并且将runnable对象设置给message对象的callback,最后会将该对象插入消息队列。sendmessage也是类似实现:
public final boolean sendmessage(message msg) { return sendmessagedelayed(msg, 0); }
不管是post一个runnable还是message,都会调用sendmessagedelayed(msg, time)方法。handler最终将消息追加到messagequeue中,而looper不断地从messagequeue中读取消息,并且调用handler的dispatchmessage分发消息,这样消息就源源不断地被产生、添加到messagequeue、被handler处理,android应用就运转起来了。
3.检验
new thread(){ handler handler = null; public void run () { handler = new handler(); }; }.start();
上述代码有问题吗?
looper对象是threadlocal的,即每个线程都用自己的looper,这个looper可以为空。但是,当在子线程中创建handler对象时,如果looper为空,那么会出现异常。
public handler(callback callback, boolean async) { //代码省略 //获取looper mlooper = looper.mylooper(); if (mlooper == null) { throw new runtimeexception( "can't create handler inside thread that has not called looper.prepare()"); } //获取消息队列 mqueue = mlooper.mqueue; }
当mlooper为空时,抛出异常。这是因为looper对象没有创建,因此,sthreadlocal.get()会返回null。handler的基本原理就是要与messagequeue建立关联,并且将消息投递给messagequeue,如果没有messagequeue,则handler没有存在的必要,而messagequeue又被封住在looper中,因此创建handler时,looper一定不能为空。解决办法如下:
new thread(){ handler handler = null; public void run () { //为当前线程创建looper,并且绑定到threadlocal中 looper.prepare() handler = new handler(); //启动消息循环 looper.loop(); }; }.start();
如果只创建looper不启动消息循环,虽然不抛出异常,但是通过handler来post或者sendmessage()也不会有效。因为虽然消息会被追加到消息队列,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!