本文正在参加「金石计划 . 瓜分6万现金大奖」
Hi ,你好 :)
导言
在上一篇,求知 | 聊聊Android资源加载的那些事 – 小试牛刀 中,咱们经过讨论 Resource.getx()
等办法,然后解说了相关办法的背面完成。
那么,不知道你有没有猎奇 context.resources
与 Resource.getSystem()
有什么不同呢?前者又是在什么时候被初始化的呢?
假如你对上述问题仍然存疑,或许你想在杂乱中找到一个较清晰的头绪,那本文或许会对你有所协助。本篇将与你一起讨论关于 Resources
初始化的那些事。
本篇定位中等,主要经过伪源码的方式探究
Resources
的初始化过程targetSdk: 31
导航
学完本篇,你将明白以下内容:
-
Resource(Activity)
、Resource(App)
初始化流程 -
Context.resource
与Resource.getSystem()
的不同之处
根底概念
开端本篇前,咱们先了解一些必备的根底概念:
-
ActivityResource
用于持有
Activity
或许WindowContext
相相关的Resources
; -
ActivityResources
用于办理
Acitivty
的config
和其一切ActivityResource
,以及当时正在显现的屏幕id; -
ResourceManager
用于办理
App
一切的resources
,内部有一个mActivityResourceReferences
map保存着一切activity
或许windowsToken
对应的Resources
目标。
Resource(Activity)
在 Activity
中调用 getX() 相关办法时,点进源码不难发现,内部都是调用的 getResource().x ,而 getResource()
又是来自 Context
,所以一切的源头也即从这里开端。
了解 context
的小伙伴应该有印象, context
作为一个顶级抽象类,无论是 Activity
还是 Application
都是其的子类, Context
的完成类又是 ContextImpl
,所以当咱们要找 Activity
中 resource
在哪里被初始化时,也即是在找:
->
ContextImpl.resource
在哪里被初始化? ➡️
顺藤摸瓜,咱们去看看 ContextImpl.createActivityContext()
。
该办法的调用时机是在构建咱们 Activity
之前调用,意图是用于创立 context
实例。
流程剖析
详细如下:
ContextImpl.createActivityContext ->
上述总结如下:
内部会获取当时的 分辨率 、
classLoader
等装备信息,并调用ResourcesManager.getInstance()
然后获取ResourcesManager
的单例目标,然后运用其的createBaseTokenResources()
去创立终究的Resources
。接着 将resource目标保存到
context
中,即赋值给ContextImpl.mResources
。ps: 假如 sdk>=26 ,还会做
CompatResources
的判别。
了解了上述流程,咱们接着去看 resourcesManager.createBaseTokenResources
() 。
ResourceManager.createBaseTokenResources()
上述总结如下:
该办法用于创立当时
activity
相对应的resources
,内部会阅历如下过程:
先查找或创立当时 token(activity) 所对应的
resources
;Yes -> 什么都不做;
No -> 创立一个
ActivityResources
,并将其添加到mActivityResourceReferences
map中;接着再去更新该
activity
对应resources
(内部会再次履行第一步);再次查找当时的
resources
,假如找到,则直接回来;假如找不到,则从头创立一个
resources
(内部又会再履行第一步);
详细的过程如下所示:
-1. getOrCreateActivityResourcesStructLocked()
ResourcesManager.getOrCreateActivityResourcesStructLocked()
private ActivityResources getOrCreateActivityResourcesStructLocked(
IBinder activityToken) {
// 先从map获取
ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
// 不存在,则创立新的,并以token为key保存到map中,并回来新创立的ActivityResources
if (activityResources == null) {
activityResources = new ActivityResources();
mActivityResourceReferences.put(activityToken, activityResources);
}
return activityResources;
}
如题所示,获取或创立 ActivityResources
。假如存在则回来,不然创立并保存到 ResourcesManager.mActivityResourceReferences中。
-2. updateResourcesForActivity()
ResourcesManager.updateResourcesForActivity()
流程如下:
内部会先获取当时
activity
对应的resources
(假如不存在,则创立),假如当时传入的装备与之前共同,则直接回来。
不然先运用当时 activity
对应的装备 创立一个 [旧]装备目标,接着去更新该 activity
一切的 resources
详细完成类impl
。每次更新时会先与从前的装备进行差异更新并回来新的 ReourcesKey
,并运用这个 key
获取其对应的 impl
(假如没有则创立),获取到的 resource
完成类 impl
假如与当时的不共同,则更新当时 resources
的 impl
。
-3. findResourcesForActivityLocked()
ResourcesManager.findResourcesForActivityLocked()
流程如下:
当经过 findResourcesForActivityLocked()
获取指定的 resources
时,内部会先获取当时 token
对应的 activityResources
,然后拿到其一切的 resources
;然后遍历一切 resources
,假如某个 resouces
对应的 key(ResourcesKey) 与当时查找的共同而且符合其他规则,则直接回来,不然无符合条件时回来null。
–4.createResourcesForActivity()
ResourcesManager.createResourcesForActivity()
流程如下:
在创立
Resources
时,内部会先运用key
查找相应的ResourcesImpl
,假如没找到,则直接回来null,不然调用createResourcesForActivityLocked()
创立新的Resources
.
总结
当咱们在 Activity
、Fragment
中调用 getX()
相关办法时,由于 context
仅仅一个署理,提供了获取 Resources
的 getx()
办法,详细完成在 ContextImpl
。所以在咱们的 Activity
被创立之前,会先创立 contextImpl
,然后调用 createActivityContext()
这个办法内部完成了对 resources
的初始化。内部会先拿到 ResourcesManager
(用于办理咱们一切resources),然后调用其的createBaseTokenResources()
去创立所需要的 resources
,然后将其赋值给 contextImpl
。
在详细的创立过程中分为如下几步:
- 先从
ResourcesManager
缓存 (mActivityResourceReferences) 中去找当时 token(Ibinder) 所对应的ActivityResources
,假如没找到则从头创立一个,并将其添加到map
中; - 接着再去更新当时
token
所相关ActivityResources
内部(activityResource)一切的resources,假如现有的装备参数与当时要更新的共同,则越过更新,不然遍历更新一切resources
; - 再去获取所需要的
resources
,假如找到则回来,不然开端创立新的resources
; - 内部会先去获取
ResourcesImpl
,假如不存在则会创立一个新的,然后带上一切装备以及 token 去创立相应的resources
,内部也同样会履行一遍第一步,然后再创立ActivityResource
,并将其添加到第一步创立的activityResouces
中。
Resrouces(Application)
Application
等级的,咱们应该从哪里找进口呢?
既然是 Application
等级,那就找找 Application
什么时候初始化?而 Resources
来自 Context
,所以咱们要寻觅的方位又天然是 ContextImpl
了。故此,咱们去看看
->
ContexntImpl.createSystemContext()
该办法用于创立 App
的第一个上下文目标,即也就是 AppContext
。
流程剖析
ContexntImpl.createSystemContext()
fun createSystemContext(mainThread:ActivityThread) {
// 创立和体系包有关的资源信息
val packageInfo = LoadedApk(mainThread)
...
val context = ContextImpl(xxx)
➡️
context.setResources(packageInfo.getResources())
...
return context
}
如上所示,当创立体系 Context
时,会先初始化一个 LoadedApk
,用于办理咱们体系包相关信息,然后再创立 ContextImpl
,然后调用创立好的 LoadedApk
的 getResources()
办法获取体系资源目标,并将其设置给咱们的 ContextImpl
。
➡️ LoadedApk.getResources()
当咱们获取 resources
时,内部会先判别是否存在,假如不存在,则调用 ResourcesManager.getResources()
去获取新的 resources
并回来,不然直接回来现有的。相应的,咱们再去看看 ResourcesManager.getResources()
。
➡️➡️ ResourcesManager.getResources()
如上所示,内部会对传入的 activityToken
进行判别,假如为 不为 null ,则调用 createResourceForActivity()
去创立;不然调用 createResources()
去创立,详细内部的逻辑和最开端相似,内部会先运用 key
查找相应的 ResourcesImpl
,假如没找到,则分别调用相关办法再去创立 Resources
。
关于 createResourceLocked()
,咱们再看一眼,如下所示:
这个办法内部创立了一个新的
resources
, 终究将其add到了ResourcesManager.mResourceReferences
这个List中,以便复用。
总结
当咱们的 App
启动后,初始化 Application
时,会调用到 ContexntImpl.createSystemContext()
,该办法内部一起也会完成对咱们Resources
的初始化。内部流程如下:
- 先初始化
LoadedApk
目标(其用于办理app的信息),再调用其的getResources()
办法获取详细的Resources
; - 在上述办法内部,会先判别当时
resources
是否为 null。 假如为null,则运用ResourcesManager.getResources()
去获取,由于这是application
的初始化,所以不存在activityToken
,故内部会直接调用ResourceManager.createResource()
办法,内部会创立一个新的Resources
并将其添加到mResourceReferences
缓存中。
Resources(System)
我们都应该见过这样的代码,比方 Resources.getSystem().getX()
, 而他内部的完成也十分简单,如下所示:
Tips
当咱们运用 Resources.getSystem()
时,其实也就是在调用当时 framework
层的资源目标,内部会先判别是否为 null,然后进行初始化,初始化的过程中,由于体系结构层的资源,所以实践的资源办理器直接调用了 AssetManager.getSystem()
,这个办法内部会运用当时体系结构层的apk作为资源路径。所以咱们天然也无法用它去加载咱们 Apk
内部的资源文件。
小问题
在了解了上述流程后,假如你存在以下问题(就是这么倔强),那么无妨鼓舞鼓舞自己,[你没掉队]!
- 为什么要存在 ActivityResources 与 ActivityResource ? 咱们一直调用的不都是Resources吗?
首先说说
ActivityResource
,见名知意,它是作为Resources
的包装类型呈现,内部持有当时要加载的装备,以及真实的Resources
,以便装备变更时更新 resources。又由于一个
Activity
或许相关多个Resources
,所以ActivityResources
是一个activity
(或许windowsContext
) 的一切resources
合集,内部用一个List保护,而ActivityResources
又被ResourcesManager
缓存着。当咱们每次初始化Act时,内部都会创立相应的
ActResources
,并将其添加到manager中作为缓存。终究的resources 仅仅一个署理目标,然后供开发者调用, 真实的完成者ResourcesImpl
则被全局缓存。
- Resources.getSystem() 获取运用drawable,为什么会报错?
原因也很简单啊,由于
resources
相应的AssetManager
对应的资源路径时frameWork
啊,你让它获取当时运用资源,它不造啊。
结语
终究,让咱们反推上去,总体再来回忆一下 Resources
初始化的相关:
- 本来咱们的
resources
都是在context
创立时初始化,而且咱们所调用的resources
实践上被ActivityResource
所包装; - 本来咱们的
Resources
仅仅一个署理,终究的调用其实是ResourcesImpl
,而且被ResourcesManager
所缓存。 - 本来每逢咱们初始化一个
Activity
,咱们一切的resources
都会被刷新,为什么呢,由于咱们的 config 装备或许会改变,比方深色形式切换等。 - 本来
Resource.getSystem()
无法加载运用资源的原因仅仅由于AssetManager
对应的资源路径是frameWork.apk
。
本篇中,咱们专心于一个概念,即:resources 到底从何而来,而且从原理上剖析了不同context
resources
的初始化流程,也明白了他们之间的差异与差异。仔细的小伙伴会发现,从上一篇,咱们从运用层
Resources.getx()
开端,到现在Resources
初始化。咱们沿着开发者的运用习惯由浅入深,去探究底层规划,逐渐理清 Android Resources 的全体头绪。下一篇我将同我们剖析
ResourcesManager
,而且解说许多为什么,然后探究其背面的规划思想 :)
关于我
我是 Petterp ,一个 Android工程师 ,假如本文对你有所协助,欢迎点赞支撑,你的支撑是我持续创作的最大鼓舞!