Android的消息机制主要就是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,它可以将一个任务切换到Handler所在的线程执行。
Android系统规定,子线程是不能更新UI的,因为当我们调用View的invalidate或者requestLayout更新UI的时候,都会经过ViewRootImpl的这个方法:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
即当前的线程不是UI线程会抛异常,并且还不建议在UI线程里面做耗时的操作,否则会ANR(Application No Respond)。
Android里面所有的View的更新操作都是线程不安全的,如果都加锁的会话导致更新UI的效率变低而且会让UI的访问逻辑变得复杂,最简单高效的方法就是采用单线程模型来处理,向主线程的消息队列发送消息来更新UI。
当我们在Activity或者Service里面new Handler,并直接handleMessage的话,lint会提示下面一下内容:
This Handler class should be static or leaks might occur
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
上面的警告说的是在主线程,Handler里面会持有主线程的Looper和MessageQueue,由于这种是内部类的方式,Hanlder会持有Activity的引用,如果里面消息没处理完,我们结束掉了Activity,会导致Activity无法被回收进而导致内存泄漏,Service同理。所以我们需可以静态类来这样处理:
static class MyHandler extends Handler {
private WeakReference<MainActivity> weakReference;
public MyHandler(MainActivity mainActivity) {
weakReference = new WeakReference<>(mainActivity);;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = weakReference.get();
if(mainActivity != null) {
//do something
}
}
}
另外在子线程里面创建Handler是没有Looper的,如果向里面发消息的话会抛RuntimeException。
Handler的工作流程:
因为在子线程里面默认是没有Looper的,所以新建Handler的时候需要在当前线程准备一个Looper:
public class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
ThreadLocal是一个线程内部的数据存储类,所有线程共享一个threadLocal对象,里面的数据每个线程都有他们自己的值,当一个线程改变了里面的值,不会影响到其他线程。所以preare()就是给当前的线程准备了一个Looper,myLooper()返回当前线程里面的Looper对象,quitAllowed表示Looper能否结束,这里为true,可以调用quit退出Looper中的消息循环,主线程里面的Looper是不能结束的。创建Looper的时候里面会构造一个MessageQueue与当前的Looper关联,需要注意,虽然叫消息队列,但使用的数据结构不是队列,而是通过一个单链表来维护消息列表,链表的插入和删除效率高。
创建Handler:
public class Handler {
public Handler() {
this(null, false);
}
final MessageQueue mQueue;
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();//没有调用Looper.prepare() 这里是获取不到的
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//与Looper里面的MessageQueue关联
mCallback = callback;
mAsynchronous = async;
}
}
调用loop()循环或阻塞取出Handler发过来的消息:
//Looper.java
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
//...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//...
msg.target.dispatchMessage(msg);
//...
msg.recycleUnchecked();//消息复用,通过Message.obtain()
}
}
当我们使用Handler发消息的时候,就会next()就会在特定的时刻收到消息并处理,发送消息就是往MessageQueue插入一条消息:
pulbic class MessageQueue {
boolean enqueueMessage(Message msg, long when) {
//...
synchronized (this) {
//..
msg.when = when;
Message p = mMessages;//头
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
Message next() {
//...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//...
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
}
当我们调用next()获取的时候,不可能for里面一直循环获取,消息队列空闲的时候需要一种阻塞的机制等待消息的到来,Android2.2以前使用的是java线程里面的wait来实现的,Android 2.3以后就使用了linux管道加epoll的机制来实现,很多教程都没有详细的描述这个,强烈建议参考老罗的 Android应用程序消息处理机制(Looper、Handler)分析。
当消息队列处于空闲状态的情况下,可以向里面注册IdleHandler来执行空闲状态下的操作。当Looper.loop()里面获得消息就会调用Handler#dispatchMessage(msg)来分发消息:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们可以在Handler的构造方法里面指定Hanlder.Callback来处理消息,就不用重写handleMessage(Message msg)方法了,并且可以决定是否向handleMessage(Message msg)里面分发。
上面方法完整的过程:
final Handler mHandler;
new Thread(){
@Override
public void run() {
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//dosomething
}
};
Looper.loop();
}
}.start();
mHandler.sendMessage(Message.obtain());
主线程ActivityThread默认带有Looper,并且不能结束,前面Activity,Service的启动流程中生命周期回调,都是通过ActivityManagerService向ActivityThread中的ApplicationThread发起IPC调用,由于ApplicationThread中的方法是在又是在Binder线程池里面执行,为了提高系统的并发性,使用Handler发送消息,消息的发送方只要把消息发送到应用程序的消息队列,它可以马上返回去处理别的事情(或者不处理),而不需要等待消息的接收方去处理完这个消息才返回。
public class ActivityThread {
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}