主线程为什么不用初始化Looper?

答:因为应用在启动的过程中就已经初始化主线程Looper了。

每个java应用程序都是有一个main方法入口,Android是基于Java的程序也不例外。Android程序的入口在ActivityThread的main方法中:

// 初始化主线程Looper

Looper.prepareMainLooper();

...

// 新建一个ActivityThread对象

ActivityThread thread = new ActivityThread();

thread.attach(false, startSeq);

// 获取ActivityThread的Handler,也是他的内部类H

if (sMainThreadHandler == null) {

sMainThreadHandler = thread.getHandler();

}

...

Looper.loop();

// 如果loop方法结束则抛出异常,程序结束

throw new RuntimeException("Main thread loop unexpectedly exited");

}

main方法中先初始化主线程Looper,新建ActivityThread对象,然后再启动Looper,这样主线程的Looper在程序启动的时候就跑起来了。我们不需要再去初始化主线程Looper。

为什么主线程的Looper是一个死循环,但是却不会ANR?

答: 因为当Looper处理完所有消息的时候会进入阻塞状态,当有新的Message进来的时候会打破阻塞继续执行。

这其实没理解好ANR这个概念。ANR,全名Application Not Responding。当我发送一个绘制UI 的消息到主线程Handler之后,经过一定的时间没有被执行,则抛出ANR异常。Looper的死循环,是循环执行各种事务,包括UI绘制事务。Looper死循环说明线程没有死亡,如果Looper停止循环,线程则结束退出了。Looper的死循环本身就是保证UI绘制任务可以被执行的原因之一。同时UI绘制任务有同步屏障,可以更加快速地保证绘制更快执行。同步屏障下面会讲。

Handler如何保证MessageQueue并发访问安全?

答:循环加锁,配合阻塞唤醒机制。

我们可以发现MessageQueue其实是“生产者-消费者”模型,Handler不断地放入消息,Looper不断地取出,这就涉及到死锁问题。如果Looper拿到锁,但是队列中没有消息,就会一直等待,而Handler需要把消息放进去,锁却被Looper拿着无法入队,这就造成了死锁。Handler机制的解决方法是循环加锁。在MessageQueue的next方法中:

Message next() {

...

for (;;) {

...

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {

...

}

}

}

我们可以看到他的等待是在锁外的,当队列中没有消息的时候,他会先释放锁,再进行等待,直到被唤醒。这样就不会造成死锁问题了。

那在入队的时候会不会因为队列已经满了然后一边在等待消息处理一边拿着锁呢?这一点不同的是MessageQueue的消息没有上限,或者说他的上限就是JVM给程序分配的内存,如果超出内存会抛出异常,但一般情况下是不会的。

Handler是如何切换线程的?

答: 使用不同线程的Looper处理消息。

前面我们聊到,代码的执行线程,并不是代码本身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个Looper都运行在对应的线程,所以不同的Looper调用的dispatchMessage方法就运行在其所在的线程了。

Handler的阻塞唤醒机制是怎么回事?

答: Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制。

这个机制也是类似于handler机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。

能不能让一个Message加急被处理?/ 什么是Handler同步屏障?

答:可以 / 一种使得异步消息可以被更快处理的机制

如果向主线程发送了一个UI更新的操作Message,而此时消息队列中的消息非常多,那么这个Message的处理就会变得缓慢,造成界面卡顿。所以通过同步屏障,可以使得UI绘制的Message更快被执行。

什么是同步屏障?这个“屏障”其实是一个Message,插入在MessageQueue的链表头,且其target==null。Message入队的时候不是判断了target不能为null吗?不不不,添加同步屏障是另一个方法:

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;

// 把当前需要执行的Message全部执行

if (when != 0) {

while (p != null && p.when <= when) {

prev = p;

p = p.next;

}

}

// 插入同步屏障

if (prev != null) { // invariant: p == prev.next

msg.next = p;

prev.next = msg;

} else {

msg.next = p;

mMessages = msg;

}

return token;

}

}

可以看到同步屏障就是一个特殊的target,哪里特殊呢?target==null,我们可以看到他并没有给target属性赋值。那这个target有什么用呢?看next方法:

Message next() {

...

// 阻塞时间

int nextPollTimeoutMillis = 0;

for (;;) {

...

// 阻塞对应时间

nativePollOnce(ptr, nextPollTimeoutMillis);

// 对MessageQueue进行加锁,保证线程安全

synchronized (this) {

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

Message msg = mMessages;

/**

* 1

*/

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 {

// 获得消息且现在要执行,标记MessageQueue为非阻塞

mBlocked = false;

/**

* 2

*/

// 一般只有异步消息才会从中间拿走消息,同步消息都是从链表头获取

if (prevMsg != null) {

prevMsg.next = msg.next;

} else {

mMessages = msg.next;

}

msg.next = null;

msg.markInUse();

return msg;

}

} else {

// 没有消息,进入阻塞状态

nextPollTimeoutMillis = -1;

}

// 当调用Looper.quitSafely()时候执行完所有的消息后就会退出

if (mQuitting) {

dispose();

return null;

}

...

}

...

}

}

这个方法我在前面讲过,我们重点看一下关于同步屏障的部分,看注释1的地方的代码:

if (msg != null && msg.target == null) {

// 同步屏障,找到下一个异步消息

do {

prevMsg = msg;

msg = msg.next;

} while (msg != null && !msg.isAsynchronous());

}

如果遇到同步屏障,那么会循环遍历整个链表找到标记为异步消息的Message,即isAsynchronous返回true,其他的消息会直接忽视,那么这样异步消息,就会提前被执行了。注释2的代码注意一下就可以了。

注意,同步屏障不会自动移除,使用完成之后需要手动进行移除,不然会造成同步消息无法被处理。从源码中可以看到如果不移除同步屏障,那么他会一直在那里,这样同步消息就永远无法被执行了。

有了同步屏障,那么唤醒的判断条件就必须再加一个:MessageQueue中有同步屏障且处于阻塞中,此时插入在所有异步消息前插入新的异步消息。这个也很好理解,跟同步消息是一样的。如果把所有的同步消息先忽视,就是插入新的链表头且队列处于阻塞状态,这个时候就需要被唤醒了。看一下源码:

boolean enqueueMessage(Message msg, long when) {

...

// 对MessageQueue进行加锁

synchronized (this) {

...

if (p == null || when == 0 || when < p.when) {

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else {

/**

* 1

*/

// 当线程被阻塞,且目前有同步屏障,且入队的消息是异步消息

needWake = mBlocked && p.target == null && msg.isAsynchronous();

Message prev;

for (;;) {

prev = p;

p = p.next;

if (p == null || when < p.when) {

break;

}

/**

* 2

*/

// 如果找到一个异步消息,说明前面有延迟的异步消息需要被处理,不需要被唤醒

if (needWake && p.isAsynchronous()) {

needWake = false;

}

}

msg.next = p;

prev.next = msg;

}

// 如果需要则唤醒队列

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

同样,这个方法我之前讲过,把无关同步屏障的代码忽视,看到注释1处的代码。如果插入的消息是异步消息,且有同步屏障,同时MessageQueue正处于阻塞状态,那么就需要唤醒。而如果这个异步消息的插入位置不是所有异步消息之前,那么不需要唤醒,如注释2。

那我们如何发送一个异步类型的消息呢?有两种办法:

使用异步类型的Handler发送的全部Message都是异步的

给Message标志异步

Handler有一系列带Boolean类型的参数的构造器,这个参数就是决定是否是异步Handler:

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {

mLooper = looper;

mQueue = looper.mQueue;

mCallback = callback;

// 这里赋值

mAsynchronous = async;

}

在发送消息的时候就会给Message赋值:

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

}

但是异步类型的Handler构造器是标记为hide,我们无法使用,所以我们使用异步消息只有通过给Message设置异步标志:

public void setAsynchronous(boolean async) {

if (async) {

flags |= FLAG_ASYNCHRONOUS;

} else {

flags &= ~FLAG_ASYNCHRONOUS;

}

}

但是!!!!,其实同步屏障对于我们的日常使用的话其实是没有多大用处。因为设置同步屏障和创建异步Handler的方法都是标志为hide,说明谷歌不想要我们去使用他。所以这里同步屏障也作为一个了解,可以更加全面地理解源码中的内容。

什么是IdleHandler?

答: 当MessageQueue为空或者目前没有需要执行的Message时会回调的接口对象。

IdleHandler看起来好像是个Handler,但他其实只是一个有单方法的接口,也称为函数型接口:

public static interface IdleHandler {

boolean queueIdle();

}

在MessageQueue中有一个List存储了IdleHandler对象,当MessageQueue没有需要被执行的Message时就会遍历回调所有的IdleHandler。所以IdleHandler主要用于在消息队列空闲的时候处理一些轻量级的工作。

IdleHandler的调用是在next方法中:

Message next() {

// 如果looper已经退出了,这里就返回null

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

// IdleHandler的数量

int pendingIdleHandlerCount = -1;

// 阻塞时间

int nextPollTimeoutMillis = 0;

for (;;) {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

// 阻塞对应时间

nativePollOnce(ptr, nextPollTimeoutMillis);

// 对MessageQueue进行加锁,保证线程安全

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 {

// 获得消息且现在要执行,标记MessageQueue为非阻塞

mBlocked = false;

// 一般只有异步消息才会从中间拿走消息,同步消息都是从链表头获取

if (prevMsg != null) {

prevMsg.next = msg.next;

} else {

mMessages = msg.next;

}

msg.next = null;

msg.markInUse();

return msg;

}

} else {

// 没有消息,进入阻塞状态

nextPollTimeoutMillis = -1;

}

// 当调用Looper.quitSafely()时候执行完所有的消息后就会退出

if (mQuitting) {

dispose();

return null;

}

// 当队列中的消息用完了或者都在等待时间延迟执行同时给pendingIdleHandlerCount<0

// 给pendingIdleHandlerCount赋值MessageQueue中IdleHandler的数量

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

pendingIdleHandlerCount = mIdleHandlers.size();

}

// 没有需要执行的IdleHanlder直接continue

if (pendingIdleHandlerCount <= 0) {

// 执行IdleHandler,标记MessageQueue进入阻塞状态

mBlocked = true;

continue;

}

// 把List转化成数组类型

if (mPendingIdleHandlers == null) {

mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];

}

mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

}

// 执行IdleHandler

for (int i = 0; i < pendingIdleHandlerCount; i++) {

final IdleHandler idler = mPendingIdleHandlers[i];

mPendingIdleHandlers[i] = null; // 释放IdleHandler的引用

boolean keep = false;

try {

keep = idler.queueIdle();

} catch (Throwable t) {

Log.wtf(TAG, "IdleHandler threw exception", t);

}

// 如果返回false,则把IdleHanlder移除

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

// 最后设置pendingIdleHandlerCount为0,防止再执行一次

pendingIdleHandlerCount = 0;

// 当在执行IdleHandler的时候,可能有新的消息已经进来了

// 所以这个时候不能阻塞,要回去循环一次看一下

nextPollTimeoutMillis = 0;

}

}

代码很多,可能看着有点乱,我梳理一下逻辑,然后再回去看源码就会很清晰了:

当调用next方法的时候,会给pendingIdleHandlerCount赋值为-1

如果队列中没有需要处理的消息的时候,就会判断pendingIdleHandlerCount是否为<0,如果是则把存储IdleHandler的list的长度赋值给pendingIdleHandlerCount

把list中的所有IdleHandler放到数组中。这一步是为了不让在执行IdleHandler的时候List被插入新的IdleHandler,造成逻辑混乱

然后遍历整个数组执行所有的IdleHandler

最后给pendingIdleHandlerCount赋值为0。然后再回去看一下这个期间有没有新的消息插入。因为pendingIdleHandlerCount的值为0不是-1,所以IdleHandler只会在空闲的时候执行一次

同时注意,如果IdleHandler返回了false,那么执行一次之后就被丢弃了。

建议读者再回去把源码看一遍,这样逻辑会清晰很多。

最后

到这里,Handler系列的文章就结束了。如果有地方遗漏没介绍到,或者有疑问,可评论区留言。 感谢阅读。

大家如果还想了解更多Android 相关的更多知识点可以点进我的【GitHub】项目中,里面记录了许多的Android 知识点。

android handle 阻塞,Android全面解析之Handler机制:常见问题汇总相关推荐

  1. android bluetooth 阻塞,Android BluetoothSocket.isConnected始终返回false

    @H_502_5@ 以及有问题的代码: protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedI ...

  2. android终端模拟器黑屏,BlueStacks 蓝叠模拟器常见问题汇总,教你如何一招搞定

    说到现在比较流行的安卓模拟器,BlueStacks 绝对是榜上有名,它的出现让广大玩家可以在电脑中成功体验各种手游.即使如此好用的模拟器,也存在着很多问题,让很多玩家为此头疼不已.今天小编整理了一些关 ...

  3. Android Handler机制简单分析

    丨版权说明 : <Android Handler机制简单分析>于当前CSDN博客和乘月网属同一原创,转载请说明出处,谢谢. 本文一切从简,将围绕以下流程展开叙述: what why how ...

  4. android串口补位,Rust多线程中的消息传递机制

    代码说话. use std::thread; use std::sync::mpsc; use std::time::Duration; fn main() { let (tx, rx) = mpsc ...

  5. 【Android 异步操作】Handler 机制 ( MessageQueue 消息队列的阻塞机制 | Java 层机制 | native 层阻塞机制 | native 层解除阻塞机制 )

    文章目录 一.MessageQueue 的 Java 层机制 二.MessageQueue 的 native 层阻塞机制 三.MessageQueue 的 native 层解除阻塞机制 三.Messa ...

  6. android Handler机制原理解析(一篇就够,包你形象而深刻)

    首先,我将Handler相关的原理机制形象的描述为以下情景: Handler:快递员(属于某个快递公司的职员) Message:包裹(可以放置很多东西的箱子) MessageQueue:快递分拣中心( ...

  7. 深入Android应用开发_核心技术解析与最佳实践

    感谢作者的奉献,以下是我读了这本书的笔记,也只是摘录对我有用的片段,分享之: 一.深入解析android核心组件和应用框架 1.listView下空列表的显示问题.重写适配器的isEmpty(),或H ...

  8. Android多线程:深入分析 Handler机制源码(二)

    前言 在Android开发的多线程应用场景中,Handler机制十分常用 接下来,深入分析 Handler机制的源码,希望加深理解 目录 1. Handler 机制简介 定义 一套 Android 消 ...

  9. Android消息机制(Handler机制) - 线程的等待和唤醒

    我们都知道,Android的Handler机制,会在线程中开启消息循环,不断的从消息队列中取出消息,这个机制保证了主线程能够及时的接收和处理消息. 通常在消息队列中(MessageQueue)中没有消 ...

最新文章

  1. 上传图片,要求图片200100象素,大小小于2M
  2. php.ini 中文英文对照详细配置手册
  3. Mac用户SVN图形界面推荐
  4. 什么是跨域?什么是CSRF?
  5. testflight开发者已将您从测试计划中移除_使用 TestFlight 测?试 App
  6. python3 slice
  7. Leetcode之整数反转
  8. ZOJ 3256 Tour in the Castle(插头DP-按行递推—矩阵)
  9. 抽象代数基本概念(一):代数系
  10. 操作系统之银行家算法实现代码
  11. 光有想法怎么开技术公司?
  12. 适用于 Linux 系统的 11 款图像查看器
  13. GPS定位(四)-经纬度格式转换-(互转 度转度分秒 度分秒转度……)
  14. 【python】字符串string的截取;获取字符串内的一串
  15. 六款Android 应用的自动化测试工具
  16. javaSE-String,StringBuffer和StringBuilder
  17. linux-top命令详解
  18. 斐讯k3搭建nginx+php+MariaDB(mysql )的教程
  19. PMOS与NMOS的区别
  20. python五角星教程_绘制五角星_清华尹成python入门教程_少儿编程视频-51CTO学院

热门文章

  1. JAVA实现美团电影价格抓取(附代码)
  2. js 全屏 退出全屏
  3. 支付宝会员卡开卡表单模板配置(alipay.marketing.card.formtemplate.set)JAVA版本demo
  4. Spring Boot SchedulingConfigurer定时执行任务(配置式反射调用)
  5. java web项目中连接mysql数据库,javaweb之eclipse工程连接mysql数据库
  6. pycharm自带python环境_Pycharm安装+python安装+环境配置
  7. [NLP] 相对位置编码(二) Relative Positional Encodings - Transformer-XL
  8. git 多用户多仓库配置
  9. Hadoop学习笔记
  10. Topshelf创建Windows服务