Android消息机制
消息处理机制
安卓消息机制主要涉及到这几个类:Handler、Message、Looper,MessageQueue
- Handler通过ThreadLocal来获得对应线程的Looper
- Looper中封装了MessageQueue
- Handler在发送消息时把自己封装到Message中
- Looper通过Message中封装的handler来调用handler的dispatch方法
Handler.sendMessage和post(Runnable)方法
- send方法会发送一个Message,在handleMessage时执行handler的handleMessage方法
- post方法会自动生成一个msg,并把msg.callback = Runnable,在处理消息时执行runnable
Message消耗的顺序
1. msg自身的callback,如果有,直接消耗掉;
2. Handler的mCallback,如果有而且返回true,则消耗掉
3. 交给Handler的handleMessage处理
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
主线程的mainLooper不允许退出
public static void prepare() {
prepare(true);
}
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));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
Message的处理可以通过
- 设置Message.callback
- Handler.handlerMessage
优先调用Message自己的callback
public static Message obtain(Handler h, Runnable callback) {
Message m = obtain();
m.target = h;
m.callback = callback;
return m;
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
MessageQueue的阻塞问题
next()是消息队列中最为核心的方法,Looper从消息队列中取消息就是通过next()实现的。next()保证每次调用都能让Looper得到一个消息,除非消息队列正在退出或者已经废弃(此时返回null)。也就是说如果暂时取不出消息,next()并不会返回!此时为了节省资源,next()会根据消息队列的情况设定阻塞时长然后再阻塞线程。
消息队列(未退出、未被废弃)在以下四种情况下,next()会选择阻塞线程:
- 1)队列中没有任何消息 – 永久阻塞:这个时候不能返回null,因为next()的目的是取出一个消息,队列中现在没有消息并不代表一段时间后也没有消息。消息队列还在可用中,随时都有可能有Handler发布新的消息给它。那么问题来了,为了节省资源准备阻塞线程但是多少时间后唤醒它呢?臆断一个时长并不是很好的解决方案。我们知道消息队列是用来管理消息的,既然确定不了阻塞时长那么不如先永久阻塞,等新消息入队后主动唤醒线程。
- 2)队首的消息执行时间未到 – 定时唤醒:每个消息的when字段都给出了希望系统处理该消息的时刻。如果在next()方法取消息时,发现消息队列的队首消息处理时间未到,next()同样需要阻塞。因为消息队列是按照消息的when字段从小到大排列的,如果队首消息的处理时间都没到那么整个队列中都没有能够立即取出的消息。这个时候因为知道下一次处理的具体时间,结合当前时间就可以确定阻塞时长。
- 3)队首消息是同步障碍器(SyncBarrier),并且队列中不含有异步消息 – 永久阻塞:因为对消息队列施加了同步障碍,所有晚于队首同步障碍器处理时间的同步消息都变得不可用,next()在选取返回消息时会完全忽略这些消息。这和第一种情况相似,所以采取的阻塞方案也是永久阻塞。
- 4)队首消息是同步障碍器(SyncBarrier),队列中含有异步消息但执行时间未到 – 定时唤醒:因为对消息队列施加了同步障碍,所有晚于队首同步障碍器处理时间的同步消息都变得不可用,next()在选取返回消息时会完全忽略这些消息。也就是说对于next(),它只会考虑队列中的异步消息。这和第二种情况相似,所以采取的阻塞方案是设置阻塞时长再阻塞。
同步障碍器
主线程的 Looper 会一直循环调用 MessageQueue 的 next() 来取出队头的 Message 执行,当 Message 执行完后再去取下一个。当 next() 方法在取 Message 时发现队头是一个同步屏障的消息时,就会去遍历整个队列,只寻找设置了异步标志的消息,如果有找到异步消息,那么就取出这个异步消息来执行,否则就让 next() 方法陷入阻塞状态。如果 next() 方法陷入阻塞状态,那么主线程此时就是处于空闲状态的,也就是没在干任何事。所以,如果队头是一个同步屏障的消息的话,那么在它后面的所有同步消息就都被拦截住了,直到这个同步屏障消息被移除出队列,否则主线程就一直不会去处理同步屏幕后面的同步消息。
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
当msg.target == null 时说明这个消息是一个同步障碍器。
这个方法会插入一个同步障碍器:
private int postSyncBarrier(long when) {
在View绘制流程中的scheduleTraversals()方法里调用了postSyncBarrier(),即添加了一个同步障碍器
Message是串行处理的吗?
在入队的时候会对Message根据when字段进行排序。
AsyncTask
为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1. Task的实例必须在UI thread中创建
2. execute方法必须在UI thread中调用
3. 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
4. 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。
new AsyncTask<Void, Integer, String>() {
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} return "ok";
}
@Override
protected void onPostExecute(String s) {
Log.d("RecordActivity", s);
}
}.execute();