线程的跨越是怎么实现的?
内存是不分线程的,可以在子线程和主线程都使用。
在子线程里面执行某个函数(比如调用 Handler 的 sendMessage 方法),这个函数就在子线程里面
事件变为内存了,MessageQueue.enqueueMessage(msg) 将这一块内存放入了 MessageQueue 了。
Looper.loop 是在主线程中,所以取出来的 Message 是通过 dispatchMessage 放在 主线程执行的
所以上述就是子线程切换到主线程的流程。
问题
有学员问存在以下几个条件:
所有 Looper 对象(不论主线程还是子线程的)中放 Looper 对象的 ThreadLocal 都是同一个对象
这个没问题,因为 Looper 类中的 用于存放Looper 对象的 ThreadLocal 是 static final 的,整个 App 只有一个
那么看起来,所有线程也都公用同一个 Looper 对象了啊
看下 Looper 的源码:
1 | static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); |
所以学员说得没错, sThreadLocal 是整个 App 唯一的。但是,如果我们了解 ThreadLocal 的源码的话,就能得出如下的式子:
Thread1 -> 有自己的 ThreadLocalMap1 -> 存储的键值对样式 <sThreadLocal, Looper1>
Thread2 -> 有自己的 ThreadLocalMap2 -> 存储的键值对样式<sThreadLocal, Looper2>
能看到,在线程1中键值对存在ThreadLocalMap1 这个map 对象中, 样式是 <sThreadLocal, Looper1> ;线程2 中键值对存在 ThreadLocalMap2 这个 map 对象中,的样式是 <sThreadLocal, Looper2>
所以,根本原因在于: 每个线程都有自己的 ThreadLocalMap,虽然所有的线程中的不一样的 Looper 对象都有同一个 ThreadLocal 对象,但是这些线程有不一样的 ThreadLocalMap ,所以用同一个 ThreadLocal 对象作为 key 在不同的 ThreadLocalMap 中取值,取到的肯定是不同的 Looper ,这下明白了。
如何获取 Message
使用 Message.obtain() ,因为 Message 使用完成后,都会回收,所以我们可以一直使用,这是享元设计模式。这样就维持一个池子,避免内存抖动,防止 OOM。new 了必然会回收,回收了就可能有碎片。
在 Looper.loop 方法里面,msg.target.dispathcMessage(msg) 执行完后,在 for(; ;) 死循环最后一句的时候会调用 msg.recycleUnchecked(); 进行回收,将回收的msg 插入到池子的头部。
Looper 死循环不会 ANR ?
不是同一个层面的东西。毫不相关的问题。点击 5秒没响应 ANR ,其实这个点击事件它也是一个 Message。
HandlerThread
HandlerThread 存在的意义:
方便使用
线程安全,getLooper 的线程安全问题
wait 会释放锁,这样其他的函数才能获取到锁执行 notify 操作。notify 不会释放锁, 要等 synchronized 代码全部执行完才释放锁(notify操作可能不在synchronized 代码块最后 )。
IntentService
处理后台耗时任务。一个任务分为多个子任务,子任务按照顺序执行完成后,任务才能算完成,这时候可以使用 IntentService ,这样可以保证所有的子任务在同一线程执行。关于要执行的多个任务,代码写法如下:
1 | // 请求1 |
具体可以参考Android多线程:IntentService使用教程(含实例讲解)-腾讯云开发者社区-腾讯云 (tencent.com)
类似的思想也体现在 Fragment 的管理上,:
在 FragmentPagerAdapter 中,会有 instantiateItem 方法。里面会执行 mCurTrasaction.attach(fragment) ,去 attach fragment ,但是这个 attach 方法会立即执行么? 不会的,这时候只会加入list 中,等 commit 的时候才会一起执行。
并且可以保证 attach 一定是在 detach 后面执行,也是用 msg 来实现
具体得自己再去看下源码,老师讲得不详细。
glide 中的巧妙使用
假设有这样一段代码:
1 | fun with(){ |
如果连续调用 2 次,会怎样?可能你会想,第一次已经创建了 MyFragment 实例了,第二次直接通过 findFragmentByTag 就能找出来了。但是不是的,可能会创建 2 次 MyFragment 。这是因为最后在 commitAllowingStateLoss 的时候,最终是将这个操作作为一个 msg 扔到 Handler 里面。所以,第二次调用 with 的时候,第一次不一定执行完!所以,可能执行 2 次。 这个在 glide 的源码中有体现:
创建一个 HashMap ,以 tag 为key ,第一次的时候,首先从 hashmap中获取,如果没有,再从 fragmentManager 中获取,如果还没有,就创建一个,然后将其加入到 HashMap 中
第二次的时候,首先判断 HashMap 里面有没有,如果有的话,就忽略,因为这个 Fragment 最终肯定会被创建出来,此时应该是commit 的中提交到 handler 中的 msg 还没执行。
在上述代码的注释部分,可以看到最终通过handler 发送一个消息,这个消息用于将 fragment 从 HashMap 中移除 Fragment ,防止内存泄漏。
第三点为什么可以这么做呢?因为同一个handler ,commit 的时候发的消息,肯定在前面,后续用于移除 msg 的 msg 肯定在后面,这样很巧妙地保证了用完fragmengt 就从 HashMap 中移除。
上述代码其实就是 glide 中的一段代码设计思想,具体可以参考 Glide生命周期管理 - 简书 (jianshu.com) 中 2.0.1 创建RequestManagerFragment 这个章节。