内容简介:相信于此,绝大多数同学都会回答消息机制是android 为了线程间通信而引入的工具。可以轻松的将一个任务切换到handler所在线程执行。android开发规范有规定,不允许于子线程更新ui,这样会触发异常;我们平时使用handler主要都是将子线程切换到主线程中去执行;因此从本质上来来说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。A:因为Android的UI线程是非A:我们都知道在java中,线程存在以下几种基本状态:
相信于此,绝大多数同学都会回答消息机制是android 为了线程间通信而引入的工具。可以轻松的将一个任务切换到handler所在线程执行。android开发规范有规定,不允许于子线程更新ui,这样会触发异常;我们平时使用handler主要都是将子线程切换到主线程中去执行;因此从本质上来来说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。
Q?为何不能在主线程外更新ui呢?
A:因为Android的UI线程是非 线程安全
的,应用更新UI,是调用 invalidate()
方法来实现界面的重绘,而 invalidate()
方法是 非线程安全 的,也就是说当我们 在非UI线程来更新UI时,可能会有其他的线程或UI线程也在更新UI,这就会导致界面更新的不同步 。因此我们不能在非UI主线程中做更新UI的操作。也就是说我们在使用Android中的线程时,要保证: 更新ui都在UI主线程执行.
Q:那为何不将需要更新ui的操作放在UI线程执行呢?
A:我们都知道在 java 中,线程存在以下几种基本状态: 创建
, 就绪
, 运行
, 阻塞
, 死亡
。我们的应用启动后,所有的交互都是在 UI
线程完成的;如果在 UI
执行延时操作,如常见的 网络请求
, UI
线程就会进入 阻塞
状态;此时用户就无法响应任何操作了;如果此过程超过5秒,就会让程序处于 ANR(application not response)
,这时用户就可能想要和你的应用说声 gg
了。
Q:Android提供了哪几种线程间通信方式?
A: AsyncTask?
, Handler
。为什么 AsynTask
打了个 ?
呢,我们可以简单看下 AsynTask
源码,他内部也是接住handler来进行线程间通信的。
Q:MessageQueue存在Targer对象的消息,那和我们正常流程中,由handler传递的消息有什么出入呢?
A:其实平时我们使用的 Message
,都是通过 Handler
发送的,有一些系统消息,他们会直接通过调用 MessageQueue
发送一个屏障消息,这类消息没有 Target
,然后配合 Handler
发送异步消息来使用;当 MessageQueue
读取到屏障消息后,他们会直接在链表中找到最近的 异步消息
,直接执行。
feature-要素
-
Message(消息单元)定义一个可以发送到
Handler
的消息;它定义了消息Id
,两个额为的int字段和一个额外的object字段
(消息处理对象),它们可以不被初始化;虽然它的构造方法是public,但是还是建议我们通过obtain系列函数进行定义。 -
MessageQueue(消息队列)存放所有发送的消息队列,单链表结构,供Looper从中读取数据;延时消息是怎么存取的,这个很有趣;
-
Looper(消息读取者)永动机;其中有个死循环函数
Loop()
,不断读取MessageQueue
中的消息,交给目标处理;问题来了,既然是个死循环,那不是始终会阻塞Looper
所在线程吗。这又是如何解决的。 -
Handler(消息分发以及处理者)通过
sendMessage
系列函数,会将Message
传入MessageQueue
中;Looper.loop()
读取到消息传递给Handler
处理。
desc
-
handler
创建前,Looper.loop()
执行前;需要保证当前线程Looper
有创建,而这个保证即Looper.prepare()
;主线程由于在1ActivityThread1创建时,已经做过,所以无需执行; -
Looper.loop()
中有一个死循环,所以线程资源不会释放;在线程运行结束时调用MessageQueue
中的quit
函数,我们才能释放资源; -
Java
中,所有非静态成员变量会持有当前对象的引用(不然你又是怎么引用外部类的各种成员变量和函数等);那样我们在Activity
中通过new Handler()
, 创建的对象会持有当前页面的引用;而我们发送的每个消息不能保证是立即执行,以及迅速执行结束的,handler.sendEmptyMessageDelayed
;消息是会持有handler
做为他的target
,那在这个message
在通过msg.target.dispatchMessage(msg);
会一直被持有;这样会导致messageQueue->message->handler->activity|fragment
;在页面被销毁,声明周期执行到desatory
时,activity
不会得到释放,从而 内存泄漏 ;handler
得到消息处理时,如果当前页面已经被销毁,执行Ui
更新,又会导致难以预料的问题。 - 针对
3
所提的我们可以按以下两种处理: 1:页面destory
销毁时,调用handler.removeCallbacksAndMessages(null);
2:通过软引用创建静态Handler对象;
流程解析
android handler流程分析晚上有很多资料;我们这儿简单介绍下:
-
Looper
,MessageQueue
就绪;调用Looper.prepare()
,其间会向Looper
静态线程变量sThreadLocal
插入一个当前线程的Looper
;在调用Looper
构造函数时,我们会初始化MessageQueue
,并将mThread
设置为当前Thread.currentThread();
-
Looper.prepare()
代码块如下:public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //sThreadLocal->ThreadLocal对象,里面封装了一个map逻辑,key是线程hash值;static 类变量 if (sThreadLocal.get() != null) {//不允许多次prepare throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));//设置当前线程的Looper } 复制代码
Looper
构造函数,以及MessageQueue
构造函数如下:private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); //初始化消息池 mThread = Thread.currentThread(); } //是否允许退出 MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); //线程id } 复制代码
-
接下来我们看下数据插入
sendMessage(Message msg) sendEmptyMessage(int what) sendEmptyMessageDelayed(int what, long delayMillis) sendEmptyMessageAtTime(int what, long uptimeMillis) sendMessageDelayed(Message msg, long delayMillis) sendMessageAtTime(Message msg, long uptimeMillis) sendMessageAtFrontOfQueue(Message msg)
这以上七个方法,可以通过handler向handler所在线程发送消息;其中
1,2,3,4,5
都是调用方法6
进行执行的;其中方法6
中的uptimeMillis
取的是系统非休眠时间SystemClock.uptimeMillis()
;我们接下来看下
sendMessageAtTime(Message msg, long uptimeMillis)
:public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; //将线程变量Looper中的queue取出使用 if (queue == null) { //queue判空,其实创建handler时,也是必须要Looper初始化结束;queue创建后的 RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis);//简单判断,交给enqueueMessage函数执行; } 复制代码
而
7.sendMessageAtFrontOfQueue(Message msg)
调用的函数如下:public final boolean sendMessageAtFrontOfQueue(Message msg) { 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, 0);//简单判断,交给enqueueMessage函数执行;设置执行时间0 } 复制代码
据此,我们发现所有消息的发送都是通过
MessageQueue
的enqueueMessage(Message msg, long when)
方法; -
我们来看下
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; //将msg的target设置为当前handler;这儿可以看出msg和handler是 n:1的关系 if (mAsynchronous) {//handler是否是异步?默认false;将值赋予msg msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis);//可以看到,此处最终调用了eqeue的enqueueMessage方法 } 复制代码
那我们看下
enqueueMessage
函数(handler的消息基本都是通过该函数放入线程MessageQueue
中):boolean enqueueMessage(Message msg, long when) { if (msg.target == null) {//handler不能为空 throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) {//消息是否在被使用处理 throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) {//同步锁 if (mQuitting) {//mQuitting;子线程消息池我们在线程即将结束时,调用这个mQuitting退出;之后发送的消息都是不会被收入消息池的;所以如果遇到消息没有发送成功,我们可能需要判断是不是looper已经退出了; IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse();//标志该消息被处理了 msg.when = when;//设置执行的时间 Message p = mMessages;//链表头消息 boolean needWake;//是否需要唤醒线程 if (p == null || when == 0 || when < p.when) {//表头无消息||即时消息||当前消息执行时间小于表头时间 // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; //表头消息替换为放入的msg; needWake = mBlocked;//巧妙处,如果锁住,就唤醒; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. //入队列中,默认不唤醒,仅当头部msg是屏障消息,当前msg是异步消息 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插入对应节点 msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } 复制代码
-
于此我们所有的消息入栈已经看完了;那消息是怎么获取的呢;
mBlocked
是不是真的代表线程阻塞呢?根据前面的图形介绍,我们知道,Looper
中有一个loop
函数,他是一个死循环,负责向MessageQueue
读取数据,接下来我们来看下这个函数;/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. 在线程结束时,要调用looper.quit()退出 */ public static void loop() { final Looper me = myLooper(); //静态方法,获取当前线程的Looper;两个工作线程进行通信,需要先在补获线程调用prepare(),并在其run()结束时,调用quit() if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; //获取mQueue // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); //清空远程调用端的uid和pid,用当前本地进程的uid和pid替代 final long ident = Binder.clearCallingIdentity(); // Allow overriding a threshold with a system prop. e.g. // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start' final int thresholdOverride = SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow", 0); boolean slowDeliveryDetected = false; for (;;) { Message msg = queue.next(); // might block 获取消息可能阻塞线程;我们先分析它,后面的代码下面分析 …… } } 复制代码
MessageQueue.next():
Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { //在调用quit结束loop后,又一次尝试调用prepare后,此时ptr会为0,不支持 return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration 默认pendingIdleHandler为0 int nextPollTimeoutMillis = 0; //需要阻塞时间,-1表示无限阻塞,直到消息入栈调用nativeWake唤醒 for (;;) { if (nextPollTimeoutMillis != 0) {//time不为0存在阻塞 Binder.flushPendingCommands(); //native方法,看注释是配合线程长时间阻塞使用,用于释放任何的挂起对象 } nativePollOnce(ptr, nextPollTimeoutMillis);//线程阻塞,time阻塞时长 //同步锁 synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //当前消息的目标为屏障消息(消息无target),找寻下一个异步消息执行 if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { //when比当前时间大;需要阻塞 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 { //返回这个将要执行的消息;将mBlocked阻塞置false;将当前message置为执行消息后一个 // Got a message. mBlocked = false;无需阻塞 if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; //返回消息,退出当前循环 } } else { // No more messages.没有消息;无限阻塞,直到新消息入列唤醒它 nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { //如果执行了退出。调用dispose(); dispose(); return null;//返回null作为next()执行结果,注意,此时Looper.loop()也会执行结束 } //idleHandlers->idleHandler是指一个线程当前没有需要立即执行的消息,(延时执行or无消息)时,会执行的一个callback;根据上面的分析,只有在next()执行,且没有需要返回消息时执行 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {//延时执行or无消息 pendingIdleHandlerCount = mIdleHandlers.size(); //只有调用addIdleHandler加入idle时,count才会增加 } //默认0;无idle时,mBlocked阻塞置为true,执行循环 for (;;) {}内部内容 if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle();//根据queueIdle返回值,决定是否需要执行后移除该idle } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } //执行完PendingIdleHandler后,我门将count置为0,不再执行他 // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; //执行完idle后,可能有消息准备就绪,我们重新计算阻塞时间 // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } } 复制代码
总结一下,
MessageQueue
不断获取待执行消息,并可能阻塞线程(没有message or 待执行message
的when
比当前时间晚);而MessageQueue
提供了一个Idle机制
,用于在当前线程没有由于没有待执行Message
或者延时Message
时执行,而addIdleHandler
就是用于添加Idle
的我们再回头看下
Looper.loop()
:for (;;) { Message msg = queue.next(); // might block 刚刚分析上文,当前消息是延时消息或者消息队列为空时,会进行阻塞 if (msg == null) { //没有消息退出循环,loop结束工作 ; next控制 ;即Mq执行quit退出后,不在执行任何消息 // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger //日志输出;支持自定义 final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } //Trace用于追踪一个Message执行的;可以结合TraceView等 工具 查看,具体请百度吧;而我们的Ui线程所有的ui绘制,事件流执行,等都属于一个消息,可以通过Trace进行跟踪; final long traceTag = me.mTraceTag; long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs; if (thresholdOverride > 0) { slowDispatchThresholdMs = thresholdOverride; slowDeliveryThresholdMs = thresholdOverride; } final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0); final boolean logSlowDispatch = (slowDispatchThresholdMs > 0); final boolean needStartTime = logSlowDelivery || logSlowDispatch; final boolean needEndTime = logSlowDispatch; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg));//跟踪起始 } final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; try { msg.target.dispatchMessage(msg); //分发消息 dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; //记录执行结束时间 } finally { if (traceTag != 0) { Trace.traceEnd(traceTag);//跟踪结束 } } //Slow?没研究,但也是打印相关日志信息的。。 if (logSlowDelivery) { if (slowDeliveryDetected) { if ((dispatchStart - msg.when) <= 10) { Slog.w(TAG, "Drained"); slowDeliveryDetected = false; } } else { if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg)) { // Once we write a slow delivery log, suppress until the queue drains. slowDeliveryDetected = true; } } } if (logSlowDispatch) { showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); //打印结束时间 } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked();//msg结合obtion()实现对象复用 } 复制代码
结合代码来看
Looper.loop()
所做的事情不多,主要都是用于记录分析Message
信息的: -
开启一个死循环,将消息读取交给
MessageQUeue
的next()
函数,该函数可能导致线程阻塞; -
提供一个
Printer
接口,记录打印每个Message
的执行开始和结束信息; -
提供的
Trace
函数用来记录每个消息的处理信息; -
通过
msg.target.dispatchMessage(msg)
执行消息 -
通过
msg.recycleUnchecked()
回收消息,使得Message
消息池得到复用;Message
是一个链表结构,提供了Message.obtion()
方法,用于不断的取链表头对象;在表头空时新建;消息执行完调用的recycleUnchecked
会将Message
相关消息情况,插入链表头至此我们对
Android
的消息机制发送和读取有了一个完整的了解:下面附上一个简单的流程图(md的流程图绘制,真心累啊。)
对于一个消息创建流程,加入消息队列, MessageQueue
简单通过 Mq
代表了:
接下来是消息读取的流程图:
上面流程图中有涉及到一个新的消息概念 屏障消息(无target的消息)
:
我们向消息队列 MessageQueue
发送一个屏障消息,然后再发送一个异步消息;在我们读取到这个屏障消息的时候,我们会找到链表后的第一个异步消息;这样就能快速执行该异步消息了;
系统有一个 postSyncBarrier()
用来发送屏障消息,但是被隐藏了;我们可以反射调用或者直接向 MessageQueue
表头反射插入一个 Message
,但是不建议这样做;
发送异步消息可以通过:创建 Handler
时,传入异步参数:
public Handler(boolean async); public Handler(Callback callback, boolean async); public Handler(Looper looper, Callback callback, boolean async); 复制代码
这样就能发送屏障消息和异步消息了;
在系统源码 ViewRootImpl.scheduleTraversals
中,为了更快响应 UI刷新事件时
:
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //设置同步障碍,确保mTraversalRunnable优先被执行 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //内部通过Handler发送了一个异步消息 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } 复制代码
mTraversalRunnable
调用了 performTraversals
执行 measure、layout、draw
为了让 mTraversalRunnable
尽快被执行,在发消息之前调用 MessageQueue.postSyncBarrier
设置了同步屏障
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
新零售:低价高效的数据赋能之路
刘润 / 中信出版集团 / 2018-9 / 65.00元
小米新零售,如何做到20倍坪效? 天猫小店,如何利用大数据助力线下零售? 盒马鲜生,为什么坚持必须用App才能买单? 名创优品,实体小店在电商冲击下,如何拥抱春天? 新零售的未来在何方?什么样的思维模式才可应对? 新零售,不是商界大佬的专用名词,它就在我们生活触手可及的各个角落——小到便利店的酸奶,大到京东商城的冰箱,都蕴含着消费者、货物、经营场所三者共同作用的经济逻......一起来看看 《新零售:低价高效的数据赋能之路》 这本书的介绍吧!