Android进阶知识树——Android Handler消息机制
1、概述
在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答案这就是今天的主题Android的消息机制;
- Android处理消息的方式——handler、Looper 和 MessageQueue
- Handler :负责将任务添加到队列,执行结束后在主线程执行UI操作
- Looper :为绑定的线程开启循环消息队列,并获取消息
- MessageQueue :任务队列,保存发送的消息
- 在进行源码分析之前先提出两个常见的疑问
为什么子线程直接创建Handler会抛出异常?
Handler究竟执行在哪个线程中?
2、源码分析
对于Handler的使用方法此处不做介绍,在使用是只要注意避免内存泄漏需创建静态Handler,此处从执行过程和原理的角度简单分析Handler的源码,首先大家都知道在主线程中可以直接使用Handler,而在子线程中不可以,有没有想过为什么呢?现在就先看看主线程究竟有什么不同吧,从头看安卓的入口方法ActivityThread.main()
public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");...Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}
- 上述代码看出在程序启动后,执行三个步骤:
- 调用Looper.prepareMainLooper();创建消息循环Looper
- 使用 thread.getHandler(); 获取UI线程的Handler
- 使用 Looper.loop()开启消息循环
Handler与消息队列关联,而消息队列被封装在Looper中,每个Looper关联一个线程,所以Handler的消息大致为handler作为一个消息处理器,将消息传递到消息队列中,线程中的Looper逐个取出消息并执行,此处的线程即为UI线程,
- Handler最基本的用法是创建Handler 发送Message到队列,现在看看创建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;}
- 调用Looper.myLooper():获取Looper对象,如果为空则抛出异常,然后调用lopper.mQuene开启消息队列
- 查看myLooper()方法
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();public static @Nullable Looper myLooper() {return sThreadLocal.get();}
- 在myLooper中看出,Looper从sThreadLocal中获取,sThreadLocal是静态创建实例,它的作用就是以线程为单位保存数据,那此处的Looper对象什么时候设置进去的呢?另外从这也可以看出来,只要sThreadLocal没有Looper的实例就会抛出异常,此时sThreadLocal就是在当前的执行线程中,换句话说只要Handler所在的线程中没有Looper的实例就会异常;
- 接下来看看UI线程开始时创建的Looper对象的方法prepareMainLooper() :
public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}
- prepareMainLooper() 调用了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));}private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed); // 创建消息队列MessageQueuemThread = Thread.currentThread(); // 保存当前线程}
- 看到了 sThreadLocal.set(new Looper(quitAllowed)),此处创建Looper对象然后保存进sThreadLocal中,那此时在当前线程中已经初始化了Looper对象,到此是不是明白了上面的sThreadLocal.get()获取的Looper是哪里的了?
- 在创建Looper对象的同时,系统会实例化一个MessageQueue对象,作为消息对列保存发送的消息事件;
到这里我们介绍了Handler 和 Looper的创建,以及各自对线程的关联,下面查看如何启动消息循环获取任务并执行,在入口方法的最后调用了Loop.loop()方法开启消息队列循环,下面查看Looper.loop()方法:
public static void loop() {final Looper me = myLooper();final MessageQueue queue = me.mQueue;
for (;;) { //使用死循环获取消息Message msg = queue.next(); // might blockif (msg == null) {return;}
...msg.target.dispatchMessage(msg);msg.recycleUnchecked();
}
- 调用myLooper()获取Looper的对象,从Looper对象中获取消息队列MessageQueue,然后在循环内依次调用MessageQueue.next()依次取出其中的Message,注意这里是个死循环;
- 调用Message的msg.target.dispatchMessage(msg)传递消息;
- 查看Message的源码便知此处调用的target实际是Handler的对象,说明绕了一圈最后还是Handler的dispatchMessage(msg)处理逻辑
Handler target;
Runnable callback;
Message next;
- Handler的dispatchMessage(msg)方法
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
private static void handleCallback(Message message) {message.callback.run();}
总结一下:looper从MessageQuene中依次取出Message,然后调用Msg绑定的Handler的dispatchMessage(msg)处理,dispatchMessage()中依次判断执行:
- 先判断Message的Runable对象callback是否为null,若不为空使用handleCallback(),在handleCallback直接调用Runnable.run()方法执行程序;
- 若callback为空判断mCallBack,若mCallBack不为空则调用mCallBack.handleMessage()处理事件,这里的mCallBack是Callback接口对象,在创建Handler时传入的回调方法,不经常使用但某些条件下需要拦截或代理Handler时可以使用;
- 如果上面两者都为null,则执行handleMessage(),handlerMessage()在Handler中默认为空实现,一般在创建Handler()都需要重写的handlerMessage(),然后处理相应的事件;
到此Android中Handler的消息处理,从创建Handler、Looper和MessageQueue到消息的传递和事件的处理都介绍完了,下面我们看看使用时如何发送消息到MessageQueue以及上述的callback、mCallBack什么情况下为空?
- 创建的Handler后发送消息方法:sendMessage(Message msg)、post(Runnable r)
- sendMessage():发送Message消息;
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;return enqueueMessage(queue, msg, uptimeMillis);}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this; // 此处设置target为Handler对象if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis); // 添加Message}
上面的执行过程代码从上到下一目了然,在enqueueMessage()中设置了Message.target即为调用方法的Handler,所以在调用dispatchMessage()时,会调用Handler的handleMessage()处理,然后根据当前时间和发送时设置的延迟时间计算时间发送的准确时间,然后将消息Message保存到MessageQueue中;
- post(Runnable r)
public final boolean post(Runnable r){return sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) {Message m = Message.obtain(); // 创建Message封装Runnablem.callback = r; // 设置Message的callback为rreturn m;}
- 调用getPostMessage()设置Message中的callback,所以callback就是发送的runnable,其余方法和sendMessage()一致;
3、源码细节
- Message的保存形式
public final class Message implements Parcelable {
Message next;
}
由Message的源码中看出,在Message的内部中存在Message类型的next属性,也就是说对象中指向对象,很显然这是链表结构,所以Handler中的Message是以单向链表形式保存数据的;
- MessageQueue中任务的保存和获取
由上面的分析知道当Handler发送Message后,程序会执行enqueueMessage()向MessageQueue中添加任务
boolean enqueueMessage(Message msg, long when) {synchronized (this) {msg.recycle();return false;}msg.markInUse();msg.when = when; // 保存要发送的时间,主要针对延迟发送Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) { // 与链表头部对比时间msg.next = p; // 放在链表头部mMessages = msg;needWake = mBlocked;} else {needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) { // 循环遍历队列中的任务,将当前任务插入到对应的时间位置prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}if (needWake) {nativeWake(mPtr);}}return true;}
从上面添加过程可以看出MessageQueue中存在属性mMessages,mMessages始终指向链表的第一条数据,在插入Message对象msg时使用mMessages的发送时间和msg时间对比,如果msg立即发送或小于mMessage则将msg防止链表头部,否则循环遍历MessageQueue中的队列将Msg插入合适的位置;
对于Message的获取,因为Message是以链表结构保存的,所以在MessageQueue中只需要保存链表头部的一个Message对象即可,然后利用链表的遍历依次取出next的对象,只是在获取到Message对象时会比较当前时间和Message的执行时间,然后通过阻塞实现延迟发送;
- Message 缓存获取
在使用Handler发送消息时先需要创建Message对象,一般创建都使用Message.obtain()方法,这里有没有想过为什么不直接new出对象呢?答案就在Message的内部,为了提高程序的性能节省创建的时间,Message内部使用缓存机制实现对象的复用;
Message next // message使用链表结构保存,next用于指向下一个对象
private static Message sPool; // 当前缓存复用的队象
private static int sPoolSize = 0; // 缓存message对象大小
public static final Object sPoolSync = new Object(); // 锁
private static final int MAX_POOL_SIZE = 50;public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}
从上面代码看出Message使用链表结构保存数据,内部使用sPool缓存当前空闲的、准备复用的Message对象,当调用obtain()时如果sPool 为空则创建Message对象,否则系统会将sPool返回,同时将sPool.next对象复制给sPool作为下一个空闲的对象,那使用完的Message是如何缓存的呢?
void recycleUnchecked() {flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = -1;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}}
在Message使用后会调用recycler()回收对象,recycler中会调用recycleUnchecked(),recycleUnchecked中会清除使用的Message中的信息,判断如果缓存对象数量未达到最大值,则将当前对象复制给sPool缓存起来,同时将原来的缓存放到链表的next中,实现链式缓存;
Handler消息机制的原理和过程介绍完毕了,带着上面的了解现在来解决上面的两个疑问:
- 为什么不能在子线程直接创建创建handler吗?
通过上面的源码分析,我们知道抛出的异常在上述代码中也出现了
mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}
当Looper.myLooper()取出为空时抛出异常,而myLooper()中时从sThreadLocal.get()中获取,即此时sThreadLocal中没有设置Looper对象,所以也就没有后面的队列等操作,从上面知道创建并设置Looper对象是在prepare()方法中,所以要在子线程中使用Handler要先调用Looper.prepare()创建Looper对象,后调用Looper.loop()开启消息循环。
- Handler究竟执行在哪个线程中?
其实针对这个问题,网上很多答案是:执行在Handler()所在的线程,即在哪个线程创建Handler实例,handler后续的任务就执行在哪个线程,其实这个答案可对可错,从我们平时使用来看无论是在子线程初始化Loop后创建Handler(),还是在主线程直接创建,最终Handler所执行的线程就是创建线程,从这个角度说答案是正确的;
但这只是其中一种情况,下面看看Handler的另一种创建方式:从上面我们知道对于任务的处理在looper.loop()中,以及后面对任务的处理都在同一线程中,整个过程知道HandlerMessage()都没有执行线程切换处理,也就是说调用loop()方法的Looper实例在哪个线程创建就会在哪个线程执行,同时消息队列mQueue也在相同线程,之所以会有人说执行的线程会是Handler创建的线程,其实因为在主线程或子线程使用无参构造函数创建Handler()时,Looper实例和Handler实例在同一线程,所以任务和Handler执行在此线程,到从可以得出结论:Handler真正执行的线程是消息队列Looper()实例的所在线程,关于此结论的验证,代码很简单请读者自行验证;
Android进阶知识树——Android Handler消息机制相关推荐
- Android进阶知识树——Android消息队列
1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...
- Android Framework学习(八)之Handler消息机制(Native层)解析
在深入解析Android中Handler消息机制一文中,我们学习了Handler消息机制的java层代码,这次我们来学习Handler消息机制的native层代码. 在Java层的消息处理机制中,Me ...
- 【Android】线程间通信——Handler消息机制
文章目录 引言 Java层 永动机跑起来 示例 Looper Handler MessageQueue 永动机停下 Native层 nativeInit() nativePollOnce() nati ...
- android进阶知识总结,Android进阶学习有哪些知识点
Android进阶学习有哪些知识点 发布时间:2020-07-29 12:50:39 来源:亿速云 阅读:114 作者:Leah 本篇文章给大家分享的是有关Android进阶学习有哪些知识点,小编觉得 ...
- Android Handler消息机制源码分析
一,前言 众多周知, Android 只允许在主线程中更新UI,因此主线程也称为UI线程(ActivityThread). 如此设计原因有二: (1) 由于UI操作的方法都不是线程安全的,如果多个线程 ...
- Android Handler消息机制不完全解析
1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...
- Android Handler消息机制源码解析
好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...
- 【安卓学习笔记】Android Handler 消息机制探究
一.概述 1.android消息机制的含义: Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper ...
- Android进阶知识:绘制流程(上)
1.前言 之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android中View事件分发与响应的流程.关于View除了事件传递流程还有一个很重要的就是View的绘制流程.一个 ...
最新文章
- Linux IO多路复用之epoll网络编程(含源码)
- OS- -I/O之盘和时钟
- 吴恩达《机器学习》第一章:监督学习和无监督学习
- unity每次运行总是game窗口最大化怎么解决?
- 【人脸识别】基于matlab GUI SVM和PCA人脸识别【含Matlab源码 369期】
- Xmind2021绿色版,思维导图最佳软件
- 语音信号处理及特征提取
- 局域网络连接的计算机不全,大神教你win7局域网内可以看到对方计算机但无法连接的办法?...
- Unity 射线检测
- Js 嵌套if选择结构
- echarts 的柱状图 填充色为透明
- B2B是什么意思? 外贸工厂企业适合做独立站吗?
- Unsupported Modules Detected: Compilation is not supported for following modules: app, library. Unfo
- macbook卡在进度条开不了机_Mac 开机停在进度条解决方法
- python gdal 基于栅格shp文件裁剪geotif图
- [计算机组成原理]-32/64位、寻址能力
- 批处理批量替换文本内容,用bat代码全篇替换txt文本文件中指定字符信息
- 为什么白素贞能生文曲星转世许仕林? 和她的另一个身份有关
- 【C/C++ 经典小程序(一)】
- 水笔:20年好用软件或者工具备忘