导读
- 移动开发知识体系总章(Java基础、Android、Flutter)
- Android Handler消息机制
- Android中为什么主线程不会因为Looper.loop里的无限循环ANR?
- ActivityThread工作原理
由于我更新优化本篇文章中的笔误之处,导致文章莫名被删除,故此重新发布!
Android Handler消息机制
为了避免ANR,我们通常会把耗时操作放在子线程里面去执行,因为子线程不能更新UI,所以当子线程需要更新UI的时候就需要借助到Andriod的消息机制,也就是Handler机制了。那么其原理是什么呢?下面我们根据平时的使用一步一步来解读源码。
以上内容可能会产生的问题:
- 什么是ANR?
在Android上,如果你的应用程序有一段时间无法响应,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。
- 耗时操作为什么不能再主线程?
- 为什么子线程不能更新UI?
- Handler机制是什么?
一. Handler的常见用法。
- 创建handler对象并重写handleMessage方法,以便于处理自己需要的逻辑。
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
...
}
}
- 使用handler 对象发送消息(对象、载体)。
private void sendMsg(){
Mssages msg = handler.obtainMessage();
msg.what=what;
msg.age1=age1;
handler.sendMessageDelayed(msg,1000);
}
...
二. 代码分析
- 以上代码可以看出,在主线程创建了一个handler对象,并重构了handleMessage方法,然后通过handler发送消息对象,中间经过一系列处理后,handleMessage方法接收到传递的数据,就可以处理具体的逻辑了(如更新UI)。
- 以上代码可以看出,发送消息、最后处理数据时都使用的是Message对象,这是在源码设计层面时约定的媒介(载体、中间件、快递),这里先不进行展开,只做一个简单的介绍:
what字段作为标识,
arg1/arg2字段作为简单类型的参数,
obj字段作为复杂对象,以及Bundle常见参数
target字段作为handler对象标识
- 再来看发送消息 sendMessageDelayed,通过源码可以发现所有的sendMessage方法执行后,最终都会走sendMessageAtTime()方法(这里就不贴上具体源码了)。
public boolean sendMessageAtTime(@NonNull 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);
}
在这个sendMessageAtTime方法里会拿到一个MessageQueue的实例对象,并触发enqueueMessage(queue, msg, uptimeMillis)方法。那我们就先看看enqueueMessage方法,然后在去看mQueue怎么来的。
boolean enqueueMessage(Message msg, long when) {
...
Message p = mMessages;
synchronized (this) {
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
...
Message p = mMessages;
...
msg.next = p; // invariant: p == prev.next
...
}
}
return true;
}
首先这是一个被synchronized修饰的同步代码块,确保了并发问题;核心逻辑是对Message的实例对象p进行非空判断,最终两个两个逻辑里都会把msg放到Message实例对象p的next方法中进行排队。
4.现在回来研究这个mQueue对象是怎么来的呢,通过command+f(ctrl+f)搜索,最终发现是Handler构造方法中获取的(通过代码可以看出mQueue的获取是通过looper.mQueue,而我们前面在创建handler的时候使用的是无参构造的,那么这个Looper对象的实例额mLooper是怎么来的?)
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
4.1 观察Handler的其他重载的构造方法发现,我们的无参构造最后会走如下构造,而这个mLooper实例对象是通过Looper.myLooper()方法获取的而且还不能为空!由此可以看出Looper、MessageQueue都是非常重要的对象。
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;
}
4.2 继续跟踪源码,进入Looper类发现myLooper()的内部是通过sThreadLocal.get()获取的,那么有get()自然是有set()的,所以我们command+f(ctrl+f)搜索找到了prepare()方法,而这个方法会被prepareMainLooper的方法执行,prepare翻译过来是准备的意思,prepareMainLooper就是准备主Looper的意思了,这里要注意有synchronized修饰噢。
...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
...
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));
}
...
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
4.3 到这里,我们知道了Handler无参构造的时候其实是使用了一个叫MainLooper及MessageQueue的对象,那这个MainLooper具体又是在哪里创建的呢,那就查查这个prepareMainLooper()方法在哪里被调用的使用command+shift+f进行全局搜索:
搜索出来4个结果,除了Looper.java外,SystemServer.java是系统级别的这里不做深入,后续新增该类的解读; Bridge.java是桥梁类,注释中写着“Main entry point of the LayoutLib Bridge.”,这里也不做深入;最后是ActivityThread.java类,也是这里需要重点关注的类:
public final class ActivityThread extends ClientTransactionHandler {
...
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
...
}
...
}
有两个重大发现:
1、Looper.prepareMainLooper()是在一个叫ActivityThread 的 final 类型的Java类,的main(String[] args) 方法中执行的。
2、还执行了Looper.loop();
main是Java的入口方法,说明什么呢,在整个(应用)代码里Looper.prepareMainLooper()是最先执行的代码之一,也就是说Handler所使用的Looper早就初始化好了,而且是同步唯一的(如果忘了请回翻 )。
接下来看看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;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
这里可以看出几个重要消息
1、Looper、MessageQueue是不可缺少的对象
2、使用到了for (;;) ,就是说无限循环,一直去MessageQueue的next()去查有没有Message对象,
3、有Message对象时会执行msg.target.dispatchMessage(msg);方法
当然也会有2个困惑的问题
3.1、for (;;) 这个无限循环不会让APP卡死吗?
3.2、 有一行代码if (msg == null) return 其注释是的意思是是阻塞,return之后不是继续for循环吗?
4.4 继续跟踪 msg.target.dispatchMessage(msg)方法,前面有提到target其实就是Handler(为什么呢,大家有兴趣可以看看源码)也就是说dispatchMessage方法是Handler类下的
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
发现这里调用了一个很眼熟的方法名handleMessage 没错,这就是一开始我们重写的的handleMessage方法,至此,脑海中是否已经有了一个Handle整体轮廓图?是否还有一个疑问,Handler啥时候把线程给切换了?
总结
1.APP在启动的时候,最先启动ActivityThread类的main方法,其中Looper的初始化工作和准备工作就是这时候执行的(获得Looper对象、MessageQueue)
2.Looper开启loop()方法(永动机)去死循环遍历MessageQueue的消息队列(至于为什么没有卡死前面有提到?有提到吗,为什么我修改的时候发现没有,捂脸)
3.在通常用法中创建Handler对象时,构造方法会拿到looper、mQueue对象
4.使用Handler对象发送消息(sendXxx())
5.把消息加入队列(mQueue.enqueueMessage)
6.第2步的永动机读取MessageQueue的消息
7.执行dispatchMessage方法
8.执行代码中创建Handler方法时重写的handleMessage方法完成Handler机制的整个过程。
最后,让我们带着文章中的问题,进行继续深入:Android中为什么主线程不会因为Looper.loop里的无限循环ANR?