Android Handler 通讯源码分析以及内存泄漏分析
Handler的通讯原理
大体流程
Handler 多线程通讯范式
HandlerThreadthread=newHandlerThread("t");
// 开启线程 t
thread.start();
// 构造这个线程的消息接收回调
classThreadHandlerextendsHandler{
publicThreadHandler(Looperlooper){
looper=looper;
}
覆写回调方法
@Override
publicvoidhandleMessage(Messagemsg) {
// do something
super.handleMessage(msg);
}
}
ThreadHandlerth=newThreadHandler(thread.getLooper());
// 构造消息
Messagemsg=Message.obtain(null,1,"arg1");
// 给线程t 发送消息
th.sendMessage(msg);
源码分析
先来看看 HandlerThread, 这个类继承了Thread,里面维护着一个Looper对象,主要看看里面覆写的run()方法
@Override
publicvoidrun() {
mTid=Process.myTid();
Looper.prepare();
synchronized(this) {
mLooper=Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid=-1;
}
可以看到 是先去获取Looper对象,这个Looper对象被封装在ThreadLocal 里面,换句话说就是每一个线程有着自己的Looper。
这里为什么要唤醒其他线程呢? 我们看到getLooper() 方法
publicLoopergetLooper() {
if(!isAlive()) {
returnnull;
}
// If the thread has been started, wait until the looper has been created.
synchronized(this) {
while(isAlive()&&mLooper==null) {
try{
wait();
}catch(InterruptedExceptione) {
}
}
}
returnmLooper;
}
因为HandlerThread 是存在于子线程,所以如果当我们在主线程先去获取Looper对象的时,而这个时侯线程并没有执行run方法,也就是没有被开启,也就无法从ThreadLocal中获取Looper对象。
为了解决这个问题,所以内部写了一个唤醒等待机制解决了Looper对象同步问题。
再来看看Looper类,找到里面的loop()方法,分析部分代码
publicstaticvoidloop() {
// 获取ThreadLocal 里面的Looper对象
finalLooperme=myLooper();
if(me==null) {
thrownewRuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 获取Looper 里面的 MessageQueue
finalMessageQueuequeue=me.mQueue;
....
// 循环遍历MessageQueue队列
for(;;) {
// 出队
//这里可能会存在堵塞,因为 MessegeQueue 的 next(); 方法添加了同步锁,是线程安全的
Messagemsg=queue.next();
if(msg==null) {
// No message indicates that the message queue is quitting.
return;
}
.....
}
}
那Looper 类又是怎么和Handler 扯上关系的呢? 答案就是再Handler的构造方法里注入进去,可见上面的范式。
然后再看看Handler的 sendMessage(Message msg) 方法, 发现它最后是调用MessageQueue类中的enqueueMessage(Message msg, long when) 方法,这里不对 MessageQueue类进行详细分析。
大概总结一下就是 Looper 与Handler 是消费者与生产者的关系, Handler生产消息到MessageQueue消息队列, Looper 消费消息,然后回调Handler 类里我们要覆写的 handlerMessage(Message msg)方法。
这里的MessageQueue的出队和入队都添加了同步锁,且锁住的对象是this,也就说明不管哪一个线程,他们的MessageQueue的对象是一样的。(MessageQueue里的本地方法nativeInit()会在本地创建一个NativeMessageQueue对象。)
内存泄漏问题
先看看下面Activity里写Handler 消息处理类的写法
publicclassMainActivityextendAppCompatActivity{
....
privateHandlermHandler=newHandler() {
@Override
publicvoidhandleMessage(android.os.Messagemsg) {
super.handleMessage(msg);
....
Intentintent=newIntent(MainActivity.this,OtherActivity.class);
startServiceforResult(intent,1);
}
};
}
这个时候一般android studio 都会提示你将你的Handler 指定为静态类,并且对外部类进行一个弱引用。下面来分析一下原因。
为何要指定为静态类
因为如果Handler 被定义为内部类的时,使用到它的时侯,它的外部类就必须被实例话,如果外部类我们已经用不到了,但是还是被实例起来,并且与Handler 关联,Handler持有着Message还没有被消费使得外部类无法被GC。
定义成静态类的目地就是,静态内部类被使用时,不会实例化外部类,且只会被加载一次,全局统一。
为何要使用弱引用
上面说到,因为会影响外部类的GC,所以当内部类要用到外部类的引用时,使用弱引用,使其在OMM前被GC 回 收。
问题解决
知道了影响外部类被GC的原因后,就好解决了。
在Activity销毁时清除所有消息
@OverrideprotectedvoidonDestroy() {
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
Hander 使用静态类声明 ,使用WeakReference 弱引用外部类。
privateHandlermHandler=newMHandler(this);
staticclassMHandlerextendsHandler{
privateWeakReference<MainActivity>ma;
publicMHandler(MainActivityma) {
ma=ma;
}
@Override
publicvoidhandleMessage(android.os.Messagemsg) {
super.handleMessage(msg);
Intentintent=newIntent(ma.get(),OtherActivity.class);
// do somthing
}
}