Handler的几个问题
-
说一下handler的流程
这里我盗用一个网上的图,看懂这个流程图基本上就差不多了
image.png
首先就是通过Looer启动loop这个方法,让MessageQueue初始化,然后当你想给哪个线程发消息,就使用那个线程的Handler对象调用sendMessage(message),这时候消息会被发送到消息队列里,然后MessageQueue里的next()方法会计算时间,当这个message时间到了就会调用handler.dispatchMessage这个方法发送到当前持有Looper的这个线程,也就是我们的目标线程。
其实总结起来,handler跨线程通信的核心就是,当你想给哪个线程发消息,就使用那个线程的Handler对象调用sendMessage(message)。再来看一下简单的时序图,不涉及native层面的
未命名文件.png
-
一个线程有几个Looper?如何保证?
一个线程只会有一个Looper,其中的对应关系是:一个线程绑定一个Looper,一个Looper维护一个MessageQueue队列,而一个线程可以对应多个Handler。我们可以看Looper里的prepare源码是如何保证的
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));
}
可以看出抛出的异常Only one Looper may be created per thread 就是每个thread只能有一个Looper,那又是如何保证的呢?我们进一步去看这个 sThreadLocal的set方法
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
再看一下这个values的put方法吧
void put(ThreadLocal<?> key, Object value) {
cleanUp();
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
这个values维护的是一个类似hashmap的键值对集合,实际上Java的实现就是如此,(这里的实现方式和arraymap类型,google好像在Android上主推的就是这种SparseArray、ArrayMap用来代替HashMap,手机上很少有很大,数据一千以内的性能会比hashmap强)而Android SDK里用的是一个Object数组,下标偶数的作为key,奇数下标的作为value,从而实现key 和value的一一对应。实际上是通过Threadlocal对象为键, Looper对象为值的方式,保证了线程和Looper对象唯一对应的关系。
-
Handler泄漏的真正原因是什么?
先看一个简单的例子
private Button btn;
Handler handler =new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity.this.btn;
btn;
}
};
我们在handleMessage的回调里能很轻松的持有这个当前Activity的引用,其实这就是问题的所在,内部类持有外部类的引用,当外部类销毁的时候内部类还存在,比如handler执行了一个delay的方法的时候。我们继续追下去,无论handler的任何send或者post方法都会最终调用这个方法
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);
}
这个方法又进入到enqueueMessage这个方法里
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
注意这时候有个 msg.target = this Message 会持有一个对 Handler 的引用,当这个 Handler 是非静态内部类的时候,又会持有一个对外部类的引用(比如Activity)。所以是有一个持有的关系MessageQueue>message>handler>this(如Activity),所以如果是延时消息messge一直存在,导致如果Activity销毁的时候就泄漏了。
解决方式就是静态类+弱引用的方式
-
子线程中如何维护Handler?
首先我们要知道在主线程中是可以直接new Handler的,因为主线程已经帮我做好了一些准备工作
public static void main(String[] args) {
Looper.prepareMainLooper();
xxxxxx
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
如果我们要在子线程中使用的话,就必须要先prepare然后再loop。
使用完了如果需要销毁的话可以使用MessageQueue中的这个方法,可以看到主线程中是不可以退出的,只有我们在子线程中才可以使用。
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
- Handler机制Looper死循环为什么不会导致应用卡死?
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;
// 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
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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
当MessageQueue中无消息时,queue.next()会阻塞在nativePollOnce(),这里涉及到Linux pipe/epoll机制,nativePollOnce()被阻塞时,如果进一步的往下追到native方法里,是调用的epoll.wait()的方法,这时候主线程会释放CPU资源,进入休眠状态. 直到下个消息到达或者有事务发生,会通过pipe管道写入数据来唤醒主线程工作。
这里主线程进入休眠状态和死循环是有区别的:
死循环是指主线程死锁在这里,一直执行某一块代码,无法再响应其他事件。
休眠状态是指在内核状态里,主线程被挂起,线程状态转移到休眠状态。(核心还是linux的epoll.wait()方法)
-
推荐使用obtain的方式获取message
看源码可以知道, obtain是通过一个消息池Pool的方式存放消息的,这样获取使用消息的时候利用率高,更省内存
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}