Handler原理学习
前言
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原理学习相关推荐
- Mybatis底层原理学习(二):从源码角度分析一次查询操作过程
在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...
- 从使用到原理学习Java线程池
来源:SilenceDut http://www.codeceo.com/article/java-threadpool-learn.html 线程池的技术背景 在面向对象编程中,创建和销毁对象是很费 ...
- Android 系统(189)---Android Handler:这是一份 全面、详细的Handler机制 学习攻略
Android Handler:这是一份 全面.详细的Handler机制 学习攻略 前言 在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将献上一份 全面.详细的Handl ...
- 【Android 异步操作】Handler ( 主线程中的 Handler 与 Looper | Handler 原理简介 )
文章目录 一.主线程中的 Handler 与 Looper 二.Handler 原理简介 一.主线程中的 Handler 与 Looper Android 系统中 , 点击图标启动一个应用进程 , 就 ...
- Docker镜像原理学习理解
Docker镜像原理学习理解 一.Docker镜像的组成 1.Docker镜像图层 2.union file system 3.镜像层-bootfs 4.镜像层-rootfs 5.镜像层-依赖环境 6 ...
- Android Handler原理
前言 Handler消息处理机制在Android开发中起着举足轻重的作用,我们有必要好好理解下其原理,先前我写的一篇文章,感觉疏漏了好多东西,因此打算写这篇文章,下面我们先从一个简单的例子出发 一.日 ...
- android 实例源码解释,Android Handler 原理分析及实例代码
Android Handler 原理分析 Handler一个让无数android开发者头疼的东西,希望我今天这边文章能为您彻底根治这个问题 今天就为大家详细剖析下Handler的原理 Handler使 ...
- Redis主从复制原理学习
Redis主从复制原理学习总结 - 运维笔记 和Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况.为了分担读压力,Redis支持主从复制,Redis的 ...
- java锁原理_Java锁原理学习
Java锁原理学习 为了学习Java锁的原理,参照ReentrantLock实现了自己的可重入锁,代码如下: 先上AQS的相关方法: // AQS = AbstractQueuedSynchroniz ...
最新文章
- 电脑识别指令和代码的原理
- JVM虚拟机参数配置官方文档
- java导出oracle到excel_java实现将oracle表中的数据导出到excel表里
- 22考生这些院校计算机专业改考408
- 年近八旬教授曾一次性捐款8000多万,今获省杰出贡献奖!
- 使用计算机计算一个多边形,多边形面积计算器
- 每天一个linux命令(10):more命令
- python 项目发布会_发布会直播技术及业务实践
- java自动行走_java数据结构实现机器人行走
- SAP 独立系统的传输请求
- messagebox
- Linux常用命令大全 阶段性总结(一)
- 高德地图ios11 定位失败
- 如何在Mac上合并照片库?
- 回归平静是一种自我保护
- Ant下载及配置安装
- 分水岭算法java,OpenCV 学习笔记 04 深度估计与分割——GrabCut算法与分水岭算法...
- python一天学费多少_自学python一天的小项目实战
- python抓取抖音热门视频_要是30行代码!7步教会你Python爬取网页抖音热门视频
- 逐句回答,流式返回,ChatGPT采用的Server-sent events后端实时推送协议Python3.10实现,基于Tornado6.1
热门文章
- 如何使用Java写“脚本”(单个Java文件如何像脚本一样使用运行)
- 史明星:微博在品牌营销上的贡献
- 用计算机写作400字,玩电脑作文400字
- 电脑上的竖线符号怎么打出来
- mysql collect_set_Hive sql 使用group by 字段被限制使用 collect_set/collect_list处理
- 实验3:CUP的译码ID阶段实现
- win 10 激活 步骤和工具(自测可用)
- (一)u-boot2013.01.01 for TQ210:《Uboot简介》
- 【48】DMA:为什么Kafka这么快?
- 简单动画制作方法,教程在这里! | 万彩动画大师