Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
// step1:获取当前线程 Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread that has not called Looper.prepare()");
}
// step2:获取 Looper 对象绑定的 MessageQueue 对象并赋值给 Handler 的 mQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- step1:调用myLooper() 方法,该方法是使用 sThreadLocal 对象获取当前线程的 Looper 对象,回顾一下:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
如果获取的 Looper 对象为 null,说明没有执行 Looper.prepare() 为当前线程保存 Looper 变量,就会抛出 RuntimeException。这里又说明了Handler 必须在有 Looper 的线程中使用,报错不说,没有 Looper 就无法绑定 MessageQueue 对象也就无法进行更多有关消息的操作。
-
step2:mQueue = mLooper.mQueue 说明了 Handler 的 MessageQueue 对象是由当前线程 Looper 的 MessageQueue 对象赋值的。这里问题 5 解决:Handler 在创建时绑定了当前线程 Looper 的 MessageQueue 对象。
- 由于 Handler 和 Looper 可以看作使用的是同一个 MessageQueue 对象,所以 Handler 和 Looper 可以共享消息队列 MessageQueue。Handler 发送消息(用 mQueue 往消息对列插入消息),Looper 可以方便的循环使用 mQueue 查询消息,如果查询到消息,就可以用 Message 对象绑定的 Handler 对象 target 去处理消息,反之则阻塞。
既然说到了 Handler 的构造器,就想到一个问题:问题 6:关于 handler,在任何地方 new handler 都是什么线程下?这个问题要分是否传递 Looper 对象来看。
-
不传递 Looper 创建 Handler:Handler handler = new Handler();上文就是 Handler 无参创建的源码,可以看到是通过 Looper.myLooper() 来获取 Looper 对象,也就是说对于不传递 Looper 对象的情况下,在哪个线程创建 Handler 默认获取的就是该线程的 Looper 对象,那么 Handler 的一系列操作都是在该线程进行的。
- 传递 Looper 对象创建 Handler:Handler handler = new Handler(looper);那么看看传入 Looper 的构造函数:
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
// 第一个参数是 looper 对象,第二个 callback 对象,第三个消息处理方式(是否异步)
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看出来传递 Looper 对象 Handler 就直接使用了。所以对于传递 Looper 对象创建 Handler 的情况下,传递的 Looper 是哪个线程的,Handler 绑定的就是该线程。
到这里 Looper 和 Handler 就有一个大概的流程了,接下来看一个简单的子线程 Handler 使用例子:
new Thread() {br/>@Override
public void run() {
// step1
Looper.prepare();
// step2
Handler handler = new Handler(){br/>@Override
public void handleMessage(Message msg) {
if(msg.what == 1){
runOnUiThread(new Runnable() {br/>@Override
public void run() {
Toast.makeText(MainActivity.this,"HandlerTest",Toast.LENGTH_SHORT).show();
}
});
// step5
Looper.myLooper().quit();
}
}
};
// step3
handler.sendEmptyMessage(1);
// step4
Looper.loop();
}
}.start();
- step1: 调用 Looper.prepare(); 为当前线程创建 Looper 对象,同时也就创建了 MessageQueue,之后将该线程的 Looper 对象保存在 ThreadLocal 中。注意这里的一切操作都在子线程中,如果不调用 Looper.prepare() 就使用 Handler 会报错。
- step2: 创建 Handler 对象,覆写 handleMessage 处理消息,等待该 Handler 发送的消息处理时会调用该方法。
- step3: 使用 handler 发送消息,这里只是示例,毕竟自己给自己发送消息没啥必要。发送的过程中会将自己赋值给 msg.target,然后再将消息插入到 Looper 绑定的 MessageQueue 对象中。
- step4: 调用 Looper.loop(); 首先获取当前线程的 Looper 对象,根据 Looper 对象就可以拿到 Looper 保存的 MessageQueue 对象 mQueue。有了 MessageQueue 对象就可以 for 循环获取它保存的消息 Message 对象,如果消息不存在就返回 null 阻塞,反之则使用 Message 中保存的 Handler:msg.target 来处理消息,最终调用 handleMessage 也就是之前覆写的方法来处理消息。
- step5: 逻辑处理完毕以后,应在最后使用 quit 方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。
##三、总结和其它
3.1 Handler、Looper、MessageQueue、Message
- Handler 用来发送消息,创建时先获取默认或传递来的 Looper 对象,并持有 Looper 对象包含的 MessageQueue,发送消息时使用该 MessageQueue 对象来插入消息并把自己封装到具体的 Message 中;
- Looper 用来为某个线程作消息循环。Looper 持有一个 MessageQueue 对象 mQueue,这样就可以通过循环来获取 – MessageQueue 所维护的 Message。如果获取的 MessageQueue 没有消息时,便阻塞在 loop 的queue.next() 中的 nativePollOnce() 方法里,反之则唤醒主线程继续工作,之后便使用 Message 封装的 handler 对象进行处理。
- MessageQueue 是一个消息队列,它不直接添加消息,而是通过与 Looper 关联的 Handler 对象来添加消息。
- Message 包含了要传递的数据和信息。
3.2 Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
这是知乎上的问题,感觉问的挺有意思。平时可能不太会太深究这些问题,正好有大神回答那就记录一下吧。
- 为什么不会因为死循环卡死?
线程可以看作是一段可执行代码,当代码执行完毕线程的生命周期就该终止了。对于主线程来说我们不希望它执行一段时间后退出,所以简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。既然是死循环那么怎么去处理消息呢,通过创建新线程的方式。 - 为这个死循环准备了一个新线程
在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:
public static void main(){
…
Looper.prepareMainLooper();
//创建ActivityThread对象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (创建新线程)
thread.attach(false);
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
// step2: 开始循环
Loop.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。
-
主线程的死循环一直运行是不是特别消耗CPU资源呢?
其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.nextAndroid消息机制1-Handler(Java层),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资。 - Activity的生命周期是怎么实现在死循环体外能够执行起来的?
上文 main 函数有一部分获取 sMainThreadHandler 的代码:
final H mH = new H();
public static void main(){
…
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
…
}
尾声
面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。
不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2017-2020字节跳动Android面试历年真题解析》
如果需要PDF版本可以自行领取!
原创文章,作者:745907710,如若转载,请注明出处:https://blog.ytso.com/219614.html