本文主要介绍Handle的原理、如何处理Handler可能导致的内存泄漏问题,需要深入理解Handler的机制,当我们在创建Handler的时候,要明白现在用的是哪个Looper?其他线程如何使用Looper进行通信,释放Looper的区别,以及HandlerThread的原理与用法等等内容。
Handler 使用方式 假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。于是在Android中采用单线程模型处理 UI 操作,通过 Handler 切换到 UI 线程,解决子线程中无法访问 UI 的问题。
1 2 3 4 5 6 7 8 9 10 11 12 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.main_tv); new Thread(()-> { textView.setText("" ); }).start(); } }
上面的代码会抛出CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
方式一 post(Runnable) 创建一个 Handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MainActivity extends AppCompatActivity { Handler handler = new Handler(); @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.main_tv); new Thread(()-> { handler.post(()-> { textView.setText("" ); }); }).start(); } }
方式二 sendMessage(Message) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class MainActivity extends AppCompatActivity { private static final int UPDATE_CONTENT_CODE = 101 ; Handler handler = new Handler() { @Override public void handleMessage (@NonNull Message msg) { switch (msg.what) { case UPDATE_CONTENT_CODE: textView.setText("" ); break ; default : break ; } } }; private TextView textView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.main_tv); new Thread(()-> { Message msg = Message.obtain(); msg.what = UPDATE_CONTENT_CODE; handler.sendMessage(msg); }).start(); } }
Handler 存在的问题 当我们直接写出如下代码时:
1 2 3 4 5 6 Handler handler = new Handler() { @Override public void handleMessage (@NonNull Message msg) { super .handleMessage(msg); } };
AndroidStudio会提示我们:This Handler class should be static or leaks might occur (anonymous android.os.Handler)即可能会发生内存泄漏问题!
此时就相当于这个Handler是一个内部类,非static内部类或者匿名内部类都会持有外部类的引用(textView)。如果此时Activty退出了, handler持有他的引用,则这个Activity 并不会被销毁,其实还是在内存中,所以就造成了内存泄漏 (Memory Leak) 的问题。
可以使用静态的内部类 + 虚引用可以解决这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class MainActivity extends AppCompatActivity { private static final int UPDATE_CONTENT_CODE = 101 ; private TextView textView; private final Handler handler = new MyHandler(this ); static class MyHandler extends Handler { private final WeakReference<MainActivity> mActivityWR; public MyHandler (MainActivity activity) { mActivityWR = new WeakReference<>(activity); } @Override public void handleMessage (@NonNull Message msg) { super .handleMessage(msg); MainActivity activity = mActivityWR.get(); if (activity != null ) { switch (msg.what) { case UPDATE_CONTENT_CODE: activity.textView.setText("" ); break ; default : break ; } } } } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.main_tv); new Thread(()-> { Message msg = Message.obtain(); msg.what = UPDATE_CONTENT_CODE; handler.sendMessage(msg); }).start(); } @Override protected void onDestroy () { super .onDestroy(); handler.removeCallbacks(postRunnable); handler.removeMessages(UPDATE_CONTENT_CODE); } }
另外还有一种解决方式:
1 2 3 4 5 6 @Override protected void onDestroy () { super .onDestroy(); mHandler.removeCallbacksAndMessages(null ); }
这种方式会导致外部类Activity生命周期结束时消息队列中的所有消息被清空,所以更推荐使用静态内部类 + 弱引用的方式。
为什么这两种方式可以解决呢?先来看看Handler 通信机制吧:
Handler 通信机制
1、创建Handler,并采用当前线程的Looper创建消息循环系统;
2、Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;
3、Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。
1、Handler 发送消息 1 handler.sendMessage(msg);
调用链如下:sendMessage -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage -> queue.enqueueMessage。其实就是Handler的MessageQueue插入了一个新的的Message对象。
对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。
1 2 3 public final boolean post (@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0 ); }
2、消息入队列 在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage回调到该handler中处理消息:
1 2 3 4 5 6 7 private boolean enqueueMessage (MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this ; if (mAsynchronous) { msg.setAsynchronous(true ); } return queue.enqueueMessage(msg, uptimeMillis); }
在MessageQueue中,由Message的消息链表进行入队列。
3、Looper 处理消息 再说处理消息之前,先看Looper是如何构建与获取的:
1、构造Looper时,构建消息循环队列,并获取当前线程
1 2 3 4 private Looper (boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:
1 2 3 4 5 6 7 8 9 10 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)); }
这里创建Looper,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?
1 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。
那么Looper在loop中如何处理消息呢?
在loop中,一个循环,通过next取出MessageQueue中的消息,若取出的消息为null,则结束循环,返回。设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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; Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); if (msg == null ) { return ; } Printer logging = me.mLogging; if (logging != null ) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null ) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" Long.toHexString(ident) + " to 0x" Long.toHexString(newIdent) + " while dispatching to " msg.target.getClass().getName() + " " msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }
4、handler处理消息 Looper把消息回调到handler的dispatchMessage中进行消息处理:
若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback。
若handler的mCallback不为空,则交由通过callback创建handler方式去处理。
否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。
1 2 3 4 5 6 7 8 9 10 11 12 public void dispatchMessage (Message msg) { if (msg.callback != null ) { handleCallback(msg); } else { if (mCallback != null ) { if (mCallback.handleMessage(msg)) { return ; } } handleMessage(msg); } }
Handler 进行子线程通信 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class OtherActivity extends AppCompatActivity { private static final String TAG = "OtherActivity" ; Handler handler1; Handler handler2; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_other); new ThreadA().start(); new ThreadB().start(); } class ThreadA extends Thread { @Override public void run () { super .run(); Looper.prepare(); handler1 = new Handler(Looper.myLooper()){ @Override public void handleMessage (@NonNull Message msg) { super .handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; try { Thread.sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (handler2 != null ) handler2.sendEmptyMessage(2 ) ; Looper.loop(); } } class ThreadB extends Thread { @Override public void run () { super .run(); Looper.prepare(); handler2 = new Handler(Looper.myLooper()){ @Override public void handleMessage (@NonNull Message msg) { super .handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; try { Thread.sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (handler1 != null ) handler1.sendEmptyMessage(5 ) ; Looper.loop(); } } }
1、调用Looper类的 prepare()
方法可以为当前线程创建一个消息循环,调用loop()
方法使之处理信息,直到循环结束。
2、Handler有几个构造重载,如果构造时不提供Looper类对象参数,默认会获取当前线程的Looper对象,即将当前线程的消息循环作为Handler关联的消息循环。
3、消息处理机制中,消息存放在一个消息队列中,而线程围绕这个队列进入一个无限循环,直到程序退出。
如果队列中有消息,线程就会把消息取出来,并分发给相应的Handler进行处理;
如果队列中没有消息,线程就会进入空闲等待状态,等待下一个消息的到来;
HandlerThread 本质 在上面利用Handler完成两个线程通信的代码中,需要调用Looper.prepare() 为一个线程开启一个消息循环,默认情况下Android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。 然后通过Looper.loop() 让Looper开始工作,从消息队列里取消息,处理消息。
所以要使用Handler完成线程之间的通信,首先需要调用Looper.prepare() 为该线程开启消息循环,然后创建Handle,然后调用 Looper.loop() 开始工作。这都是很常规的流程,所以:
HandlerThread 已经帮我们做好了这些事情,HandlerThread 本质上就是一个普通Thread,只不过内部建立了Looper。
HandlerThread 用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class OtherActivity extends AppCompatActivity { private static final String TAG = "OtherActivity" ; private Handler handler1; private Handler handler2; private HandlerThread handlerThread1; private HandlerThread handlerThread2; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_other); handlerThread1 = new HandlerThread("handle-thread-1" ); handlerThread2 = new HandlerThread("handle-thread-2" ); handlerThread1.start(); handlerThread2.start(); handler1 = new Handler(handlerThread1.getLooper()) { @Override public void handleMessage (@NonNull Message msg) { super .handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; handler2 = new Handler(handlerThread2.getLooper()) { @Override public void handleMessage (@NonNull Message msg) { super .handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; handler2.sendEmptyMessage(2 ); handler1.sendEmptyMessage(5 ); } @Override protected void onDestroy () { super .onDestroy(); handlerThread1.quit(); handlerThread2.quitSafely(); } }
创建 Looper 并执行 loop()
的线程在任务结束的时候,需要手动调用 quit。反之,线程将由于 loop() 的轮询一直处于可运行状态,CPU 资源无法释放。更有可能因为 Thread
作为 GC Root
持有超出生命周期的实例引发内存泄漏。
SDK 推荐使用 quitSafely()
去终止 Looper,原因是其只会剔除执行时刻晚于当前调用时刻
的 Message。这样可以保证 quitSafely 调用的那刻,满足执行时间条件的 Message 继续保留在队列中,在都执行完毕才退出轮询。
那么主线程需要 quit 吗?其实不需要,在内存不足的时候 App 由 AMS 直接回收进程。因为主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要! 换言之,ActivityThread 的作用域超过了这些组件,不该由这些组件去处理它的结束。比如,Activity destroy 了,ActivityThread 仍然要处理其他 Activity 或 Service 等组件的事务,不能结束。
Handler 在系统中的身影 其实Android本身就是一个巨大的消息处理机,ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。APP进程中UI事件的执行代码段都是由ActivityThread提供的。也就是说,主线程实例是存在的,只是创建它的代码我们不可见。ActivityThread的main函数就是在这个主线程里被执行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public final class ActivityThread { private static ActivityThread sCurrentActivityThread; public static ActivityThread currentActivityThread () { return sCurrentActivityThread; } private void attach (boolean system) { sCurrentActivityThread = this ; } public static void main (String[] args) { Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false ); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited" ); } }
Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施即可。