一般线程间的消息传递,有许多方法,有Activity.runOnUiThread、Handler.post、View.post等方法,这些方法本质上还是通过Handler传递一个Message来实现的。举个常见的场景:在子线程进行任务的下载,需要实时显示进度在UI上,但是AndroidUI是线程不安全的,直接在子线程刷新UI是会导致应用崩溃的。一般的做法是创建一个Handler对象,通过在子线程中发送message,在handleMessage中进行刷新界面就不会有问题了。这种处理方式被称为异步消息处理模型。
一、异步消息处理模型
该模型主要由以下几个构成:
1)Handler:负责消息的发送,接收;
2)Looper:工作线程,不断地从消息队列中取出message,不断地回调;
3)MessageQueue:消息队列,消息存放的地方,需要同步机制,保证在多个不同线程插入消息的有序性;
4)Message:消息
二、源码分析
1)创建Handler
通常,我们创建Handler话,是通过new一个对象,那么在主线程创建和子线程创建有什么不同吗?
public class MainActivity extends Activity { private Handler handler1; private Handler handler2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler1 = new Handler(); new Thread(new Runnable() { @Override public void run() { handler2 = new Handler(); } }).start(); } }
如果运行上面的代码,结果是handler1创建没有问题,handler2创建就导致程序崩溃了,提示的错误信息为 Can't create handler inside thread that has not called Looper.prepare() ,意思是不能在线程中创建一个没有调用Looper.prepare方法的Handler。也许你会有疑问了,为什么在UI线程就可以呢?我们来看下Handler的构造方法:
public Handler() { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> 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()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; }
关键的是这句:mLooper = Looper.myLooper(); 获取一个Looper对象,如果返回null,就会抛出异常。我们来看看myLooper方法,
public static final Looper myLooper() { return (Looper)sThreadLocal.get(); }
该方法中是从sThreadLocal中获取Looper对象,既然有get方法,显然会有与之对应的set方法,在该方法中创建一个Looper对象,分析源码,我们发现在prepare方法中调用了set方法,
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
如果sThreadLocal.get()返回的不为空,就会抛出异常,提示Looper只能创建一个,所以prepare方法只能调用1次;如果没有Looper对象,就创建一个,设置到sThreadLocal中。所以这也能解释为什么需要先调用prepare才能创建Handler对象了,而且每个Handler只有一个对应的Looper。
说到这,你可能要说了,这个也没有解释为什么在主线程中创建Handler就可以呀。别急,我们接着分析。这个是因为程序启动时 ,系统主动帮我们调用Looper.prepare方法了。查看ActivityThread类,我们发现了它的调用位置。
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
从Looper.prepareMainLooper(); 从这个方法名来看,我们不难猜出是在这个方法中调用了prepare方法。
public static final void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
if (Process.supportsProcesses()) {
myLooper().mQueue.mQuitAllowed = false;
}
}
到这里,总算明白为什么主线程中创建Handler可以了,而想在子线程创建Handler,必须先调用Looper.prepare方法才可以。
2)发送message
创建完Handler后,一般我们通过创建Message对象,使用sendMessage(Message msg)等方法发送消息,然后在Handler的handlerMessage方法中接收消息。那么消息是发送到哪里?为什么Handler能接收到消息呢?我们继续往下分析。
通过阅读源码,我们发现发送消息的方法最终都会调用到sendMessageAtTime方法,代码如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this; sent = queue.enqueueMessage(msg, uptimeMillis); } else { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); } return sent; }
从代码我们可以看到,第一个参数就是我们传递进来的message,第二个参数uptimeMillis是指系统自启动到当前时间的毫秒数加上延迟的时间,然后将这两个参数设置到enqueueMessage方法中。代码中的MessageQueue,就是消息队列的意思,消息都存放在MessageQueue中。MessageQueue是在Looper的构造函数中创建的,因此一个Looper对应一个MessageQueue。它有提供消息入列和出列的方法。enqueueMessage就是入列的方法。我们来下这个方法:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
首先你要知道,MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码的16~31行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。
接下来我们看下消息出列方法。这个方法在Looper.loop方法中调用:
public static final void loop() { Looper me = myLooper(); MessageQueue queue = me.mQueue; while (true) { Message msg = queue.next(); // might block if (msg != null) { if (msg.target == null) { return; } if (me.mLogging!= null) me.mLogging.println( ">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what ); msg.target.dispatchMessage(msg); if (me.mLogging!= null) me.mLogging.println( "<<<<< Finished to " + msg.target + " " + msg.callback); msg.recycle(); } } }
从第4行可以看出,它进入一个死循环,不断地取出消息,如果当前的消息队列中存在待处理消息,就将这个消息出列,然后让下一条消息成为待处理,否则就进入堵塞,直至新消息入列。不难看出msg.targer就是指发送消息的Handler,每当有一个消息出列,就会传递到dispatchMessage方法。我们来下这个方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果callback不为空,就返回回调;如果为空,就返回handlerMessage。到这里,相信大家明白为什么Handler能够接收到之前发送出去的消息了。
三、总结
一个完整的异步消息处理模型的流程图如下:
创建Looper,它在构造方法中会创建一个MessageQueue,所以一个Looper对应一个MessageQueue;
Looper调用prepare方法,再次调用的话会报错,所以一个Handler对应一个Looper;
创建Handler对象,再调用loop方法,让队列无限循环,之后通过Handler的方法,将message插入到MessageQueue中,它会根据target属性找到对应的Handler,执行dispatchMessage方法,决定是调用callback还是handler方法,这样就消费了一个message。
参考链接: