前言

Android 的消息机制原理是Android进阶必学知识点之一,在Android面试也是常问问题之一。在Android中,子线程是不能直接操作View,需要切换到主线程进行。那么这个切换动作就涉及到了Android的消息机制,也就是本文要讲的Handler、Looper、MessageQueue、Message它们之间的关系。

Handler

Handler在消息机制中扮演发送消息处理消息的角色,也是我们平常接触最多的类。

Handler如何处理消息?

下面代码展示Handler如何处理消息。新建Handler对象,并重写handleMessage,在方法内处理相关逻辑,一般处理和主线程相关的逻辑。Handler有很多的构造器,下面构造器常用在主线程。

  private Handler handler=new Handler(){@Overridepublic void handleMessage(Message msg) {if (msg.what==1){Toast.makeText(MainActivity.this,"handle message",Toast.LENGTH_LONG).show();}}};复制代码

Handler是如何发送消息的呢?

通过下面的代码可以了解到,Handler对象支持发送MessageRunable。Runable最终被包装成Messagecallback实例变量(Handler对象处理消息会优先处理callback的逻辑),和Message一样的方式放到消息队列中。而每个方法都有相关的变形,支持延迟发送,或者未来的某段时间里发送等等。

    //在消息池获取消息体,能达到消息重用,如果消息池没有消息,则新建消息Message msg = handler.obtainMessage();msg.what = 1;//发送消息handler.sendMessage(msg);//发送空消息,参数会自动被包装msg.what=1handler.sendEmptyMessage(1);//未来的时间里发送消息handler.sendEmptyMessageAtTime(1, 1000);//延迟发送消息handler.sendEmptyMessageDelayed(1, 1000);msg = handler.obtainMessage();msg.what = 2;//将消息发送消息队列前面handler.sendMessageAtFrontOfQueue(msg);//发送任务,run方法内容将handler被处理。handler.post(new Runnable() {@Overridepublic void run() {Log.i("Handler", "Runnable");}});
复制代码

如果平常使用,我们只需要主线程定义Hanlder处理消息的内容,在子线程发送消息即可达到切换流程。

Looper

Looper负责循环的从消息队列中取消息,发送给Handler处理。因为消息队列只用来存储消息,所以需要Looper不断的从消息队列中取消息给Handler。默认情况,所有线程并不拥有Looper。如果在子线程直接执行Looper.loop方法,就会发生异常。那主线程为什么不会报错?在App的启动流程中,创建ActivityThread时,会调用Looper.prepare来创建LooperMessageQueue,和Looper.loop开启循环。也就是系统为我们在主线程创建LooperMessageQueue。所以,在子线创建Handler前,需要先调用Looper.prepare方法,创建LooperMessageQueueIntentService就是这样实现的。点击看IntentService的知识点。

MessageQueue

MessageQueue内部是以链表的形式组织的,主要作用是存储Message。在创建Looper的时候,会自动创建MessageQueue

三者关系形成了Android的消息机制

Handler发送消息时会将消息插入到MessageQueue,而Looper不断的从MessageQueue中取消息分发给Handler处理。

源码解析

Handler的构建

我们先看一下Handler的构造器代码。

    public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}//分析一mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}//分析二mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}复制代码

Handler有很多重载的构造器,我们常用在使用默认构造器,最终会调用上面的构造器。

分析一

通过Looper.myLooper(),获Looper的实例。而在myLooper的实现中,是通过ThreadLocalget方法来获取的。如果ThreadLocal不存在Looper,则放回nullThreaLocal这里可以简单理解为保存当前线程私有独立的实例,其他线程不可访问。如果ThreadLocal不存在Looper实例则,返回null。这也就是前面说的,在子线程创建Handler前,需要先调用Looper.prepare方法。否则会抛出RuntimeException

 public static @Nullable Looper myLooper() {return sThreadLocal.get();}
复制代码

分析二

mQueueLooper中的消息队列,mCallBack定义了一个接口,用于回调消息处理。

  public interface Callback {/*** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/public boolean handleMessage(Message msg);}
复制代码

Handler发送消息

Handler所有发送消息方法的变体最终都会以下面方法放去到消息队列中。

    public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}复制代码

这里最重要的就是enqueueMessage方法中,将当前Handler对象设置给Messagetarget变量。然后调用队列queueenqueueMessage方法。

    boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {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) {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;//如果队列为空或者插入message未来处理时间小于当前对头when//则将当前消息设为队列头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 {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;}复制代码

而在MessageQueueenqueueMessage方法中,会先检查target是否nullmessage是否应在使用,当前线程是否退出,死亡状态。如果是,则抛出异常。如果当前队列是空或者阻塞,直接当前Message对象设为队列的头并唤醒线程。如果不是,则根据Message对象的when插入到队列合适的位置。因此可以看得出,Handler发送消息时是将消息放到队列中。

Looper和MessageQueue的创建

前面讲过,子线程使用Handler,需要调用Looper的静态prepare方法。

 public static void prepare() {prepare(true);}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就会报错。如果没有,new Looper并保存到ThreadLocal中。new Looper非常简单,只是新建一个MessageQueue,和持有当前线程。

 private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}
复制代码

Looper是如何实现循环的

在调用了Looper.prepare创建LooperMessageQueue对象后,要调用Loop.loop的开始循环分发消息队列中消息。

 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;// 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);boolean slowDeliveryDetected = false;//分析一for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// 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);}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);}}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);}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();}}复制代码

分析一: 通过无限制的for循环,读取队列的消息。而MessageQueuenext方法内部通过链表的形式,根据when属性的顺序返回message

分析二: 调用Message对象的targetdipatchMessage方法。这里的target就是发送消息的Handler对象。而在Handler对象的dipatchMessage方法中,优先执行Message对象的callback方法,即优先执行我们发送消息时以Runable发送的任务,如果有的话。不然检测Callback对象的handleMessage方法,最后才是我们重写Hanlder对象的handleMessage方法。因为Handler不仅有默认构造函数,还有可以传入Callback,Looper等的构造函数。

    public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
复制代码

Message的复用

通过handler.obtainMessage而不是new方式获得消息实例。因为obtainMessage方法会先检测消息池是否有可以复用的消息,没有再去new一个消息实例。下面是类Message的obtain方法。

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();}
复制代码

sPool的类型是Message,内部通过成员变量next,维护一个消息池。虽然叫消息池,内部却通过next不断的指向下一个Message,以链表维护的这个消息池,默认大小为50。在链表sPool不为空的情况,取表头Message元素,并将相关属性进行初始化。

那么Message对象是在什么时候被放进消息池中的呢?

Looperloop方法中,最后调用MessagerecycleUnchecked方法

void recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.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++;}}}
复制代码

在同步代码块,可以看到,将sPool指向当前要被回收的Message对象,而Messagenext指向之前的表头。

总结

  1. 在子线程使用Handler,需要先调用Looper.prepare方法,再调用Looper.loop方法。
  2. 消息队列以链表的形式维护着,消息的存放和获取顺序根据when时间依次排列。
  3. 通过Handler,在子线程耗时操作,主线程更新UI。应用场景:IntentServiceHandlerTreadAsyncTack

知识点分享

HandlerThread必知必会

IntentService必知必会

如果觉得文章有用,给文章点个赞,铁子

Android:Handler的消息机制相关推荐

  1. [Android]Handler的消息机制

    最经面试中,技术面试中有一个是Handler的消息机制,细细想想,我经常用到的Handler无非是在主线程(或者说Activity)新建一个Handler对象,另外一个Thread是异步加载数据,同时 ...

  2. 图解 Android Handler 线程消息机制

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

  3. 浅析Android中的消息机制

    在分析Android消息机制之前,我们先来看一段代码: [java] view plaincopy public class MainActivity extends Activity impleme ...

  4. Android中的消息机制

    Android 中的消息机制其实就是指的是 Handler 消息机制以及附带的 Looper 和 MessageQueue 的工作流程. 1.Android 为什么提供Handler? 解决子线程不能 ...

  5. 重温Android中的消息机制

    引入: 提到Android中的消息机制,大家应该都不陌生,我们在开发中不可避免的要和它打交道.从我们开发的角度来看,Handler是Android消息机制的上层接口.我们在平时的开发中只需要和Hand ...

  6. android return 如何跳出两个循环_关于不得不学的Android知识之消息机制

    概述 相信不管是出入Android,还是已开发多年的老司机们,肯定都对Android的Handler不会陌生,而它就是今天要介绍的Android消息机制中的一部分.在Android系统中,有两大特色利 ...

  7. Android Handler 异步消息处理机制的妙用 创建强大的图片载入类

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 近期创建了一个群.方便大家交流,群号: ...

  8. Android 线程与消息 机制 15问15答

    1.handler,looper,messagequeue三者之间的关系以及各自的角色? 答:MessageQueue就是存储消息的载体,Looper就是无限循环查找这个载体里是否还有消息.Handl ...

  9. Android中的消息机制:Handler消息传递机制

    参考<疯狂android讲义>第2版3.5 P214 一.背景 出于性能优化考虑,Android的UI操作并不是线程安全的,这意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为 ...

最新文章

  1. fushioncharts破解
  2. Saiku_学习_01_saiku安装与运行
  3. 如何让引擎蜘蛛天天光临你的网站
  4. 中国最大的python社区-python 最大堆
  5. GPT v.s. 中国象棋:写过文章解过题,要不再来下盘棋?
  6. CDN预热与刷新在促销活动中的应用
  7. 使用cocoaPods一键集成第三方登录(新浪微博,qq,微信)
  8. C++ int型与char型辨析
  9. spring mvc 解决csrf跨站请求攻击
  10. 《遥感原理与应用》总结—遥感概论
  11. QT遍历 Json 根节点
  12. 红米手机android无法开机画面,红米手机一直停在开机画面怎么办?
  13. .Net Core WebApi 模型验证的处理
  14. PPT另存为PDF图片去白边
  15. java x的平方怎么打出来_java 中的输入输出
  16. R语言包翻译——翻译
  17. Java中的代码点和代码单元(转)
  18. va和tn玩游戏哪个好 va和tn哪个伤眼睛
  19. 明亮如星研旅(4)—— Online and offline handwritten Chinese character recognition
  20. APP登录注册 步骤三:客户端

热门文章

  1. js-ES6学习笔记-Iterator和for-of循环
  2. 测试人员的GitHub
  3. httpWebRequest 错误
  4. swift 动态设置UILabel的高度
  5. oracle重新启动步骤
  6. jquery获取元素的值,获取当前对象的父对象等等
  7. rhel6ACL权限
  8. Notification和Notification Manager的使用
  9. C#----DataGridView控件60招(一) [转]
  10. 清理服务器,不可不知的两个指令