前言

Handler可以说是老生常谈了,因为它在整个Android的系统中是非常重要的一部分,学习它的原理是很有必要的。本文带来的分享是对Handler原理的全面解析,相信对日常的开发和面试会有很大的帮助,下面我们一起看看它的原理。

正文

在讲解Handler原理之前,我们先通过一张图片看看它的整个流程:

当然,我们接下来所讲的Handler原理,不仅仅是Handler本身,还包括MessageQueue、Looper等类。

Handler的作用

在日常开发中,我们很多时候需要切换线程去做某件事情,比如在工作线程做好计算后,需要切换到主线程去更新ui,那么这个时候便可以使用Handler来进行线程的切换,又比如我们想做一个延迟任务,也可以使用Handler去开启一个延迟任务等等。

Handler

创建:

Handler的构造函数最后会调用到下面的构造函数:

public Handler(@Nullable Callback callback, boolean async) {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;
}

构造函数初始化Looper成员,会对Looper进行检查,如果之前还没初始化,则会抛异常,所以创建Handler之前,需要确保当前线程的Looper已经创建了,Looper创建后会与当前线程绑定存放起来,至于它得怎么创建,我们后面会讲到。

但是我们在主线程中,则无需去创建Looper,因为在ActivityThread的main方法里边,系统已经帮我们创建好了:

//只保留核心代码
public static void main(String[] args) {Looper.prepareMainLooper();Looper.loop();}

Looper.prepareMainLooper()里边会去创建一个Looper,并且这个Looper是在主线程中。

发送消息:

发送消息之前,需要创建Message,Message可以说是整个流程的载体,它承载着一些信息。它的创建方式如下:

new Message()
Message.obtain()
handler.obtainMessage()
//直接new跟obtain有什么区别呢?obtain的话,会优先到池子里拿,池子没有到话再创建一个,所以我们一般优先考虑obtain的方式去获取Message。//发送消息
handler.sendMessage(msg);

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {// 省略校验return enqueueMessage(queue, msg, uptimeMillis);
}

发送消息的最后都会调用到sendMessageAtTime方法,它里边调用到是enqueueMessage方法:

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);
}

主要做了三件事:

  • 将msg的target设置为this,在消息分发时会用到,后面内容分析。
  • 如果当前Handler支持异步,那么会将msg设置为异步消息,用于异步屏障使用,后面分析
  • 调用MessageQueue的enqueueMessage方法。

下面我们一起看看MessageQueue

处理消息:

public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}

从上面的代码我们可以看到分发的顺序是:

  • 如果Message有设置callback,则使用messaeg的callback进行回调,其实我们平时使用handler.post()/postDelay(),都会使用将Runnable赋值给Message的callback。
  • 如果Handler有设置callback,则会先回调Handler的callback。
  • 最后是Handler的handleMessage,这也是我们常见的一个方法,初始化Handler的时候去复写这个方法,并在里边进行消息的处理。

MessageQueue

内部时一个链表结构,用于存放Message和提供Message

插入消息:

上面讲到的enqueueMessage方法,就是用来存放Message的:

//已精简了部分代码
boolean enqueueMessage(Message msg, long when) {synchronized (this) {//省略部分代码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) {//唤醒阻塞。(next方法里)nativeWake(mPtr);}}return true;
}
  • 根据消息的时间寻找合适地方进行插入
  • 插入后尝试唤醒操作,因为在下边的取消息时可能会进入阻塞,调用唤醒的前提是这个消息不是同步屏障。

既然我们MessageQueue提供了存消息的方法,同时也提供了取消息的方法,具体如下:

取出消息:

Message next() {//...int nextPollTimeoutMillis = 0;for (;;) {//...//尝试阻塞nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {//同步屏障,拿到异步消息do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {//还没到分发的时间,计算需要阻塞的时长nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {//可以分发,移除mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;msg.markInUse();return msg;}} else {// 没有消息,需要进入阻塞nextPollTimeoutMillis = -1;}}
}

next方法会进入一个循环,然后分下列几步走:

  • 如果没有Message的话,会进入阻塞。如果拿到Message,会先判断是否为同步屏障(msg.target == null),如果是的话,则会去取后面的异步Message。同步屏障后面会进行分析。
  • 拿到Message后,先判断当前消息是否已经到达了分发的时间,则将它从链表中移除并且返回,否则的话会计算分发当前消息需要等多久,然后进行阻塞,阻塞时长为计算出来的时长。
  • 如果没有Message,则会进入阻塞,阻塞没有时长,会一直阻塞。等到有新的消息插入的话,才会通知打断阻塞。

上面便是Message的两个核心方法存和取,那么又是哪个角色会从MessageQueue中进行去消息呢?下面介绍重要的类:Looper。它将负责取消息并进行分发。

Looper

创建:

Looper的创建需要调用Looper.prepare()方法,最后会调用prepare(quitAllowed)方法:

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));
}

创建后,便放到了ThreadLocal里边,ThreadLocal可以保证将Looper与线程绑定起来。在创建之前,还会进行校验是否本线程是否已经创建过了。在这里我们顺便看一看Looper的构造函数:

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

它的构造函数是私有的,也就是说外面无法直接new出来,并且构造函数里边会初始化MessageQueue。

启动:

上面我们讲过handler发送消息,然后插入到了MessageQueue,那么什么时候才会去处理放到队列里边的消息呢,这时Looper的一个工作,创建后,调用Looper.loop()方法,就会进入一个无限循环,不断去尝试获取Message:

//精简了部分代码
public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}me.mInLoop = true;final MessageQueue queue = me.mQueue;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {return;}final Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}try {msg.target.dispatchMessage(msg);} catch (Exception exception) {throw exception;}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}msg.recycleUnchecked();}
}

loop方法主要做三件事:

  • 开启一起死循环
  • 不断地从MessageQueue里边拿Message
  • 拿到Message后,通过msg.target进行分发,其实target就是Handler,所以这里就能够将消息分发到Handler

上面我们提过,主线程中系统在一开始就启动Looper了,所以我们无需手动启动。

源码里的logging可以通过Looper的setMessageLogging进行设置。在这里我们同时也可以监控每个任务的耗时,通过对比logging的println方法参数值“>>>”和“<<<”进行配对。

回到Handler,看看它是如何进行消息的分发:

上面便是Message发送、取出、分发的整个过程,下面我们继续讲讲两个知识点:同步屏障和IdleHandler。

同步屏障

除了普通的Message以外,还有另外两种特殊的Message:屏障消息与异步消息,屏障消息则是为异步消息服务的。下面对他们进行详细的讲解。

同步屏障的作用:

屏障消息与普通消息的区别是target为null,它的作用就像是开了一绿色通道,如果开启同步屏障,那么发送异步消息后,都会得到优先处理,之前发送到普通消息则不会得到处理,得等到同步屏障的移除。

开启同步屏障:

调用MessageQueue的postSyncBarrier方法,则可以发送一个同步屏障消息,并且会返回对于的token,当我们关闭同步屏障的时候,则使用这个token去关闭:

@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {return postSyncBarrier(SystemClock.uptimeMillis());
}private int postSyncBarrier(long when) {synchronized (this) {final int token = mNextBarrierToken++;final Message msg = Message.obtain();msg.markInUse();msg.when = when;msg.arg1 = token;Message prev = null;Message p = mMessages;if (when != 0) {while (p != null && p.when <= when) {prev = p;p = p.next;}}if (prev != null) {msg.next = p;prev.next = msg;} else {msg.next = p;mMessages = msg;}return token;}
}

可以看到,开启同步屏障是往消息链表中插入一个target为null的Message,并且返回一个token,但是我们平时在开发中,没法直接去调用,如果要使用的话,则可以使用反射进行调用。

移除同步屏障:

@UnsupportedAppUsage
@TestApi
public void removeSyncBarrier(int token) {synchronized (this) {Message prev = null;Message p = mMessages;while (p != null && (p.target != null || p.arg1 != token)) {prev = p;p = p.next;}if (p == null) {throw new IllegalStateException("The specified message queue synchronization "+ " barrier token has not been posted or has already been removed.");}final boolean needWake;if (prev != null) {prev.next = p.next;needWake = false;} else {mMessages = p.next;needWake = mMessages == null || mMessages.target != null;}p.recycleUnchecked();if (needWake && !mQuitting) {nativeWake(mPtr);}}
}

通过token,找到屏障消息,然后将其从队列中移除掉,并且看看是否在此屏障消息后面还有消息,有的话需要唤醒操作。

那么屏障消息在哪里起作用呢?其实我们在前面的MessageQueue的next方法已经提过了:

if (msg != null && msg.target == null) {//同步屏障,拿到异步消息do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());
}

如果遇到同步屏障消息,则会去获取异步消息,然后进行分发处理。

哪里有用到呢?

在ViewRootImpl中,scheduleTraversals方法。

void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//里边发送的是一条异步消息mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);}
}

也就是每次去调度刷新ui的时候,便会开启同步屏障,这也可以理解,因为是ui的刷新,所以优先级是比较高的。

IdleHandler

IdleHandler可以用在哪里呢?其实看它名字大概可以猜出它是用来干嘛的,主要是用来在空闲(当前没有Message分发)的时候进行调用。也就是我们可以使用IdleHandler来执行一些不重要或者优先级比较低的任务,让当前消息队列空闲时再去执行,避免影响其他重要的任务执行。

public static interface IdleHandler {boolean queueIdle();
}

使用idleHandler可以如下:

looper.getQueue().addIdleHandler(new IdleHandler() {public boolean queueIdle() {//做一些事return false;}
});

其中queueIdle返回的值表示是否继续保留着,等到下次空闲的时候再次调用。返回false表示用完即移除。

idleHandler在什么时候调用呢?

Message next() {int pendingIdleHandlerCount = -1; // -1 only during first iterationfor (;;) {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);}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);}}}pendingIdleHandlerCount = 0;}
}

也就是在MessageQueue的next方法中,如果当前没有需要分发的Message,那么就会从mIdleHandlers拿出IdleHandler进行调用,调用后根据返回的结果来判断是否从列表里边移除掉。

结语

以上便是Handler的原理的讲解,内容较多,算是对Handler原理一个比较全面的解析,如果掌握了以上的分析,那么可以说基本对Handler原理了解了至少95%以上。

Handler原理学习相关推荐

  1. Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

    在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...

  2. 从使用到原理学习Java线程池

    来源:SilenceDut http://www.codeceo.com/article/java-threadpool-learn.html 线程池的技术背景 在面向对象编程中,创建和销毁对象是很费 ...

  3. Android 系统(189)---Android Handler:这是一份 全面、详细的Handler机制 学习攻略

    Android Handler:这是一份 全面.详细的Handler机制 学习攻略 前言 在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将献上一份 全面.详细的Handl ...

  4. 【Android 异步操作】Handler ( 主线程中的 Handler 与 Looper | Handler 原理简介 )

    文章目录 一.主线程中的 Handler 与 Looper 二.Handler 原理简介 一.主线程中的 Handler 与 Looper Android 系统中 , 点击图标启动一个应用进程 , 就 ...

  5. Docker镜像原理学习理解

    Docker镜像原理学习理解 一.Docker镜像的组成 1.Docker镜像图层 2.union file system 3.镜像层-bootfs 4.镜像层-rootfs 5.镜像层-依赖环境 6 ...

  6. Android Handler原理

    前言 Handler消息处理机制在Android开发中起着举足轻重的作用,我们有必要好好理解下其原理,先前我写的一篇文章,感觉疏漏了好多东西,因此打算写这篇文章,下面我们先从一个简单的例子出发 一.日 ...

  7. android 实例源码解释,Android Handler 原理分析及实例代码

    Android Handler 原理分析 Handler一个让无数android开发者头疼的东西,希望我今天这边文章能为您彻底根治这个问题 今天就为大家详细剖析下Handler的原理 Handler使 ...

  8. Redis主从复制原理学习

    Redis主从复制原理学习总结 - 运维笔记 和Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况.为了分担读压力,Redis支持主从复制,Redis的 ...

  9. java锁原理_Java锁原理学习

    Java锁原理学习 为了学习Java锁的原理,参照ReentrantLock实现了自己的可重入锁,代码如下: 先上AQS的相关方法: // AQS = AbstractQueuedSynchroniz ...

最新文章

  1. 电脑识别指令和代码的原理
  2. JVM虚拟机参数配置官方文档
  3. java导出oracle到excel_java实现将oracle表中的数据导出到excel表里
  4. 22考生这些院校计算机专业改考408
  5. 年近八旬教授曾一次性捐款8000多万,今获省杰出贡献奖!
  6. 使用计算机计算一个多边形,多边形面积计算器
  7. 每天一个linux命令(10):more命令
  8. python 项目发布会_发布会直播技术及业务实践
  9. java自动行走_java数据结构实现机器人行走
  10. SAP 独立系统的传输请求
  11. messagebox
  12. Linux常用命令大全 阶段性总结(一)
  13. 高德地图ios11 定位失败
  14. 如何在Mac上合并照片库?
  15. 回归平静是一种自我保护
  16. Ant下载及配置安装
  17. 分水岭算法java,OpenCV 学习笔记 04 深度估计与分割——GrabCut算法与分水岭算法...
  18. python一天学费多少_自学python一天的小项目实战
  19. python抓取抖音热门视频_要是30行代码!7步教会你Python爬取网页抖音热门视频
  20. 逐句回答,流式返回,ChatGPT采用的Server-sent events后端实时推送协议Python3.10实现,基于Tornado6.1

热门文章

  1. 如何使用Java写“脚本”(单个Java文件如何像脚本一样使用运行)
  2. 史明星:微博在品牌营销上的贡献
  3. 用计算机写作400字,玩电脑作文400字
  4. 电脑上的竖线符号怎么打出来
  5. mysql collect_set_Hive sql 使用group by 字段被限制使用 collect_set/collect_list处理
  6. 实验3:CUP的译码ID阶段实现
  7. win 10 激活 步骤和工具(自测可用)
  8. (一)u-boot2013.01.01 for TQ210:《Uboot简介》
  9. 【48】DMA:为什么Kafka这么快?
  10. 简单动画制作方法,教程在这里! | 万彩动画大师