带你了解源码中的 ThreadLocal (4)

mAttachInfo 是当 View 被 attachToWindow 时才会被赋值,所以,如果 View 还没被 attachToWindow 时,这些 Runnable 会先被缓存起来,版本 android-24 之前的实现是交由 ViewRootImpl 实现,如下:

//ViewRootImpl#getRunQueue() static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq != null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; } //ViewRootImpl.sRunQueues static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();

这点关键点是,sRunQueues 是一个 ThreadLocal 对象,而且我们使用 View.post() 是经常有可能在各种子线程中的,为的就是利用这个方法方便的将 Runnable 切到主线程中执行,但这样的话,其实如果在 View 还没被 attachToWindow 时,这些 Runnable 就是被缓存到各自线程中了,因为使用的是 ThreadLocal。

而这些被缓存起来的 Runnable 被取出来执行的地方是在 ViewRootImpl 的 performTraversals(),这方法是控制 View 树三大流程:测量、布局、绘制的发起者,而且可以肯定的是,这方法肯定是运行在主线程中的。

那么,根据我们分析的 ThreadLocal 原理,不同线程调用 get() 方法时数据是相互独立的,存值的时候有可能是在各种线程中,所以 Runnable 被缓存到各自的线程中去,但取值执行时却只在主线程中取,这样一来,就会造成很多缓存在其他子线程中的 Runnable 就被丢失掉了,因为取不到,自然就执行不了了。

验证方式也很简单,切到 android-24 之前的版本,然后随便在 Activity 的 onCreate() 里写段在子线程中调用 View.post(Runnable),看看这个 Runnable 会不会被执行就清楚了。

更具体的分析看那个大神的博客:通过View.post()获取View的宽高引发的两个问题

而在 android-24 版本之后,源码将这个实现改掉了,不用 ThreadLocal 来做缓存了,而是直接让各自的 View 内部去维护了,具体不展开了,感兴趣可以去看看我那篇博客和那个大神的博客。

PS:另外,不知道大伙注意到了没有,android-24 版本的源码是不是发生了什么大事,在这个版本好像改动了很多原本内部的实现,比如一开头分析的 ThreadLocal 内部实现在这个版本也改动了,上面看的 View.post() 在这个版本也改动了。

应用场景 源码中的应用场景

源码内部很多地方都有 ThreadLocal 的身影,其实这也说明了在一些场景下,使用 ThreadLocal 是可以非常方便的帮忙解决一些问题,但如果使用不当的话,可能会造成一些问题,就像上面说过的在 android-24 版本之前 View.post() 内部采用 ThreadLocal 来做缓存,如果考虑不当,可能会造成丢失一些缓存的问题。

场景1:Looper.myLooper()

用于不用线程获取各自的 Looper 的需求,具体见上文。

场景2:View.post()

android-24 版本之前用于缓存 Runnable,具体见上文。

场景3:AnimationHandler

大伙不清楚对这个熟悉不,我之前写过一篇分析 ValueAnimator 运行原理,所以有接触到这个。先看一下,它内部是如何使用 ThreadLocal 的:

//AnimationHandler.sAnimatorHandler public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>(); //AnimationHandler#getInstance() public static AnimationHandler getInstance() { if (sAnimatorHandler.get() == null) { sAnimatorHandler.set(new AnimationHandler()); } return sAnimatorHandler.get(); }

单例 + ThreadLocal? 是不是突然又感觉眼前一亮,居然可以这么用!

那么这种应用场景是什么呢,首先,单例,那么就说明只存在一个实例,希望外部只使用这么一个实例对象。然后,单例又结合了 ThreadLocal,也就是说,希望在同一个线程中实例对象只有一个,但允许不同线程有各自的单例实例对象。

而源码这里为什么需要这么使用呢,我想了下,觉得应该是这样的,个人观点,还没理清楚,不保证完全正确,仅供参考:

动画的实现肯定是需要监听 Choreographer 的每一帧 vsync 信息事件的,那么在哪里发起监听,在哪里接收回调,属性动画就则是通过一个单例类 AnimationHandler 来实现。也就是,程序中,所有的属性动画共用一个 AnimationHandler 单例来监听 Choreographer 的每一帧 vsync 信号事件。

那么 AnimationHandler 何时决定不监听了呢?不是某个动画执行结束就取消监听,而是所有的动画都执行完毕,才不会再发起监听,那么,它内部其实就维护着所有正在运行中的动画信息。所以,在一个线程中它必须也只能是单例模式。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzyfyj.html