文章目录

  • 前言
  • 一、Android消息机制的构成
  • 二、为什么只允许主线程对UI进行更新操作
  • 三、消息机制具体分析
    • ThreadLocal原理分析
    • MessageQueue
    • Looper
    • Handler
    • 主线程的消息循环
  • 总结

前言

整理Android消息机制,帮助自己梳理Android消息机制的内容

一、Android消息机制的构成

Android消息机制其实就是Handler()的运行机制,我们开发过程中常常用Handler()和其他任务进行交互,如开启一个子线程完成从一个数据库中拿出数据,而后通知主线程进行UI更新,这时候便需要用到Handler()将子线程的消息交给主线程进行处理;Handler()其实便是Android消息机制与上层的接口,我们要了解Android消息机制,便是了解Handler()的底层实现

Handler()的底层机制

  • MessageQueue消息队列,存储一组消息,然后以队列的形式对外提供插入和删除的操作,说白了就是存储待处理的消息,但是MessageQueue的底层实现并不是队列,而是单链表的形式
  • Looper,扮演操作员的角色,MessageQueue只是一个存储待处理消息的数据结构,Looper以无线循环的形式去查找是否有新消息要处理,有就处理,没有就等待
  • ThreadLocal,ThreadLocal并不是线程,而是一个可以在多个线程间互不干扰地提供数据的类,同时ThreadLocal可以轻松地获取到每个线程的Looper;同时要注意的是,默认子线程是没有Looper的,如果要在子线程中使用Handler,就必须要为子线程创建Looper,主线程(ActivityThread)->UI线程在被初始化时会初始化Looper,也就是默认可以在主线程使用Handler的原因

因而Android消息机制其实就是Handler的运行机制以及Handler所附带MessageQueue和Looper的工作过程

二、为什么只允许主线程对UI进行更新操作

Android UI控件并不是线程安全,在多线程中并发访问容易导致UI控件处于不可预期的状态,因而采用单线程模型(主线程)对UI进行更新操作

三、消息机制具体分析

ThreadLocal原理分析

ThreadLocal是一个线程内部的数据存储类,通过它可以向指定的线程存储数据;数据存储以后,只有在指定的线程才可以获取到数据,而同一个ThreadLocal对象,是可以在不同的线程生成数据副本的,也就是说,你将同一个ThreadLocal对象传入不同的线程,通过get()方法返回的结果是不相同的

而不同的线程,存有不同的Looper对象,对于Handler来说,需要获取到当前线程的Looper,通过一个ThreadLocal类便可轻松获取到当前线程的Looper对象(不同的线程Looper对象有所不同)

为了搞清楚为什么同一个ThreadLocal对象在不同的线程中存有副本,我们来看看在ThreadLocal对象的get()和set()方法源码源码
注:在不同的线程中,调用同一个ThreadLocal对象的set()方法,设置不同的值;而后在不同的线程中调用ThreadLocal的get()方法,返回值是不同的

  • 先看set()方法
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}/*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param  t the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}

来看这个set流程,一个ThreadLocal调用set方法经历的流程大致如下:

  • 获取到当前所处的线程
  • 通过当前线程,拿到当前线程内部的threadLocals存储,threadLocals是一个ThreadLocalMap静态类,这里是内部定义了一个继承自WeekReference的类,整体就理解为一个特殊的Map类就可以了,其存储的键值对key是线程,value是某个线程对应的值
  • 如果map不为空,调用map的set()方法,将线程-值键值对放入ThreadLocalMap类;为空,创建map,将值放入

再来看get()方法

 public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}

看完了set()方法,get()方法就更简单啦

  • 获取当前线程
  • 获取到当前线程存储的ThreadLocalMap(其实是个静态类,哪里获取都一样)
  • 将当前线程作为key传入,得到value
  • return 值;
  • 如果map为空,那么进行初始化,并将进行初始化的值进行返回,具体返回什么值要看ThreadLocal泛型实现了什么

既然每个Thread作为key可以拿出相对应的唯一值,那么试想,每个线程是不是可以扔入唯一的Looper类呢?

MessageQueue

MessageQueue主要对应两个操作,插入和删除:

  • enqueueMessage 往队列中插入一条消息
  • next 从队列中拿出一条消息,并将其从队列中进行删除
 boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}synchronized (this) {if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}if (mQuitting) {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;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.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;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}@UnsupportedAppUsageMessage 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) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}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 && 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) {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;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();return null;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}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 handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// 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;}}

来看看enqueueMessage方法

  • 检验合法性输入
  • 检验当前MessageQueue是否处于退出状态
  • 同步操作,保证线程安全:一个是给MessageQueue上锁,一个是markInUse()进行标记
  • 如果不为空,执行单链表的头插法操作
  • 如果为空,进行创建插入,而后唤醒队列

next方法

  • 使用了一个for造成无限循环,当队列中存在消息的时候就return消息跳出循环,同时将这条消息移出(prev.next=msg.next),msg.target是发送这条消息的Handler对象;如果不存在消息的时候,则会调用方法进行阻塞

Looper

Looper扮演消息循环的角色,不停地从MessageQueue中查看消息,如果有新消息就立刻执行,否则就会阻塞在那里
以下展示其中重要部分源码

 private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}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));}@UnsupportedAppUsagestatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();/*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/@SuppressWarnings("AndroidFrameworkBinderIdentity")public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;// 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();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);me.mSlowDeliveryDetected = false;for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}}private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return false;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " "+ msg.callback + ": " + msg.what);}// Make sure the observer won't change while processing a transaction.final Observer observer = sObserver;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;Object token = null;if (observer != null) {token = observer.messageDispatchStarting();}long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);try {msg.target.dispatchMessage(msg);//分发了if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (me.mSlowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");me.mSlowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.me.mSlowDeliveryDetected = 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();return true;}

下面我们对其进行分析

  • 构造函数中创建了消息队列,而后获取到了当前的线程
  • prepare()方法中创建了一个新的Looper,并且扔进了sThreadLocal这个变量,然后我们在源码中查证到这个变量就是ThreadLocal,那么也就是说,我们之前验证的,以当前线程作为key,Looper对象作为value值的做法完全行得通,这在前文已经进行了说明
  • loop()方法本质也是一个死循环,传入Looper本身,一个long的数值,一个int的数值;调用loopOnce方法,当返回值为false时,退出
  • loopOnce()方法,无非就是取出消息队列的一条消息,然后判断是否为空,当空的时候返回false,外部的loop方法结束,不为空的时候,让msg.target(Handler对象)进行分发;但是事实上next()方法其实是个阻塞方法,当消息队列为空的时候,它并不会返回null,只会阻塞,什么时候消息队列返回的msg为空呢,那便是Looper调用了quit()方法,强制msg为null,进行退出

Handler

下面来看看Handler的工作原理
先来看看sendMessage()

/*** Pushes a message onto the end of the message queue after all pending messages* before the current time. It will be received in {@link #handleMessage},* in the thread attached to this handler.*  * @return Returns true if the message was successfully placed in to the *         message queue.  Returns false on failure, usually because the*         looper processing the message queue is exiting.*/public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(@NonNull 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);}private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

无非就是几步曲

  • 查看是否需要延时操作
  • 创建消息,设置消息的target为自己
  • 插入到消息队列中

而后MessageQueue的next()方法开始工作,然后交一条消息给Looper,而后Looper开始处理,然后调用dispatchMessage()的方法

/*** Handle system messages here.*/public void dispatchMessage(@NonNull 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();}/*** Subclasses must implement this to receive messages.*/public void handleMessage(@NonNull Message msg) {}

handleCallback()其实调用msg的callback对象,其实就是我们使用Handler的过程中添加的那个Runnable对象,如果这一步完成了就return,如果没有,那么我们调用handleMessage,这一步其实就是调用我们自己实现的回调,说白了就是我们new Handler(callback)而后重写的那个handleMessage的方法

主线程的消息循环

主线程拥有自己的MessageQueue,通过Looper.prepareMainLooper()创建Looper和MessageQueue,而后looper()方法开启主线程消息循环,内部自己的Handler便为ActivityThread.H,主要负责和ApplicationThread和AMS进行通信

总结

这是目前的Android消息机制的初步整理,笔者针对源码一步步提出了自己的理解,希望帮助到大家

Android:Android消息机制整理相关推荐

  1. 聊一聊Android的消息机制

    2019独角兽企业重金招聘Python工程师标准>>> 聊一聊Android的消息机制 侯 亮 1概述 在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前 ...

  2. Android异步消息机制

    2019独角兽企业重金招聘Python工程师标准>>> 目录介绍 1.Handler的常见的使用方式 2.如何在子线程中定义Handler 3.主线程如何自动调用Looper.pre ...

  3. Android的消息机制(2)

    上一节中,是主线程自己发了一个消息到自己的Message Queue中,并把消息从队列中提取出来.那么如何由别的线程发送消息给主线程的Message Queue中呢? 直接看代码~~ 1 2 3 4 ...

  4. Android的消息机制

    Android的消息机制(一) android 有一种叫消息队列的说法,这里我们可以这样理解:假如一个隧道就是一个消息队列,那么里面的每一部汽车就是一个一个消息,这里我们先忽略掉超车等种种因素,只那么 ...

  5. Android的消息机制简单总结

    参考文章: http://gityuan.com/2015/12/26/handler-message-framework/#next 参考资料: Android Framework的源码: Mess ...

  6. 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

    第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...

  7. Android进阶知识树——Android Handler消息机制

    1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...

  8. Android 开发艺术探索——第十章 Android的消息机制

    Android 开发艺术探索--第十章 Android的消息机制读书笔记 Handler并不是专门用于更新UI的,只是常被用来更新UI 概述 Android的消息机制主要值得就是Handler的运行机 ...

  9. 【学习】Android的消息机制

    Android的消息机制主要指Handler的运行机制,而Handler的运行需要MessageQueue和Looper支撑 前置知识 MessageQueue:消息队列,内部以单链表的形式存储消息列 ...

  10. 【安卓学习笔记】Android Handler 消息机制探究

    一.概述 1.android消息机制的含义: Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper ...

最新文章

  1. 宜信开源|数据库审核软件Themis的规则解析与部署攻略
  2. 关于软件外包的一些看法(转)
  3. API for org.eclipse.paho.client.mqttv3
  4. 【入门经典】创建站点地图
  5. boost::fusion::fused_function_object用法的测试程序
  6. gson json和类转换_带有GSON和抽象类的JSON
  7. python安装lxml,在windows环境下
  8. Tomcat和Weblogic部署纯html文件
  9. c语言 前置声明,我这个C代码 为什么加了函数前置声明反而会出错,不加倒可以...
  10. CocosCreator2.3.1切换场景出现Failed to load scene ‘xxx‘ because ‘xxx‘ is already being loaded问题的解决方案
  11. android开发中悬浮窗被禁用,无权限开启悬浮窗的解决方案
  12. 名悦集团:深圳人买什么车好?
  13. “我爱淘”冲刺阶段Scrum站立会议8
  14. 湘潭大学 Hurry Up 三分,求凹函数的最小值问题
  15. 【Gitlab】配置、运行Gitlab容器实例及简单使用测试
  16. C4D 入门笔记(一)
  17. 一位算法工程师对自己工作的总结,非常具有借鉴意义!
  18. 各种优化器SGD,AdaGrad,Adam,LBFGS都做了什么?
  19. SpringBoot yml文件数据读取
  20. 中国电信将取消长途漫游费 大规模布局物联网

热门文章

  1. 都是假的!这位小姐姐 P 的图,认真看你就输了!
  2. 同样将AI装进“办公全家桶”,Microsoft 与 Google有何不同?
  3. 图像质量评价(IQA)读库代码详细介绍
  4. 如何把“底噪”压低、压低、再压低?
  5. 用数值方法计算单侧受热的无限大平板的瞬态温度场
  6. 大班音乐机器人反思_大班音乐教案及教学反思《拉拉勾》
  7. Spring Boot + Spring Cloud 实现权限管理系统 后端篇(一):Kitty 系统介绍
  8. MXNet学习之nd.random_normal 与 nd.random.normal 的比较
  9. 戴尔R620安装server2008 R2系统操作说明
  10. python websocket爬虫_python爬虫----爬取阿里数据银行websocket接口