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时则采用相应措施即可。