本文正在参加「金石计划 . 瓜分6万现金大奖」

Hi ,你好 :)

导言

在上一篇,求知 | 聊聊Android资源加载的那些事 – 小试牛刀 中,咱们经过讨论 Resource.getx() 等办法,然后解说了相关办法的背面完成。

那么,不知道你有没有猎奇 context.resourcesResource.getSystem() 有什么不同呢?前者又是在什么时候被初始化的呢?

假如你对上述问题仍然存疑,或许你想在杂乱中找到一个较清晰的头绪,那本文或许会对你有所协助。本篇将与你一起讨论关于 Resources 初始化的那些事。

本篇定位中等,主要经过伪源码的方式探究 Resources 的初始化过程

targetSdk: 31

导航

学完本篇,你将明白以下内容:

  • Resource(Activity)Resource(App) 初始化流程
  • Context.resourceResource.getSystem() 的不同之处

根底概念

开端本篇前,咱们先了解一些必备的根底概念:

  • ActivityResource

    用于持有 Activity 或许 WindowContext 相相关的 Resources

  • ActivityResources

    用于办理 Acitivtyconfig 和其一切 ActivityResource ,以及当时正在显现的屏幕id;

  • ResourceManager

    用于办理 App 一切的 resources,内部有一个 mActivityResourceReferences map保存着一切 activity 或许 windowsToken 对应的 Resources 目标。

Resource(Activity)

Activity 中调用 getX() 相关办法时,点进源码不难发现,内部都是调用的 getResource().x ,而 getResource() 又是来自 Context ,所以一切的源头也即从这里开端。

了解 context 的小伙伴应该有印象, context 作为一个顶级抽象类,无论是 Activity 还是 Application 都是其的子类, Context 的完成类又是 ContextImpl,所以当咱们要找 Activityresource 在哪里被初始化时,也即是在找:

-> ContextImpl.resource 在哪里被初始化? ➡️

顺藤摸瓜,咱们去看看 ContextImpl.createActivityContext()

该办法的调用时机是在构建咱们 Activity 之前调用,意图是用于创立 context 实例。

流程剖析

详细如下:

ContextImpl.createActivityContext ->

求知 | 聊聊Android资源加载那些事 - Resource的初始化

上述总结如下:

内部会获取当时的 分辨率classLoader 等装备信息,并调用 ResourcesManager.getInstance() 然后获取 ResourcesManager 的单例目标,然后运用其的 createBaseTokenResources() 去创立终究的 Resources

接着 将resource目标保存到 context 中,即赋值给 ContextImpl.mResources

ps: 假如 sdk>=26 ,还会做 CompatResources 的判别。

了解了上述流程,咱们接着去看 resourcesManager.createBaseTokenResources() 。


ResourceManager.createBaseTokenResources()

求知 | 聊聊Android资源加载那些事 - Resource的初始化

上述总结如下:

该办法用于创立当时 activity 相对应的 resources ,内部会阅历如下过程:

  1. 先查找或创立当时 token(activity) 所对应的 resources

    Yes -> 什么都不做;

    No -> 创立一个 ActivityResources ,并将其添加到 mActivityResourceReferences map中;

  2. 接着再去更新该 activity 对应 resources(内部会再次履行第一步);

  3. 再次查找当时的 resources ,假如找到,则直接回来;

  4. 假如找不到,则从头创立一个 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()

求知 | 聊聊Android资源加载那些事 - Resource的初始化

流程如下:

内部会先获取当时 activity 对应的 resources(假如不存在,则创立),假如当时传入的装备与之前共同,则直接回来

不然先运用当时 activity 对应的装备 创立一个 [旧]装备目标,接着去更新该 activity 一切的 resources 详细完成类impl。每次更新时会先与从前的装备进行差异更新并回来新的 ReourcesKey ,并运用这个 key 获取其对应的 impl (假如没有则创立),获取到的 resource 完成类 impl 假如与当时的不共同,则更新当时 resourcesimpl

-3. findResourcesForActivityLocked()

ResourcesManager.findResourcesForActivityLocked()

求知 | 聊聊Android资源加载那些事 - Resource的初始化

流程如下:

当经过 findResourcesForActivityLocked() 获取指定的 resources 时,内部会先获取当时 token 对应的 activityResources ,然后拿到其一切的 resources ;然后遍历一切 resources ,假如某个 resouces 对应的 key(ResourcesKey) 与当时查找的共同而且符合其他规则,则直接回来,不然无符合条件时回来null。

–4.createResourcesForActivity()

ResourcesManager.createResourcesForActivity()

求知 | 聊聊Android资源加载那些事 - Resource的初始化
流程如下:

在创立 Resources 时,内部会先运用 key 查找相应的 ResourcesImpl ,假如没找到,则直接回来null,不然调用 createResourcesForActivityLocked() 创立新的 Resources.

总结

当咱们在 ActivityFragment 中调用 getX() 相关办法时,由于 context 仅仅一个署理,提供了获取 Resourcesgetx() 办法,详细完成在 ContextImpl。所以在咱们的 Activity 被创立之前,会先创立 contextImpl,然后调用 createActivityContext() 这个办法内部完成了对 resources 的初始化。内部会先拿到 ResourcesManager(用于办理咱们一切resources),然后调用其的createBaseTokenResources() 去创立所需要的 resources ,然后将其赋值给 contextImpl

在详细的创立过程中分为如下几步:

  1. 先从 ResourcesManager 缓存 (mActivityResourceReferences) 中去找当时 token(Ibinder) 所对应的 ActivityResources,假如没找到则从头创立一个,并将其添加到 map 中;
  2. 接着再去更新当时 token 所相关 ActivityResources 内部(activityResource)一切的resources,假如现有的装备参数与当时要更新的共同,则越过更新,不然遍历更新一切 resources;
  3. 再去获取所需要的 resources ,假如找到则回来,不然开端创立新的 resources
  4. 内部会先去获取 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 ,然后调用创立好的 LoadedApkgetResources() 办法获取体系资源目标,并将其设置给咱们的 ContextImpl

➡️ LoadedApk.getResources()

求知 | 聊聊Android资源加载那些事 - Resource的初始化

当咱们获取 resources 时,内部会先判别是否存在,假如不存在,则调用 ResourcesManager.getResources() 去获取新的 resources 并回来,不然直接回来现有的。相应的,咱们再去看看 ResourcesManager.getResources()

➡️➡️ ResourcesManager.getResources()

求知 | 聊聊Android资源加载那些事 - Resource的初始化

如上所示,内部会对传入的 activityToken 进行判别,假如为 不为 null ,则调用 createResourceForActivity() 去创立;不然调用 createResources() 去创立,详细内部的逻辑和最开端相似,内部会先运用 key 查找相应的 ResourcesImpl ,假如没找到,则分别调用相关办法再去创立 Resources

关于 createResourceLocked() ,咱们再看一眼,如下所示:

求知 | 聊聊Android资源加载那些事 - Resource的初始化
这个办法内部创立了一个新的 resources , 终究将其add到了 ResourcesManager.mResourceReferences 这个List中,以便复用。

总结

当咱们的 App 启动后,初始化 Application 时,会调用到 ContexntImpl.createSystemContext() ,该办法内部一起也会完成对咱们Resources 的初始化。内部流程如下:

  1. 先初始化 LoadedApk 目标(其用于办理app的信息),再调用其的 getResources() 办法获取详细的 Resources
  2. 在上述办法内部,会先判别当时 resources 是否为 null。 假如为null,则运用 ResourcesManager.getResources() 去获取,由于这是 application 的初始化,所以不存在 activityToken ,故内部会直接调用 ResourceManager.createResource() 办法,内部会创立一个新的 Resources 并将其添加到 mResourceReferences 缓存中。

Resources(System)

我们都应该见过这样的代码,比方 Resources.getSystem().getX() , 而他内部的完成也十分简单,如下所示:

求知 | 聊聊Android资源加载那些事 - Resource的初始化

Tips

当咱们运用 Resources.getSystem() 时,其实也就是在调用当时 framework 层的资源目标,内部会先判别是否为 null,然后进行初始化,初始化的过程中,由于体系结构层的资源,所以实践的资源办理器直接调用了 AssetManager.getSystem() ,这个办法内部会运用当时体系结构层的apk作为资源路径。所以咱们天然也无法用它去加载咱们 Apk 内部的资源文件。

小问题

在了解了上述流程后,假如你存在以下问题(就是这么倔强),那么无妨鼓舞鼓舞自己,[你没掉队]!

  1. 为什么要存在 ActivityResources 与 ActivityResource ? 咱们一直调用的不都是Resources吗?

求知 | 聊聊Android资源加载那些事 - Resource的初始化

首先说说 ActivityResource ,见名知意,它是作为 Resources 的包装类型呈现,内部持有当时要加载的装备,以及真实的 Resources ,以便装备变更时更新 resources。

又由于一个 Activity 或许相关多个 Resources ,所以 ActivityResources 是一个 activity(或许windowsContext) 的一切 resources 合集,内部用一个List保护,而 ActivityResources 又被 ResourcesManager 缓存着。

当咱们每次初始化Act时,内部都会创立相应的 ActResources ,并将其添加到manager中作为缓存。终究的resources 仅仅一个署理目标,然后供开发者调用, 真实的完成者 ResourcesImpl 则被全局缓存。

  1. 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工程师 ,假如本文对你有所协助,欢迎点赞支撑,你的支撑是我持续创作的最大鼓舞!