再谈Android消息分发
Posted 刘兆贤
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再谈Android消息分发相关的知识,希望对你有一定的参考价值。
本文来自刘兆贤的博客_CSDN博客-Java高级,Android旅行,Android基础领域博主 ,引用必须注明出处!
ActivityThread从main方法开始,创建Looper对象、Handler和ApplicationThread接收ActivityManagerService的IPC调用,将消息传到Handler进行异步分发(更新UI主要用异步,ApplicationThread里的dumpActivity、dumpService、dumpProvider等保存组件状态的操作,使用同步更新,MessageQueue里的消息,同步的先执行,异步后执行)。
其中ApplicationThread,是一个Binder,继承自
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/IApplicationThread.aidl
ActivityManagerService,也是一个Binder,继承自
https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/IActivityManager.aidl
Android消息分发机制:
线程内调用 Looper.prepare():创建自己的Looper对象和MessageQueue,新建Hander(Looper.myLooper)用于发送本线程的消息,通过Looper.loop来执行本线程消息分发,同时结束时执行Looper.myLooper().quit()退出当前线程,不然线程一直运行,这也是HandlerThread的使用原理。
Handler消息分发,优化判断msg.callback是否为null,如果不是先执行它,再执行handlerMessage。
Looper什么时候退出循环?
当MessageQueue的next方法返回null时。为什么会为null?因为设置mQuiting字段为true。什么时候mQuiting为true?ActivityThread退出的时候,执行Looper的quit方法(非safe,safe模式:当前时间小于首个Message执行时间,则一个个recycle掉,反之取到最后一个元素,实行断链操作,避免成为GcRoot)。
子线程如何跟主线程交互?
双方实现各自的Handler,互调对方的hander发送消息。
为什么子线程中不能更新UI?
因为系统判断当前线程不是主线程(ViewRootImpl.checkThread),则抛出异常。
为什么onCreate方法里,可以在子线程中更新UI?
因为在onResume方法才生成ViewRootImpl实例,将其加到Window上。
为什么Loop.loop方法里是死循环(会调用MessageQueue里next方法的while循环),但主线程没有被卡死?
因为主线程是通过Handler被动接收消息的,只有手机被操作,或者任务到期才会唤醒。在这个过程中利用Linux的epoll和pipe机制,阻塞时,让出CPU,等有新消息再唤醒。
具体一点:会计算下次循环的时间,Binder.flushPendingCommands将未执行的binder命令刷入内核(针对阻塞时间较长的场景,确保已引用对象被挂起),通过nativePollOnce方法(linux的pipe机制,实现了IO复用),将CPU资源释放,等待唤醒。
番外:
NativePollOnce使用linux的epoll机制,将线程挂起,下个消息到来时被唤醒,类似thread的wait/notify操作,它会释放CPU,因此不会卡死。
Binder.flushPendingCommands,将未执行的Binder命令刷新到Linux内核驱动里,对于阻塞较长时间的操作有用,用于将这些对象引用释放掉,以免进程持有它们超过所需时间。
Epoll机制:
Linux的IO多路复制技术,支持高并发。epoll相对于poll和select优势,在有10万个连接,同时10个客户端发送数据,前者只检查这10个客户端,而后者则要检查10万个连接,是否有发送数据。
ANR机制:
发送延迟任务,检测当前线程是否在一定时间内完成,如果提前完成则清除该任务。为什么要这么设计?因为每个生命周期方法,都依赖前面的方法完成,是一个线性关系。真正出现ANR的是,执行耗时操作,阻塞了任务分发。
各组件阻塞时长限制:
Service Timeout(最长,前台20秒)
文件:ActiveServices.java
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000; // 前台 // How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; // 后台
Broadcast Timeout(10秒,同ContentProvider)
文件:ActivityManagerService.java
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000; // 前台
static final int BROADCAST_BG_TIMEOUT = 60*1000; // 后台
InputDispatching Timeout(5秒,同Activity)
文件:ActivityManagerService.java
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
ContentProvider Timeout(10秒,同Broadcast)
// How long we wait for an attached process to publish its content providers
// before we decide it must be hung.
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
怎么防止应用崩溃退出?
在监测到unCaughtException后,将Loop.loop进行try.catch操作,将错误抓住,并使用while(true)保证下次也会将错误抓住。这里由于Message的堵塞机制,所以不会一直执行loop操作。
while (true)
//用于保证下次crash仍能进来
try
//由于消息的阻塞机制,并不会一直执行loop
Looper.loop();
catch (Throwable e)
isChoreographerException(e);
if (sExceptionHandler != null)
sExceptionHandler.bandageExceptionHappened(e);
解决ANR的技巧:
一、AsyncLayoutInflater,异步解析xml生成View,解决IO加载慢,影响UI线程的问题。
注意事项:
1、在主线程中调用,如果在子线程中调用,在OnInflateFinishedListener回调onInflateFinished时,需要回到主线程操作setContentView或者其他View操作;因为回调是在当前线程内完成的(实际上,AsyncLayoutInflater内部本来就是在子线程中操作,没必要再加一个子线程)。
2、inflate结束会主动将传递过来的context置空,不用担心内存泄露,但需要判断异步返回View时,当前页面是否正在运行。
3、在当前Activity中,该类可以重复使用;原因是SynchronizedPool会缓存InflateRequest;属于一个生产者/消费者模式,无调用次数限制。
4、其内部的InflateThread一直在运行,没有销毁方法,故其最佳使用场景是在Application中,接收各种infalte操作,从而避免许多AsyncLayoutInflater对象无法回收。
二、Looper.myQueue().addIdleHandler。当queueIdle返回false则直接删除此任务,返回true保持idleHandler存活(下次处理完其他消息再回调它)。
public static interface IdleHandler
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
主要应用场景:用于在消息空闲时,处理一些不那么重要的操作。比如可以使用post方法获取View的宽高,也可以使用idleHandler,区别前者作为同步消息执行,而后者空闲里执行,可以用于优化一些操作。
怎么提高消息优先级?
- handler调用postAtFrontOfQueue,执行一个Runnable。
- 不要发送延时消息。
- 建立消息屏障,将asynchronous为true的消息先返回。
参考:
为什么 Android 要采用 Binder 作为 IPC 机制? - 知乎
以上是关于再谈Android消息分发的主要内容,如果未能解决你的问题,请参考以下文章