北京网站建设在哪里天,wordpress自动提交百度,大型旅游网站源码 织梦,wordpress如何做导航网站0. 前言做 Android 开发肯定离不开跟 Handler 打交道#xff0c;它通常被我们用来做主线程与子线程之间的通信工具#xff0c;而 Handler 作为 Android 中消息机制的重要一员也确实给我们的开发带来了极大的便利。Handler应用之广泛#xff0c;可以说只要有异步线程与主线程…0. 前言做 Android 开发肯定离不开跟 Handler 打交道它通常被我们用来做主线程与子线程之间的通信工具而 Handler 作为 Android 中消息机制的重要一员也确实给我们的开发带来了极大的便利。Handler应用之广泛可以说只要有异步线程与主线程通信的地方就一定会有 Handler。所以搞懂 Handler 对理解Android非常有必要。那么Handler 的通信机制的背后的原理是什么本文带你揭晓。注意本文所展示的系统源码基于 Android-27 并有所删减。1. 重识 Handler我们可以使用 Handler 发送并处理与一个线程关联的 Message 和 Runnable 。(注意Runnable 会被封装进一个 Message所以它本质上还是一个 Message )每个 Handler 都会跟一个线程绑定并与该线程的 MessageQueue 关联在一起从而实现消息的管理以及线程间通信。1.1 Handler 的基本用法1android.os.Handler handler new Handler(){2 Override3 public void handleMessage(final Message msg) {4 //这里接受并处理消息5 }6};7//发送消息8handler.sendMessage(message);9handler.post(runnable);实例化一个 Handler 重写 handleMessage 方法 然后在需要的时候调用它的 send 以及 post 系列方法就可以了非常简单易用并且支持延时消息。(更多方法可查询 API 文档)但是奇怪我们并没有看到任何 MessageQueue 的身影也没看到它与线程绑定的逻辑这是怎么回事2. Handler 原理解析相信大家早就听说过了 Looper 以及 MessageQueue 了我就不多绕弯子了。不过在开始分析原理之前先明确我们的问题Handler 是如何与线程关联的Handler 发出去的消息是谁管理的消息又是怎么回到 handleMessage() 方法的线程的切换是怎么回事2.1 Handler 与 Looper 的关联实际上我们在实例化 Handler 的时候 Handler 会去检查当前线程的 Looper 是否存在如果不存在则会报异常也就是说在创建 Handler 之前一定需要先创建 Looper 。代码如下 1public Handler(Callback callback, boolean async) { 2 //检查当前的线程是否有 Looper 3 mLooper Looper.myLooper(); 4 if (mLooper null) { 5 throw new RuntimeException( 6 Cant create handler inside thread that has not called Looper.prepare()); 7 } 8 //Looper 持有一个 MessageQueue 9 mQueue mLooper.mQueue;10}这个异常相信很多同学遇到过而我们平时直接使用感受不到这个异常是因为主线程已经为我们创建好了 Looper先记住后面会讲。(见【3.2】)一个完整的 Handler 使用例子其实是这样的 1class LooperThread extends Thread { 2 public Handler mHandler; 3 public void run() { 4 Looper.prepare(); 5 mHandler new Handler() { 6 public void handleMessage(Message msg) { 7 // process incoming messages here 8 } 9 };10 Looper.loop();11 }12}Looper.prepare() :1//Looper2private static void prepare(boolean quitAllowed) {3 if (sThreadLocal.get() ! null) {4 throw new RuntimeException(Only one Looper may be created per thread);5 }6 sThreadLocal.set(new Looper(quitAllowed));7}Looper 提供了 Looper.prepare() 方法来创建 Looper 并且会借助 ThreadLocal 来实现与当前线程的绑定功能。Looper.loop() 则会开始不断尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler(见【2.3】)。也就是说 Handler 跟线程的关联是靠 Looper 来实现的。2.2 Message 的存储与管理Handler 提供了一些列的方法让我们来发送消息如 send()系列 post()系列 。不过不管我们调用什么方法最终都会走到 Message.enqueueMessage(Message,long) 方法。以 sendEmptyMessage(int) 方法为例1//Handler2sendEmptyMessage(int)3 - sendEmptyMessageDelayed(int,int)4 - sendMessageAtTime(Message,long)5 - enqueueMessage(MessageQueue,Message,long)6 - queue.enqueueMessage(Message, long);到了这里消息的管理者 MessageQueue 也就露出了水面。MessageQueue 顾明思议就是个队列负责消息的入队出队。2.3 Message 的分发与处理了解清楚 Message 的发送与存储管理后就该揭开分发与处理的面纱了。前面说到了 Looper.loop() 负责对消息的分发本章节进行分析。先来看看所涉及到的方法 1//Looper 2public static void loop() { 3 final Looper me myLooper(); 4 if (me null) { 5 throw new RuntimeException(No Looper; Looper.prepare() wasnt called on this thread.); 6 } 7 final MessageQueue queue me.mQueue; 8 //... 9 for (;;) {10 // 不断从 MessageQueue 获取 消息11 Message msg queue.next(); // might block12 //退出 Looper 13 if (msg null) {14 // No message indicates that the message queue is quitting.15 return;16 }17 //...18 try {19 msg.target.dispatchMessage(msg);20 end (slowDispatchThresholdMs 0) ? 0 : SystemClock.uptimeMillis();21 } finally {22 //...23 }24 //...25 //回收 message, 见【3.5】26 msg.recycleUnchecked();27 }28}loop() 里调用了 MessageQueue.next() : 1//MessageQueue 2Message next() { 3 //... 4 for (;;) { 5 //... 6 nativePollOnce(ptr, nextPollTimeoutMillis); 7 8 synchronized (this) { 9 // Try to retrieve the next message. Return if found.10 final long now SystemClock.uptimeMillis();11 Message prevMsg null;12 Message msg mMessages;13 //...14 if (msg ! null) {15 if (now msg.when) {16 // Next message is not ready. Set a timeout to wake up when it is ready.17 nextPollTimeoutMillis (int) Math.min(msg.when - now, Integer.MAX_VALUE);18 } else {19 // Got a message.20 mBlocked false;21 if (prevMsg ! null) {22 prevMsg.next msg.next;23 } else {24 mMessages msg.next;25 }26 msg.next null;27 return msg;28 }29 } else {30 // No more messages.31 nextPollTimeoutMillis -1;32 }3334 // Process the quit message now that all pending messages have been handled.35 if (mQuitting) {36 dispose();37 return null;38 }39 }4041 // Run the idle handlers. 关于 IdleHandler 自行了解42 //...43 }44}还调用了 msg.target.dispatchMessage(msg) msg.target 就是发送该消息的 Handler这样就回调到了 Handler 那边去了: 1//Handler 2public void dispatchMessage(Message msg) { 3 //msg.callback 是 Runnable 如果是 post方法则会走这个 if 4 if (msg.callback ! null) { 5 handleCallback(msg); 6 } else { 7 //callback 见【3.4】 8 if (mCallback ! null) { 9 if (mCallback.handleMessage(msg)) {10 return;11 }12 }13 //回调到 Handler 的 handleMessage 方法14 handleMessage(msg);15 }16}注意dispatchMessage() 方法针对 Runnable 的方法做了特殊处理如果是 则会直接执行 Runnable.run() 。分析Looper.loop() 是个死循环会不断调用 MessageQueue.next() 获取 Message 并调用 msg.target.dispatchMessage(msg) 回到了 Handler 来分发消息以此来完成消息的回调。注意loop()方法并不会卡死主线程见【6】。那么线程的切换又是怎么回事呢很多人搞不懂这个原理但是其实非常简单我们将所涉及的方法调用栈画出来如下1Thread.foo(){2 Looper.loop()3 - MessageQueue.next()4 - Message.target.dispatchMessage()5 - Handler.handleMessage()6}显而易见Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定。平时我们用的时候从异步线程发送消息到 Handler这个 Handler 的 handleMessage() 方法是在主线程调用的所以消息就从异步线程切换到了主线程。2.3 图解原理文字版的原理解析到这里就结束了如果你看到这里还是没有懂没关系我特意给你们准备了些图配合着前面几个章节再多看几遍一定可以吃透。handler-looper-mq.jpghandler_java.jpg图片来源见【6】2.4 小结Handler 的背后有着 Looper 以及 MessageQueue 的协助三者通力合作分工明确。尝试小结一下它们的职责如下Looper 负责关联线程以及消息的分发会与创建它的线程绑定并负责在该线程下从 MessageQueue 获取 Message分发给 Handler MessageQueue 是个队列负责消息的存储与管理负责管理由 Handler 发送过来的 Message Handler : 负责发送并处理消息面向开发者提供 API并隐藏背后实现的细节。对【2】章节提出的问题用一句话总结Handler 发送的消息由 MessageQueue 存储管理并由 Loopler 负责回调消息到 handleMessage()。线程的转换由 Looper 完成handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。3. Handler 的延伸Handler 虽然简单易用但是要用好它还是需要注意一点另外 Handler相关 还有些鲜为人知的知识技巧比如 IdleHandler。由于 Handler 的特性它在 Android 里的应用非常广泛比如 AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。这些我会讲解一些我没讲到的可以自行搜索相关内容进行了解。3.1 Handler 引起的内存泄露原因以及最佳解决方案Handler 允许我们发送延时消息如果在延时期间用户关闭了 Activity那么该 Activity 会泄露。这个泄露是因为 Message 会持有 Handler而又因为 Java 的特性内部类会持有外部类使得 Activity 会被 Handler 持有这样最终就导致 Activity 泄露。解决该问题的最有效的方法是将 Handler 定义成静态的内部类在内部持有 Activity 的弱引用并及时移除所有消息。示例代码如下 1private static class SafeHandler extends Handler { 2 3 private WeakReference ref; 4 5 public SafeHandler(HandlerActivity activity) { 6 this.ref new WeakReference(activity); 7 } 8 9 Override10 public void handleMessage(final Message msg) {11 HandlerActivity activity ref.get();12 if (activity ! null) {13 activity.handleMessage(msg);14 }15 }16}并且再在 Activity.onDestroy() 前移除消息加一层保障1Override2protected void onDestroy() {3 safeHandler.removeCallbacksAndMessages(null);4 super.onDestroy();5}这样双重保障就能完全避免内存泄露了。注意单纯的在 onDestroy 移除消息并不保险因为 onDestroy 并不一定执行。3.2 为什么我们能在主线程直接使用 Handler而不需要创建 Looper 前面我们提到了每个Handler 的线程都有一个 Looper 主线程当然也不例外但是我们不曾准备过主线程的 Looper 而可以直接使用这是为何注意通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程而是主线程操作的管理者所以吧我觉得把 ActivityThread 认为就是主线程无可厚非另外主线程也可以说成 UI 线程。在 ActivityThread.main() 方法中有如下代码 1//android.app.ActivityThread 2public static void main(String[] args) { 3 //... 4 Looper.prepareMainLooper(); 5 6 ActivityThread thread new ActivityThread(); 7 thread.attach(false); 8 9 if (sMainThreadHandler null) {10 sMainThreadHandler thread.getHandler();11 }12 //...13 Looper.loop();1415 throw new RuntimeException(Main thread loop unexpectedly exited);16}Looper.prepareMainLooper(); 代码如下 1/** 2 * Initialize the current thread as a looper, marking it as an 3 * applications main looper. The main looper for your application 4 * is created by the Android environment, so you should never need 5 * to call this function yourself. See also: {link #prepare()} 6 */ 7public static void prepareMainLooper() { 8 prepare(false); 9 synchronized (Looper.class) {10 if (sMainLooper ! null) {11 throw new IllegalStateException(The main Looper has already been prepared.);12 }13 sMainLooper myLooper();14 }15}可以看到在 ActivityThread 里 调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法所以我们就可以直接使用 Handler 了。注意Looper.loop() 是个死循环后面的代码正常情况不会执行。3.3 主线程的 Looper 不允许退出如果你尝试退出 Looper 你会得到以下错误信息1Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.2 at android.os.MessageQueue.quit(MessageQueue.java:415)3 at android.os.Looper.quit(Looper.java:240)why? 其实原因很简单主线程不允许退出退出就意味 APP 要挂。3.4 Handler 里藏着的 Callback 能干什么在 Handler 的构造方法中有几个 要求传入 Callback 那它是什么又能做什么呢来看看 Handler.dispatchMessage(msg) 方法 1public void dispatchMessage(Message msg) { 2 //这里的 callback 是 Runnable 3 if (msg.callback ! null) { 4 handleCallback(msg); 5 } else { 6 //如果 callback 处理了该 msg 并且返回 true 就不会再回调 handleMessage 7 if (mCallback ! null) { 8 if (mCallback.handleMessage(msg)) { 9 return;10 }11 }12 handleMessage(msg);13 }14}可以看到 Handler.Callback 有优先处理消息的权利 当一条消息被 Callback 处理并拦截(返回 true)那么 Handler 的 handleMessage(msg) 方法就不会被调用了如果 Callback 处理了消息但是并没有拦截那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。这个就很有意思了这有什么作用呢我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息场景Hook ActivityThread.mH 在 ActivityThread 中有个成员变量 mH 它是个 Handler又是个极其重要的类几乎所有的插件化框架都使用了这个方法。3.5 创建 Message 实例的最佳方式由于 Handler 极为常用所以为了节省开销Android 给 Message 设计了回收机制所以我们在使用的时候尽量复用 Message 减少内存消耗。方法有二通过 Message 的静态方法 Message.obtain(); 获取通过 Handler 的公有方法 handler.obtainMessage(); 。3.6 子线程里弹 Toast 的正确姿势当我们尝试在子线程里直接去弹 Toast 的时候会 crash 1java.lang.RuntimeException: Cant create handler inside thread that has not called Looper.prepare() 2本质上是因为 Toast 的实现依赖于 Handler按子线程使用 Handler 的要求修改即可(见【2.1】)同理的还有 Dialog。正确示例代码如下1new Thread(new Runnable() {2 Override3 public void run() {4 Looper.prepare();5 Toast.makeText(HandlerActivity.this, 不会崩溃啦