学习-Android-Handler-消息机制需要注意这些问题!(下)(1)

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(){

《设计思想解读开源框架》

第一章、 热修复设计

  • 第一节、 AOT/JIT & dexopt 与 dex2oat

  • 第二节、 热修复设计之 CLASS_ISPREVERIFIED 问题

  • 第三节、热修复设计之热修复原理

  • 第四节、Tinker 的集成与使用(自动补丁包生成)

    第二章、 插件化框架设计

  • 第一节、 Class 文件与 Dex 文件的结构解读

  • 第二节、 Android 资源加载机制详解

  • 第三节、 四大组件调用原理

  • 第四节、 so 文件加载机制

  • 第五节、 Android 系统服务实现原理

    第三章、 组件化框架设计

  • 第一节、阿里巴巴开源路由框——ARouter 原理分析

  • 第二节、APT 编译时期自动生成代码&动态类加载

  • 第三节、 Java SPI 机制

  • 第四节、 AOP&IOC

  • 第五节、 手写组件化架构

    第四章、图片加载框架

  • 第一节、图片加载框架选型

  • 第二节、Glide 原理分析

  • 第三节、手写图片加载框架实战

    第五章、网络访问框架设计

  • 第一节、网络通信必备基础

  • 第二节、OkHttp 源码解读

  • 第三节、Retrofit 源码解析

    第六章、 RXJava 响应式编程框架设计

  • 第一节、链式调用

  • 第二节、 扩展的观察者模式

  • 第三节、事件变换设计

  • 第四节、Scheduler 线程控制

    第七章、 IOC 架构设计

  • 第一节、 依赖注入与控制反转

  • 第二节、ButterKnife 原理上篇、中篇、下篇

  • 第三节、Dagger 架构设计核心解密

    第八章、 Android 架构组件 Jetpack

  • 第一节、 LiveData 原理

  • 第二节、 Navigation 如何解决 tabLayout 问题

  • 第三节、 ViewModel 如何感知 View 生命周期及内核原理

  • 第四节、 Room 架构方式方法

  • 第五节、 dataBinding 为什么能够支持 MVVM

  • 第六节、 WorkManager 内核揭秘

  • 第七节、 Lifecycles 生命周期


    本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

4607112889)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值