我们都知道, 在进行 Android 应用开发的时候, 主线程 (又称为 UI 线程) 不能进行网络请求一类的耗时操作, 必须开启一个子线程来处理; 但是在子线程里面又不能进行更新 UI 的操作, 更新 UI 必须在主线程里操作. 那么当子线程进行完耗时操作时如何通知主线程更新 UI 呐? 这个时候 Handler 就孕育而生了.
Handler 被称之为 Android 内部消息机制, 他的作用是在子线程进行完耗时操作的时发送消息通知主线程来更新 UI.
使用
- private Handler handler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == 1){
- Toast.makeText(JavaDemo.this, "更新 UI 操作", Toast.LENGTH_SHORT).show();
- }
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- Thread.sleep(5000);
- handler.sendEmptyMessage(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
复制代码
这里采用实例化一个 Thread 线程并通过线程阻塞 sleep()模拟耗时操作. 我们可以从上面代码看到, 当线程完成耗时操作之后, 我们使用 sendEmptyMessage()将消息发送给主线程中的 handler, 覆写主线程中 handler 的 handMessage()方法并在这个方法里进行更新 UI 的操作. 这里需要注意一点, 其实我们这么写 handler 是错误的, 会引发内存泄露的问题, 具体如何引起的我们后面再分析.
源码分析
handler 的源码主要是由 Looper,MessageQueue,Handler,ThreadLocal 几个部分组成. 下面一个一个来进行分析.
Looper:
Looper 主要由两部分东西组成, prepare()和 loop(). 首先来看 prepare().
- 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));
- }
复制代码
可以看到, 在 prepare()中主要做了两件事. 第一, 判断 ThreadLocal 中是否能够取出 Looper 对象, 如果不为空, 则抛出 **"一个线程只能有一个 Looper"** 的异常. 这就代表说在一个线程里有且仅可以创建一个 Looper, 如果多次调用 prepare()方法创建 Looper 则程序会抛出异常. 如果发现线程之中没有 Looper, 那么便会 new 一个 Looper 将其 set 进入 ThreadLocal 当中去. 那么这个 ThreadLocal 又是什么?
ThreadLocal:
ThreadLocal 被称为线程内部存储类. 他有一个特点就是在 A 线程里面进行 set()存储的数据, 只能在 A 线程 get()取出.
- final ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
- threadLocal.set(true);
- new Thread("thread1"){
- @Override
- public void run() {
- Log.i("thread1",threadLocal.get() + "");
- }
- }.start();
复制代码
我们看到上面的例子, 在主线程中将 true 放入了 ThreadLocal 中, 之后在子线程试图从中取出, 结果发现此时报 null. 可见, 在主线程中存储的数据必须在主线程才可以取出. 那么我们再从 ThreadLocal 内部代码看看为什么会出现这种操作.
- public void set(T value) {
- Thread currentThread = Thread.currentThread();
- Values values = values(currentThread);
- if (values == null) {
- values = initializeValues(currentThread);
- }
- values.put(this, value);
- }
复制代码
我们看到 set()方法中, 首先会去获取当前线程 currentThread, 之后通过 values 从当前线程中获取数据. 判断这个数据是否为空 **"if (values == null)", 为空则调用 initializeValues()方法赋初值, 否则将获取到的 value 值 put()进入 values 中 "values.put(this, value)"**.
接着来看 get()方法.
- public T get() {
- // Optimized for the fast path.
- Thread currentThread = Thread.currentThread();
- Values values = values(currentThread);
- if (values != null) {
- Object[] table = values.table;
- int index = hash & values.mask;
- if (this.reference == table[index]) {
- return (T) table[index + 1];
- }
- } else {
- values = initializeValues(currentThread);
- }
- return (T) values.getAfterMiss(this);
- }
复制代码
从代码中可以看到, get()方法的操作其实和 set()差不多, 都是先获取当前线程, 如果 values 不为空则将值返回, 如果为空则先赋初值然后再返回初始值. 由于 set()和 get()方法都涉及到了从 currentThread()中获取数据, 这也就解释了为什么在一个线程中存储数据必须要在相同线程中才能取的到的原因. 上述只是对 ThreadLocal 这个类做简单的分析, 其实这个类内部还有很多东西, 由于篇幅原因再加上 ThreadLocal 并非这篇文章重点, 所以这里我们只是简单叙述, 有机会专门写一篇来讲解 ThreadLocal.
上面说了在判断 ThreadLocal 中取出来的数据为空时会去 new 一个 Looper, 并把他添加进 ThreadLocal 中, 那我们来看看 new 出的这个 Looper 的构造方法.
- private Looper(boolean quitAllowed) {
- mQueue = new MessageQueue(quitAllowed);
- mThread = Thread.currentThread();
- }
复制代码
构造方法非常简单, 里面实例化一个消息队列 MessageQueue, 并且还会获取当前线程. 也就是说消息队列此时已经和当前线程绑定, 其作用的区域为当前实例化 Looper 的线程 . 我们再来看 loop().
- public static @Nullable Looper myLooper() {
- return sThreadLocal.get();
- }
- 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;
- for (;;) {
- Message msg = queue.next(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- try {
- msg.target.dispatchMessage(msg);
- } finally {
- if (traceTag != 0) {
- Trace.traceEnd(traceTag);
- }
- }
- msg.recycleUnchecked();
- }
- }
复制代码
这里对代码进行了一些删减. 可以看到首先会调用 myLooper()去获取一个 Looper 对象. 而从 myLooper()的源码看到从 ThreadLocal 里取出在 prepare()中存入的 Looper 对象. 先判断对象是否为空, 若为空, 则抛出异常告诉程序在调用 loop()方法之前必须要有一个 Looper. 这也就说在使用的时候, prepare()方法必须要在 loop()方法之前被调用.
之后通过 Looper 对象获取消息队列 MessageQueue, 进入一个死循环 for( ; ; ), 调用 MessageQueue 的 next()方法, 不断从消息队列里获取消息 Message, 如果获取的消息为空, 则 return 跳出循环, 如果不为空, 则将 msg 消息交给 msg.target.dispatchMessage(msg)去处理, 那么这个 dispatchMessage()又是什么, 其实这个就是 handler, 不过我们后面再分析. 最后调用 recycleUnchecked()方法回收. 到此 Looper 源码分析完成.
Handler:
一般使用 handler 的时候, 我们都会先 new 实例化一个 handler 对象, 那么我们就从 handler 的构造方法讲起.
- public Handler(Callback callback, boolean async) {
- mLooper = Looper.myLooper();
- if (mLooper == null) {
- throw new RuntimeException(
- "Can't create handler inside thread that has not called Looper.prepare()");
- }
- mQueue = mLooper.mQueue;
- mCallback = callback;
- mAsynchronous = async;
- }
复制代码
从构造方法我们可以看到, 仍然是先调用 myLooper()方法, 从 ThreadLocal 中取出 Looper 对象, 之后判断对象是否为空, 为空则抛异常, 不为空则获取 MessageQueue 消息队列, 这样 Handler 也就和消息队里进行了绑定.
之后在使用 handler 的时候一般都会使用 sendMessage()方法去发送消息. 看看这个方法内部做了什么操作.
- public final boolean sendMessage(Message msg)
- {
- return sendMessageDelayed(msg, 0);
- }
- public final boolean sendMessageDelayed(Message msg, long delayMillis)
- {
- if (delayMillis <0) {
- delayMillis = 0;
- }
- return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- }
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- MessageQueue queue = mQueue;
- if (queue == null) {
- RuntimeException e = new RuntimeException(
- this + "sendMessageAtTime() called with no mQueue");
- Log.w("Looper", e.getMessage(), e);
- return false;
- }
- return enqueueMessage(queue, msg, uptimeMillis);
- }
复制代码
可以看到里面层层递进, sendMessage()里面调用 sendMessageDelayed(),sendMessageDelayed()又调用了 sendMessageAtTime(), 最终发现其实所有的发送消息方法最后都会来到 sendMessageAtTime()方法里, 于是着重看这个方法. 这个方法里先获取消息队列 MessageQueue, 然后将队列 queue, 发送的消息 msg 以及延时时间 uptimeMillis 一起传入到 enqueueMessage()里去.
- private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
- msg.target = this;
- if (mAsynchronous) {
- msg.setAsynchronous(true);
- }
- return queue.enqueueMessage(msg, uptimeMillis);
- }
复制代码
在这个方法里, 又调用 queue.enqueueMessage()方法将发射的消息传入到消息队列 MessageQueue 当中去. 也就是说从 handler 中发送的消息其实最后全都送到了 MessageQueue 当中去, 而之前在分析 loop 的时候我们看到, 在 loop 里面又调用了 MessageQueue 的 next()方法, 把里面的消息全部交给 dispatchMessage 去处理. 所以最后我们来看看 dispatchMessage()方法
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
复制代码
在 handler 里面我们找到了这个方法. 这个方法会根据是否有 callback 而去调用不同方法, 如果有回调则调用 handleCallback(), 如果没有回调则调用 handleMessage();
- public void handleMessage(Message msg) {
- }
复制代码
我们可以看到 handleMessage()中是一个空方法, 这就代表只要覆写了这个方法所有的一切就全部由我们自己来写逻辑了.
handler 内部整体流程:
我们在使用 handler 的时候, 若在主线程, 由于主线程已经有一个 Looper 了, 所以不需要在创建一个 Looper**(一个线程中有且仅可以有一个 Looper, 不然会报错)**. 若在子线程, 则先调用 Looper.prepare()创建一个 Looper 对象, 之后再实例化一个 Handler 对象, 这个 Handler 会和 Looper 中的 MessageQueue 进行绑定, 并将 sendMessage()发送的消息存储到这个绑定的消息队列当中去. 然后我们调用 Looper.loop()方法, 不断的从消息队列 MessageQueue 当中取出消息交给 dispatchMessage()去处理. dispatchMessage()最后调用 handleMessage(), 所有的逻辑都交给我们自己去处理. 到此, Handler 内部原理全部讲解完成.
内存泄露:
在文章开篇我写了一个例子来演示 handler 如何使用, 并且在最后说这么使用会造成内存泄漏. 那么现在来讲讲为什么这么写会造成内存泄露.
在 java 中非静态内部类和匿名内部类都会隐式持有当前类的外部类, 由于 Handler 是非静态内部类所以其持有当前 Activity 的隐式引用, 如果 Handler 没有被释放, 其所持有的外部引用也就是 Activity 也不可能被释放, 当一个对象不需要再使用了, 本来该被回收时, 而有另外一个正在使用的对象持有它的引用从而导致它不能被回收, 这导致本该被回收的对象不能被回收而停留在堆内存中, 这就产生了内存泄漏.
解决办法:
方法一: 通过逻辑代码处理来进行保护.
我们在关闭 Activity 的时候停掉你的后台线程. 线程停止掉了, 就等于切断了 handler 与外部的连接, 那么 Activity 在退出的时候就会被回收了.
如果你在写一个验证码之类的倒数计时器, 用到 delay 方法去发送消息的时候, 我们在销毁 Activity 的时候, 应该在 onDestroy()方法里面调用 removeCallbacks()将消息移除掉即可.
方法二: 将 Handler 声明为静态类
静态内部类不会持有外部类的对象, 并且为了避免在静态内部类中能够使用到 Activity 对象, 这里我们采用弱引用的方式来持有 Activity 对象. 这里顺带说下弱引用(WeakReference), 弱引用所持有的对象, 不管 Java 内存是否满了只要调用了 GC 就一定会被回收掉. 所以我们将最早使用的 handler 代码改造一下.
- static class MyHandler extends Handler {
- WeakReference<Activity> mActivityReference;
- MyHandler(Activity activity) {
- mActivityReference= new WeakReference<Activity>(activity);
- }
- @Override
- public void handleMessage(Message msg) {
- final Activity activity = mActivityReference.get();
- if (activity != null) {
- mImageView.setImageBitmap(mBitmap);
- }
- }
- }
复制代码
可见到我们写了一个静态类来继承 Handler, 并且在构造方法里面用弱引用持有了传递进来的 Activity 对象, 在 handleMessage()方法里面从弱引用中取出 activity 对象, 如果 activity 对象不为空, 则直接进行更新 UI 的操作.
到此, handler 所有的内容讲解完毕!
来源: https://juejin.im/post/5b8029a26fb9a019d53e8aee