关于android:Android高级进阶之路四一文读懂-Handler-机制

31次阅读

共计 10153 个字符,预计需要花费 26 分钟才能阅读完成。

前言


做 Android 开发必定离不开跟 Handler 打交道,它通常被咱们用来做主线程与子线程之间的通信工具,而 Handler 作为 Android 中音讯机制的重要一员也的确给咱们的开发带来了极大的便当。

能够说 只有有异步线程与主线程通信的中央就肯定会有 Handler

那么,Handler 的通信机制的背地的原理是什么?

本文带你揭晓。

留神:本文所展现的零碎源码基于 Android-27,并有所删减。

[](https://link.juejin.cn/?targe…)

1. 重识 Handler

咱们能够应用 Handler 发送并解决 与一个线程关联的 Message 和 Runnable。(留神:Runnable 会被封装进一个 Message,所以它实质上还是一个 Message

每个 Handler 都会跟一个线程绑定,并与该线程的 MessageQueue 关联在一起,从而实现音讯的治理以及线程间通信。

[](https://link.juejin.cn/?targe…)

1.1 Handler 的根本用法

android.os.Handler handler = new Handler(){
  @Override
  public void handleMessage(final Message msg) {// 这里承受并解决音讯}
};
// 发送音讯
handler.sendMessage(message);
handler.post(runnable);

实例化一个 Handler 重写 handleMessage 办法,而后在须要的时候调用它的 send 以及 post 系列办法 就能够了,非常简单易用,并且反对延时音讯。(更多办法可查问 API 文档)

然而奇怪,咱们并没有看到任何 MessageQueue 的身影,也没看到它与线程绑定的逻辑,这是怎么回事

[](https://link.juejin.cn/?targe…)

2. Handler 原理解析

置信大家早就据说过了 Looper 以及 MessageQueue 了,我就不多绕弯子了。

不过在开始剖析原理之前,先 明确咱们的问题

  1. Handler 是如何与线程关联的?
  2. Handler 收回去的音讯是谁治理的?
  3. 音讯又是怎么回到 handleMessage() 办法的?
  4. 线程的切换是怎么回事?

[](https://link.juejin.cn/?targe…)

2.1 Handler 与 Looper 的关联

实际上咱们在实例化 Handler 的时候 Handler 会去查看以后线程的 Looper 是否存在,如果不存在则会报异样,也就是说 在创立 Handler 之前肯定须要先创立 Looper

代码如下:

public Handler(Callback callback, boolean async) {
        // 查看以后的线程是否有 Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
        }
        //Looper 持有一个 MessageQueue
        mQueue = mLooper.mQueue;
}

这个异样置信很多同学遇到过,而咱们平时间接应用感触不到这个异样是因为主线程曾经为咱们创立好了 Looper,先记住,前面会讲。

一个残缺的 Handler 应用例子其实是这样的:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {Looper.prepare();
        mHandler = new Handler() {public void handleMessage(Message msg) {// process incoming messages here}
        };
        Looper.loop();}
}

Looper.prepare() :

//Looper
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.prepare()  办法来创立 Looper,并且会 借助 ThreadLocal 来实现与以后线程的绑定 性能。Looper.loop() 则会开始一直尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler(见【2.3】)

也就是说 Handler 跟线程的关联是靠 Looper 来实现的。

[](https://link.juejin.cn/?targe…)

2.2 Message 的存储与治理

Handler 提供了一些列的办法让咱们来发送音讯,如 send()系列 post()系列。

不过不论咱们调用什么办法,最终都会走到 MessageQueue.enqueueMessage(Message,long) 办法。

sendEmptyMessage(int)  办法为例:

//Handler
sendEmptyMessage(int)
  -> sendEmptyMessageDelayed(int,int)
    -> sendMessageAtTime(Message,long)
      -> enqueueMessage(MessageQueue,Message,long)
              -> queue.enqueueMessage(Message, long);

到了这里,音讯的管理者 MessageQueue 也就露出了水面
MessageQueue 顾明思议,就是个队列,负责音讯的入队出队。

[](https://link.juejin.cn/?targe…)

2.3 Message 的散发与解决

理解分明 Message 的发送与存储管理后,就该揭开散发与解决的面纱了。

后面说到了 Looper.loop()  负责对音讯的散发,本章节进行剖析。

先来看看所波及到的办法:

//Looper
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 (;;) {
       // 一直从 MessageQueue 获取 音讯
        Message msg = queue.next(); // might block
        // 退出 Looper 
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //...
        try {msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {//...}
        //...
                // 回收 message, 见【3.5】msg.recycleUnchecked();}
}

loop() 里调用了 MessageQueue.next() :

//MessageQueue
Message next() {
    //...
    for (;;) {
        //...
        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) {if (now < msg.when) {
                    // 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;
                    if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}
                    msg.next = null;
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {dispose();
                return null;
            }
        }

        // Run the idle handlers. 对于 IdleHandler 自行理解
        //...
    }
}

还调用了 msg.target.dispatchMessage(msg),msg.target 就是发送该音讯的 Handler,这样就回调到了 Handler 那边去了:

//Handler
public void dispatchMessage(Message msg) {
  //msg.callback 是 Runnable,如果是 post 办法则会走这个 if
  if (msg.callback != null) {handleCallback(msg);
  } else {
    //callback 见【3.4】if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
    }
    // 回调到 Handler 的 handleMessage 办法
    handleMessage(msg);
  }
}

留神:dispatchMessage() 办法针对 Runnable 的办法做了非凡解决,如果是,则会间接执行 Runnable.run()

剖析:Looper.loop() 是个死循环,会 一直调用 MessageQueue.next() 获取 Message,并调用 msg.target.dispatchMessage(msg) 回到了 Handler 来散发音讯,以此来实现音讯的回调

留神:loop()办法并不会卡死主线程,见【6】。

那么 线程的切换又是怎么回事 呢?
很多人搞不懂这个原理,然而其实非常简单,咱们将所波及的办法调用栈画进去,如下:

Thread.foo(){Looper.loop()
     -> MessageQueue.next()
       -> Message.target.dispatchMessage()
        -> Handler.handleMessage()}

不言而喻,Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定。

平时咱们用的时候从异步线程发送音讯到 Handler,这个 Handler 的 handleMessage() 办法是在主线程调用的,所以音讯就从异步线程切换到了主线程。

[](https://link.juejin.cn/?targe…)

2.3 图解原理

文字版的原理解析到这里就完结了,如果你看到这里还是没有懂,没关系,我特意给你们筹备了些图,配合着后面几个章节,再多看几遍,肯定能够吃透。

[](https://link.juejin.cn/?targe…)

2.4 小结

Handler 的背地有着 Looper 以及 MessageQueue 的帮助,三者通力合作,分工明确。

尝试小结一下它们的职责,如下:

  • Looper:负责关联线程以及音讯的散发 在该线程下 ** 从 MessageQueue 获取 Message,分发给 Handler;
  • MessageQueue:是个队列,负责音讯的存储与治理,负责管理由 Handler 发送过去的 Message;
  • Handler : 负责发送并解决音讯,面向开发者,提供 API,并暗藏背地实现的细节。

对【2】章节提出的问题用一句话总结:

Handler 发送的音讯由 MessageQueue 存储管理,并由 Loopler 负责回调音讯到 handleMessage()。

线程的转换由 Looper 实现,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。

[](https://link.juejin.cn/?targe…)

3. Handler 的延长

Handler 尽管简略易用,然而要用好它还是须要留神一点,另外 Handler 相干 还有些鲜为人知的常识技巧,比方 IdleHandler。

因为 Handler 的个性,它在 Android 里的利用十分宽泛,比方:AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。

这些我会解说一些,我没讲到的能够自行搜寻相干内容进行理解。

[](https://link.juejin.cn/?targe…)

3.1 Handler 引起的内存泄露起因以及最佳解决方案

Handler 容许咱们发送 延时音讯,如果在延时期间用户敞开了 Activity,那么该 Activity 会泄露。

这个泄露是因为 Message 会持有 Handler,而又因为 Java 的个性,外部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

解决该问题的最无效的办法是:将 Handler 定义成动态的外部类,在外部持有 Activity 的弱援用,并及时移除所有音讯

示例代码如下:

private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {HandlerActivity activity = ref.get();
        if (activity != null) {activity.handleMessage(msg);
        }
    }
}

并且再在 Activity.onDestroy() 前移除音讯,加一层保障:

@Override
protected void onDestroy() {safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();}

这样双重保障,就能完全避免内存泄露了。

留神:单纯的在 onDestroy 移除音讯并不保险,因为 onDestroy 并不一定执行。

[](https://link.juejin.cn/?targe…)

3.2 为什么咱们能在主线程间接应用 Handler,而不须要创立 Looper?

后面咱们提到了每个 Handler 的线程都有一个 Looper,主线程当然也不例外,然而咱们未曾筹备过主线程的 Looper 而能够间接应用,这是为何?

留神:通常咱们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者,所以吧,我感觉把 ActivityThread 认为就是主线程无可非议,另外主线程也能够说成 UI 线程。

在 ActivityThread.main() 办法中有如下代码:

//android.app.ActivityThread
public static void main(String[] args) {
  //...
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();
  }
  //...
  Looper.loop();

  throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper(); 代码如下:

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {prepare(false);
    synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();}
}

能够看到 在 ActivityThread 里 调用了 Looper.prepareMainLooper() 办法创立了 主线程的 Looper , 并且调用了 loop() 办法,所以咱们就能够间接应用 Handler 了。

留神:Looper.loop() 是个死循环,前面的代码失常状况不会执行。

[](https://link.juejin.cn/?targe…)

3.3 主线程的 Looper 不容许退出

如果你尝试退出 Looper,你会失去以下错误信息:

Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
  at android.os.MessageQueue.quit(MessageQueue.java:415)
  at android.os.Looper.quit(Looper.java:240)

why? 其实起因很简略,主线程不容许退出,退出就象征 APP 要挂。

[](https://link.juejin.cn/?targe…)

3.4 Handler 里藏着的 Callback 能干什么?

在 Handler 的构造方法中有几个 要求传入 Callback,那它是什么,又能做什么呢?

来看看 Handler.dispatchMessage(msg)  办法:

public void dispatchMessage(Message msg) {
  // 这里的 callback 是 Runnable
  if (msg.callback != null) {handleCallback(msg);
  } else {
    // 如果 callback 解决了该 msg 并且返回 true,就不会再回调 handleMessage
    if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
    }
    handleMessage(msg);
  }
}

能够看到 Handler.Callback 有 优先解决音讯的权力 ,当一条音讯被 Callback 解决 并拦挡(返回 true),那么 Handler 的 handleMessage(msg) 办法就不会被调用了;如果 Callback 解决了音讯,然而并没有拦挡,那么就意味着 一个音讯能够同时被 Callback 以及 Handler 解决

这个就很有意思了,这有什么作用呢?

咱们能够利用 Callback 这个拦挡机制来拦挡 Handler 的音讯!

场景:Hook ActivityThread.mH,在 ActivityThread 中有个成员变量 mH,它是个 Handler,又是个极其重要的类,简直所有的插件化框架都应用了这个办法。

[](https://link.juejin.cn/?targe…)

3.5 创立 Message 实例的最佳形式

因为 Handler 极为罕用,所以为了节俭开销,Android 给 Message 设计了回收机制,所以咱们在应用的时候尽量复用 Message,缩小内存耗费。

办法有二:

  1. 通过 Message 的静态方法 Message.obtain();   获取;
  2. 通过 Handler 的私有办法 handler.obtainMessage();

[](https://link.juejin.cn/?targe…)

3.6 子线程里弹 Toast 的正确姿态

当咱们尝试在子线程里间接去弹 Toast 的时候,会 crash:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

实质上是因为 Toast 的实现依赖于 Handler,按子线程应用 Handler 的要求批改即可(见【2.1】),同理的还有 Dialog。

正确示例代码如下:

new Thread(new Runnable() {
  @Override
  public void run() {Looper.prepare();
    Toast.makeText(HandlerActivity.this, "不会解体啦!", Toast.LENGTH_SHORT).show();
    Looper.loop();}
}).start();

[](https://link.juejin.cn/?targe…)

3.7 妙用 Looper 机制

咱们能够利用 Looper 的机制来帮忙咱们做一些事件:

  1. 将 Runnable post 到主线程执行;
  2. 利用 Looper 判断以后线程是否是主线程。

残缺示例代码如下:

public final class MainThread {private MainThread() { }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {if (isMainThread()) {runnable.run();
        }else{HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {return Looper.myLooper() == Looper.getMainLooper();}

}

可能省去不少样板代码。

[](https://link.juejin.cn/?targe…)

4. 知识点汇总

由前文可得出一些知识点,汇总一下,不便记忆。

  1. Handler 的背地有 Looper、MessageQueue 撑持,Looper 负责音讯散发,MessageQueue 负责音讯治理;
  2. 在创立 Handler 之前肯定须要先创立 Looper;
  3. Looper 有退出的性能,然而主线程的 Looper 不容许退出;
  4. 异步线程的 Looper 须要本人调用 Looper.myLooper().quit(); 退出;
  5. Runnable 被封装进了 Message,能够说是一个非凡的 Message;
  6. Handler.handleMessage() 所在的线程是 Looper.loop() 办法被调用的线程,也能够说成 Looper 所在的线程,并不是创立 Handler 的线程;
  7. 应用外部类的形式应用 Handler 可能会导致内存泄露,即使在 Activity.onDestroy 里移除延时音讯,必须要写成动态外部类;

文章转自 https://juejin.cn/post/684490…,如有侵权,请分割删除。

相干视频:

Android 金九银十面试重点问题解析——Handler 源码具体解析_哔哩哔哩_bilibili
【Android 进阶教程】——Framework 面试必问的 Handler 源码解析_哔哩哔哩_bilibili
【Android 进阶教程】——热修复原理解析_哔哩哔哩_bilibili
【Android 进阶教程】——如何解决 OOM 问题与 LeakCanary 原理解析_哔哩哔哩_bilibili
【Android 进阶教程】——OkHttp 原理_哔哩哔哩_bilibili
【Android 进阶教程】——终于有人能够将 retrofit 设计原理讲清楚了_哔哩哔哩_bilibili
【Android 进阶教程】——面试外围之 WorkManager 原理_哔哩哔哩_bilibili

正文完
 0