从使用Handler致内存泄漏角度源码追踪Handler工作机制
来源:互联网 发布:帖吧爆吧软件 编辑:程序博客网 时间:2024/05/16 23:41
使用Handler时内存泄漏分析
在Android中,处理完异步任务后常常会在主线程进行一些操作,所以我们可能会使用到Handler,下面是Handler的常见使用方法:
public class MainActivity extends AppCompatActivity { private Handler mHanlder = new Handler() { @Override public void handleMessage(Message msg) { //TODO } };}
但是我们这样使用时就会看到这样一句提示:
This Handler class should be static or leaks might occur
为什么会有这样的提示呢?
在Java中,匿名(非静态)内部类会隐性引用外部对象,而静态内部类则不会。
所以导致内存泄漏的原因是Activity被mHandler引用,而mHandler被其它比Activity生命周期更长的对象强引用。先上张Handler可以跨线程通信的简要说明图:
Handler工作原理图
一个线程只有一个Looper,一个Looper对应一个MessageQueue,但一个线程可对应多个Handler。
Handler可在任意线程创建,就以文章开头提到的最常见Handler创建方式为例:
mHandler = new Handler(){}是Activity的一个成员变量,Handler()无参构造函数相当于调用Handler(Looper.myLooper()),即将Handler与创建Activity所在的主线程的Looper绑定,与Handler(Looper.getMainLooper())是一样的效果,所以通过此Handler发送的消息可以会主线程进行处理,可以处理UI更新之类的消息。
源码分析
简单看了Handler工作原理图后,我们深入源码分析下为何Handler没释放?在此可根据源码简单分析一下:
//Activity中含有一个成员变量mHandler,mHandler是一个匿名内部类的实例,让我们看看Handler的构造函数中做了什么 public Handler() { this(null, false); } //Handler默认构造函数最终会调到此方法 public Handler(Callback callback, boolean async) { 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()); } } //Handler中有一个mLooper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //消息队列mQueue mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
大家已经看到Handler有一个成员变量是mLooper,那么此Looper是如何实例化的呢?
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
通过上述代码可以看到,Looper实例原来与当前线程相关联,而创建Handler时调用 myLooper() 方法是在哪个线程?主线程。
所以说Handler实例中的成员变量mLooper与mQueue最起码在APP运行期间一直存在。那么这又与内存泄漏有什么关系呢? 毕竟是mHandler强引用mLooper,而不是mLooper强引用mHandler,为什么不能释放?
那么这就要说到我们使用Handler时必会做一事情,postMessage()
下面继续看下Handler中发送消息的源码:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } public static Message obtain(Handler h) { Message m = obtain(); //Handler已被Message强引用! m.target = h; return m; } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } //最终会调到mQueue的enqueueMessage()方法 return enqueueMessage(queue, msg, 0); } boolean enqueueMessage(Message msg, long when) { //... Message p = mMessages; //... //用单向链表把msg保存起来 Message prev; for (;;) { prev = p; p = p.next; //已到链表底部或此msg的执行时间小于p的执行时间则退出循环,之后便可将新msg插入到prev结点与p结点之间, //保证MessageQueue中的Message按执行时间有序排序。其中when是(SystemClock.uptimeMillis() + delayMillis) //而不是传入的delayMillis,这也是Handler可以保证让Message顺序执行的原因。 if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; //... }
从上述代码可以看到,当我们用Handler post消息时,Message被保存到Looper.mQueue中,那么Message什么时候被销毁呢?让我们来看Looper如何对Message进行处理:
//ActivityThread.java public final class ActivityThread { public static void main(String[] args) { //... Looper.loop(); //... } } //Looper.java public static void loop() { //得到的是主线程的Looper 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 (;;) { //此处mQueue解除对msg的强引用,下面细分析 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); } //target即Handler, 在此处将msg交给Handler处理,所以一个MessageQueue中的每个Message都可对应不同的Handler。 msg.target.dispatchMessage(msg); //... msg.recycleUnchecked(); } }//Message.java void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; //此处不在强引用Handler target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { //Message加入对象池等待复用 next = sPool; sPool = this; sPoolSize++; } } }//MessageQueue.java Message next() { //... int nextPollTimeoutMillis = 0; for (;;) { //不到执行时间阻塞,Message可以准时执行的原因 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) { //计算msg准时执行需要等待时间 // 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; //Message引用方式prevMsg->msg(prevMsg.next)->msg.next,通过此方法将msg从单向链表中移除, //MessageQueue对Message实例的引用已解除 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; } //... // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
源码中重点位置已加注释,通过上述分析,我们终于可以得到一个完整的内存泄漏引用路径:
Activity <-Handler <- Message <-MessageQueue <-Looper <- MainThread
只要还有发出的Message未处理,Activity就不能释放,所以我们应该知道如何修改了:
- 解除Activity与Handler之间的引用:
private SafeHandler mHandler = new SafeHandler(this); private static class SafeHandler extends Handler{ WeakReference<MainActivity> mActivityReference; public SafeHandler(MainActivity activity){ mActivityReference = new WeakReference<MainActivity>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = mActivityReference.get(); if(activity != null){ activity.handleMessage(msg); } } } private void handleMessage(Message msg) { //TODO }
- 解除Handler与Message间的引用
mHandler.removeCallbacksAndMessages(null);
Android消息处理机制
根据上述的源码分析我们也理清了Message、Handler、MessageQueue与Looper这四者之间的协作机制:
Message:
用途:携带要发送到Looper所在线程的任务信息。
直接被引用位置:MessageQueueHandler:
用途:通过Looper绑定目标线程,负责在任意线程将Message实例加入目标线程的Looper的MessageQueue中,之后用于接收要在目标线程处理的Message信息。
直接被引用位置:任何对象都可包含Handler,Handler通过引用存储在Thread上的Looper来建立绑定关系。MessageQueue:
用途:MessageQueue引用所有要发送的Message,持有一个对应的Handler实例引用,将每一个新加入的Message都插入单向链表中来保存。
直接被引用位置:每个Looper实例化时都会初始化一个MessageQueue对象.Looper:
用途:负责接收Message将其加入MessageQueue,之后再派发出去交给每个Message对应的Handler进行处理。
直接被引用位置:Thread的ThreadLocal(线程局部变量)。
下面来个图比喻下(其实不太恬当,无实操>.>):
班组:Thread
煤块:Message
皮带:MessageQueue
班组煤块工人:Handler
皮带运输机:Looper
任意班组的煤块工人都可以扔煤块过来(任意Thread发Message),扔到皮带上皮带将其按一定次序整理好(MessageQueue为一Message队列),之后运输机带动皮带将其一个个处理(Looper.loop()),每个煤块上都有煤块工人的标记(Message引用Handler),皮带运输机根据标记将其运到各个煤块工人对应班组的煤块堆上(Looper将Message发到相应的Handler的handleMessage()处理),当然图上只有一个堆可以看成只有一个班组的煤块工人在扔。
Done.
- 从使用Handler致内存泄漏角度源码追踪Handler工作机制
- Handler机制-从源码角度分析
- 从源码角度剖析Handler 机制
- 从源码角度剖析Handler机制
- 【Android】从源码角度看Handler机制
- 从源码角度理解handler,子线称中使用Handler
- 工作多年,再来从源码的角度一步一步回忆复习Handler的机制
- Handler机制解析(源码角度)
- 从源码角度解析Handler
- 从源码角度分析java层Handler机制
- 重新从源码的角度看Handler消息通信机制
- 使用handler内存泄漏解决
- 从源码角度看Handler原理
- 从源码角度深入理解Handler
- 从源码的角度分析Handler机
- 从Android源码角度对Handler,MessageQueue,Looper之间消息传递工作原理的理解
- 【 Android】handler异步消息处理机制完全解析,带你从源码的角度彻底理解
- Android 从源码角度分析消息处理机制(Handler,Looper,Message)
- oracle数据库统计表空间使用情况
- 关于websocket
- 湖南省首届逻辑推理大赛 第一环节——争分夺秒
- oracle数据导入另外一个用户下面
- 一头扎进算法导论-堆排序
- 从使用Handler致内存泄漏角度源码追踪Handler工作机制
- oracle通过dblink的方式导出数据
- Swift3.0 UITableView/UICollectionView默认选中某个 cell
- C/C++ Windows API——线程挂起、唤醒与终止
- oracle用户解锁
- HDOJ2012
- android CoordinatorLayout使用
- ubuntu下好玩的一个脚本
- R语言之矩阵操作