续学习 Android Handler 消息机制需要注意这些问题!(上)https://www.jianshu.com/writer#/notebooks/34739187/notes/42390025/preview
2.2 Handler 的创建和作用
上面说到 loop() 方法在不断从消息队列 MessageQueue 中取出消息 (queue.next() 方法), 如果没有消息则阻塞, 反之交给 Message 绑定的 Handler 处理. 回顾一下没解决的两个问题:
- 问题 3:MessageQueue 里的消息从哪里来? Handler 是如何往 MessageQueue 中插入消息的?
- 问题 4:msg.target 是何时被赋值的?, 也就是说 Message 是如何绑定 Handler 的?
既然要解决 Handler 插入消息的问题, 就要看 Handler 发送消息的过程.
2.2.1 Handler 发送消息
- Handler --> sendMessage(Message msg);
- final MessageQueue mQueue;
- public final boolean sendMessage(Message msg){
- return sendMessageDelayed(msg, 0);
- }
- // 发送延时消息
- public final boolean sendMessageDelayed(Message msg, long delayMillis){
- if (delayMillis <0) {
- delayMillis = 0;
- }
- return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- }
- // 指定时间发送消息
- public boolean sendMessageAtTime(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);
- }
- // 处理消息, 赋值 Message 对象的 target, 消息队列插入消息
- private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
- msg.target = this;
- if (mAsynchronous) {
- msg.setAsynchronous(true);
- }
- return queue.enqueueMessage(msg, uptimeMillis);
- }
可以看到调用 sendMessage(Message msg) 方法最终会调用到 enqueueMessage() 方法, 这个方法主要有两个作用: 赋值 Message 对象的 target, 消息队列插入消息.
赋值 msg 的 target:msg.target = this 把发送消息的 Handler 赋值给 msg 对象的 target. 那么问题 4 就解决了: Handler 执行发送消息的过程中将自己绑定给了 Message 的 target, 这样两者之间就产生了联系;
消息队列插入消息: queue.enqueueMessage(msg, uptimeMillis) queue 是 MessageQueue 的一个实例, queue.enqueueMessage(msg, uptimeMillis) 是执行 MessageQueue 的 enqueueMessage 方法来插入消息. 这样问题 3 就找到答案: Handler 在发送消息的时候执行 MessageQueue 的 enqueueMessage 方法来插入消息; 关于 MessageQueue 是怎么执行插入消息的过程, 参考下方文章 4.3 节
Android 消息机制 1-Handler(Java 层)
上面 Handler 发送消息使用了 MessageQueue 的实例 queue, 可以看到这个 queue 是上一个方法 sendMessageAtTime 中由 Handler 的成员变量 mQueue 赋值的, 那么 mQueue 是哪来的? 问题 5:Handler 如何绑定 MessageQueue? 先剧透一下 Handler 绑定的是 Looper 的 MessageQueue 对象, Looper 的 MessageQueue 对象是在 Looper 创建时就 new 的.
要了解 Handler 的 MessageQueue 对象是怎么赋值的就要看 Handler 的构造函数了, Handler 创建的时候作了一些列操作比如获取当前线程的 Looper, 绑定 MessageQueue 对象等.
2.2.2 Handler 的创建
下面是 Handler 无参构造器和主要的构造器, 另外几个重载的构造器有些是通过传递不同参数调用包含两个参数的构造器. 两个参数构造函数第一个参数为 callback 回调, 第二个函数用来标记消息是否异步.
- // 无参构造器
- public Handler() {
- this(null, false);
- }
- public Handler(Callback callback, boolean async) {
- if (FIND_POTENTIAL_LEAKS) {
- final Class<? extends Handler> klass = getClass();
- if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
- (klass.getModifiers() & Modifier.STATIC) == 0) {
- 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() {
- @Override
- public void run() {
- // step1
- Looper.prepare();
- // step2
- Handler handler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- if(msg.what == 1){
- runOnUiThread(new Runnable() {
- @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(); } ... } final Handler getHandler() { return mH; }
类 H 继承了 Handler, 在主线程创建时就创建了这个 Handler 用于处理 Binder 线程发送来的消息.
Activity 的生命周期都是依靠主线程的 Looper.loop, 当收到不同 Message 时则采用相应措施:
在 H.handleMessage(msg) 方法中, 根据接收到不同的 msg, 执行相应的生命周期.
比如收到 msg=H.LAUNCH_ACTIVITY, 则调用 ActivityThread.handleLaunchActivity() 方法, 最终会通过反射机制, 创建 Activity 实例, 然后再执行 Activity.onCreate() 等方法;
再比如收到 msg=H.PAUSE_ACTIVITY, 则调用 ActivityThread.handlePauseActivity() 方法, 最终会执行 Activity.onPause() 等方法. 上述过程, 我只挑核心逻辑讲, 真正该过程远比这复杂.
3.3 Handler 使用造成内存泄露
有延时消息, 要在 Activity 销毁的时候移除 Messages
匿名内部类导致的泄露改为匿名静态内部类, 并且对上下文或者 Activity 使用弱引用.
来源: http://www.jianshu.com/p/7e7a0139c598