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