天天看点

Android资源管理框架-------之AssetManager2总述(一)

        Android对于资源管理这个模块的折腾从Android-Lollipop开始就从未停止过:Android-Lollipop引入了Runtime Resources Overlay,但是bug比较多,多得根本不能用;Android-Marshmallow算是修正了这些bug,Runtime Resources Overlay终于可以生效了。然而,从Android-Nougat开始,到Android-Oreo,再到Android-Pie,这三个版本又对整个Android资源管理模块进行了彻彻底底的重构。比如,在Android N中引入了aapt2,将Android资源的编译分成了两个过程:编译和链接,并将之前的架构彻底推翻重建,另外,

ResourcesManager

类和

Resources

也大变样,功能增强不少;Android O中引入了AssetManager2,将AssetManager中

ResTable

相关的概念彻底废除,引入了更加直观的

ApkAssets

LoadedArsc

LoadedPackage

等概念,并且增加了OverlayManagerService等系统服务。但是在这个版本中,AssetManager2只是增加了相关代码,并未真正启用,真正使用的还是原来的AssetManager;在Android P中,java层引入了

ApkAssets

相关的接口,AssetManager2真正替换掉了原来的AssetManager;到了android-10,又引入了overlay相关的策略来增加安全控制。至此,Android资源管理模块算是完成了重构。其中AAPT2算是对AAPT的推倒重建,两者之间没有依赖关系,所以我们后面可以不用学习AAPT了,毕竟它只是一个过了时的编译工具(说实话,我还是挺喜欢aapt那种代码风格的,对aapt2的风格却不怎么喜欢);但AssetManager2和AssetManager相比,并非一个单独的模块,它本身就是继承自AssetManager的(可能是为了兼容吧),它的代码仍然和AssetManager的放在一起,它仍然使用了原来AssetManager中的不少数据结构,所以,前面我们介绍了那么多AssetManager相关的东西,还是非常有必要的。AssetManager2的代码,我们基于目前最新的Android 10来分析。

相同点

        有了前面AssetManager的基础,AssetManager2理解起来就会容易得多。其实在资源的获取方面,AssetManaager2的思路和流程与AssetManager相比,基本是一致的:根据资源id,分别得到PackageId、typeIndex、entryIndex;根据PackageId找到对应

PackageGroup

的索引,进而得到对应的

PackageGroup

;从对应的

PackageGroup

中根据typeIndex拿到该

PackageGroup

中所有包该类型的资源;遍历这些资源,根据entryIndex,从中选取最符合设备当前配置的entry;如果有资源共享库,还需要查一下

DynamicReferenceTable

,将动态引用转化为静态引用;如果有必要,还要把得到的静态引用解析一下,大概就是这么个流程。

不同点

        然后,我们重点说说AssetManager2的不同吧。首先就是代码风格了,这个我适应了很久。按道理说,同一个模块的代码,整体风格应该保持一致吧。但看看AssetManager2,再看看AssetManager,我还以为我看错了,感觉就像一个人上半身西装领带,下半身拖鞋裤衩一样,违和感太强了,哈哈。

        我们知道AssetManager在native层,有大量的代码放在

ResourceTypes.cpp(以及ResourceTypes.h)

中,AssetManager2则对这部分代码做了整理,从中抽取出了部分代码单独存放,这样结构显得清晰了一些。总体来讲,AssetManager的一些关键类比较庞大,而AssetManager2中则对它们做了拆分。

        当然,最重要的在于,AssetManager2的整体架构和AssetManager完全不一样,资源的组织方式也完全不同。在AssetManager中,资源的核心处理逻辑都是放在

ResTable

这个类中的;而在AssetManager2中,则完全废弃了这个类,而是把资源处理的核心逻辑直接放到了

AssetManager2

这个类中。同时引入了

ApkAssets

LoadedArsc

LoadedPackage

等类,来分担原来

ResTable

的功能。特别是

ApkAssets

概念的引入,它会直接影响到应用层的API。比如,Android P以及以后的版本中

AssetManager

类增加了Builder类,应用层也新增了

ApkAssets

类,用来创建

AssetManager

对象。也就是说,

ApkAssets

的概念是从应用到底层贯穿整个资源管理模块的。

新增主要文件

frameworks/base/libs/androidfw/include/androidfw/AssetManager2.h

frameworks/base/libs/androidfw/AssetManager2.cpp

AssetManager2

类是

AssetManager

的子类,可以认为相当于之前的

AssetManager

+

ResTable

,由于没有了

ResTable

类,所以

AssetManager2

做了部分

ResTable

的工作。

frameworks/base/core/java/android/content/res/ApkAssets.java

frameworks/base/libs/androidfw/include/androidfw/ApkAssets.h

frameworks/base/libs/androidfw/ApkAssets.cpp

        一个ApkAssets对应于一个APK包,

ApkAssets

关注的重点在于这个包里的resources.arsc以及这个APK包对应的idmap文件(如果有的话)。另外,ApkAssets也是AssetManager2中非常重要的概念,之前版本的资源管理模块中没有数据结构与之对应。

frameworks/base/libs/androidfw/include/androidfw/LoadedArsc.h

frameworks/base/libs/androidfw/LoadedArsc.cpp

        这两个文件中定义了两个非常重要的类

LoadedArsc

LoadedPackage

。一个

LoadedArsc

对应于一个APK中的resources.arsc文件。我们知道一个resources.arsc中只有两种数据结构,一个是Global String Pool,一个是Package,

LoadedArsc

的内容也是如此。一个

LoadedPackage

则对应于一个

LoadedArsc

中的(或者说resources.arsc中的)Package,我们可以简单认为它相当于之前版本的资源管理模块中的

ResTable::Package

frameworks/base/libs/androidfw/include/androidfw/Idmap.h

frameworks/base/libs/androidfw/Idmap.cpp

        从之前的

ResTable

中分拆出来的idmap文件解析相关的逻辑被放到了这两个文件中。

frameworks/base/libs/androidfw/include/androidfw/AttributeFinder.h

frameworks/base/libs/androidfw/include/androidfw/AttributeResolution.h

frameworks/base/libs/androidfw/AttributeResolution.cpp

        Theme和style相关,从之前的Theme相关模块中分离出来的,主要用于属性的查找和解析。

关键数据结构

// framework/base/libs/androidfw/include/androidfw/AssetManager2.h
class AssetManager2 {

    //...... 省略非关键代码
    
    //用来存储该AssetManager2已经加载的所有APK包
    std::vector<const ApkAssets*> apk_assets_;
    
    /**
     * 用来将apk_assets_分组,概念和之前的ResTable::PackageGroup一样
     * 主要还是用来处理Runtime Resources Overlay的
     * 但它内部的结构已经完全和ResTable::PackageGroup不一样了
     */
    std::vector<PackageGroup> package_groups_;

    /**
     * 叫package_ids_很容易让人费解,感觉还是叫package_map比较合适
     * 因为它和ResTable::mPackageMap的作用一模一样
     * 它的key表示APK包也就是ApkAssets的id,比如应用是0x7f,系统是0x01等
     * 它的value表示PK包也就是ApkAssets所在的PackageGroup在package_groups_中的索引
     */
    std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;

    //表示设备当前的配置信息,相当于ResTable::mParams
    ResTable_config configuration_;

    /**
     * 相当于ResTable::PackageGroup::bags,用来缓存资源的Bag
     * 它的key表示一个资源的id,比如一个style,一个array
     * 它的value 表示已经从resources.arsc中解析出来了的,该资源的所有Bag
     */
    std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
    
    /**
     * 我们知道当我们获取资源的时候,系统需要选取最符合当前配置的资源
     * 这个过程包括match、isBetterThan、isMore,isMoreSpecificThan等比较过程
     * 这个变量决定是否记录这些过程
     */
    bool resource_resolution_logging_enabled_ = false;
    
    //当resource_resolution_logging_enabled_ == true时,用来记录这个过程用的
    mutable Resolution last_resolution;
}
           

        其中最核心的是

apk_assets_

package_groups_

package_ids_

configuration_

这几个成员最为核心,它们在一定程度上代表了AssetManager2中资源的组织方式,资源的加载、查找等过程的实现,也必须依赖这几个成员。我们看到Bag资源的缓存也从

ResTable::PackageGroup

类被移动到了

AssetManager2

类。我们先来看看代表一个APK包的

ApkAssets

// framework/base/libs/androidfw/include/androidfw/ApkAssets.h
class ApkAssets {

    //...... 省略非关键代码

    //一个智能指针,表示一个压缩包,也就是我们的APK
    ZipArchivePtr zip_handle_;

    //我们的apk的路径
    const std::string path_;

    //APK的最近一次修改时间
    time_t last_mod_time_;

    //一个智能指针,表示APK中resources.arsc
    std::unique_ptr<Asset> resources_asset_;
    
    //一个智能指针,表示一个idmap文件
    std::unique_ptr<Asset> idmap_asset_;
    
    /**
     * 一个智能指针,表示一个resources.arsc
     * 和resources_asset_的区别在于,resources_asset_是表示APK中的resources.arsc文件
     * 而loaded_arsc_则是对这个文件加载解析后的一个数据结构
     */
    std::unique_ptr<const LoadedArsc> loaded_arsc_;
}
           

        一个

ApkAssets

对象包括了一个APK的全部信息,包括动态的(load到内存后的,比如

loaded_arsc_

)和静态的(文件本身的,比如代表resources.arsc以及idmap文件的Asset)。这也是AssetManager2的一种思想吧,就是所有一个类会包括与之相关的全部信息;而原来的AssetManager则是分开的,动态的信息全部放到

ResTable

中,静态的信息全部放到

ZipSet

SharedZip

等数据结构中。idmap文件虽然没有打包在overlay package中,但对于一个overlay package而言,它也是这个包本身的重要部分,所以也就一起记录在

ApkAssets

对象中了。当然,

ApkAssets

的概念在java层也同样存在,并且它还是Android Framework API的一部分,当然它只是一个空壳,具体实现还在native层,java层的

ApkAssets

我们那再以后会说,这里就不啰嗦那么多了,我们接下来看看它的重要成员

LoadedArsc

// framework/base/libs/androidfw/include/androidfw/LoadedArsc.h
class LoadedArsc {

    //...... 省略非关键代码

    //Global String Pool
    ResStringPool global_string_pool_;

    //packages,对应resources.arsc中的类型为RES_TABLE_PACKAGE_TYPE的chunk
    std::vector<std::unique_ptr<const LoadedPackage>> packages_;

    //是否是系统资源
    bool system_ = false;
}
           

LoadedArsc

非常简单,主要就是一个Global String Pool和多个Package,这和resources.arsc中的内容是一致的。需要说明的是,我们看到从Android Lollipop版本(更早的没确认过)开始,系统就是支持一个resources.arsc中存在多个Package的,但是到现在也并没有哪个应用或者某个系统模块使用了这一特性。至少我还没有见到过哪个resources.arsc是1个Global String Pool + N个package的模式,大家都是一个1个Global String Pool + 1个package。所以,这个特性还是有待充分开发的。当然,难度比较大,因为它涉及到了打包的具体细节,可能还要修改aapt2。

// framework/base/libs/androidfw/include/androidfw/LoadedArsc.h
class LoadedPackage {

    //...... 省略非关键代码

    //Type String Pool
    ResStringPool type_string_pool_;

    //key String Pool
    ResStringPool key_string_pool_;

    //package name 
    std::string package_name_;

    //pacakge id
    int package_id_ = -1;

    /**
     * type Id的偏移量,需要说明的是
     * type Id是从1开始的,如果type_id_offset_的值为2
     * 那么type Id将会从3开始
     */ 
    int type_id_offset_ = 0;

    //该package是否为资源共享库
    bool dynamic_ = false;

    //该package是否为系统资源库
    bool system_ = false;

    //该package是否为overlay pacakge
    bool overlay_ = false;

    //该pacakge是否定义了overlay policy信息
    bool defines_overlayable_ = false;

    /**
     * 对应于resources.arsc中的TypeSpec
     * using TypeSpecPtr = util::unique_cptr<TypeSpec>;
     */
    ByteBucketArray<TypeSpecPtr> type_specs_;

    //仅作遍历时时使用,可以无视
    ByteBucketArray<uint32_t> resource_ids_;
 
    /**
     * 这个package所引用的资源共享库
     * DynamicPackageEntry就两个成员
     * 一个是资源共享库的name,
     * 一个是资源共享库的id,需要注意的是,这个id是本package编译时
     * 给资源共享库临时分配的id,在运行时由于加载顺序的关系,同一个资源共享库的id可能会
     * 和编译时不一样,所以这中间要有一个转换的过程,这就是DynamicReferenceTable的主要作用
     */
    std::vector<DynamicPackageEntry> dynamic_package_map_;

    //后面的这两个成员都和RRO策略有关,到时候我们会详细说明,这里可以不用太在意
    /**
     * vector中的每一个元素表示一种策略,比如这个package允许相同签名的overlay package
     * 覆盖的所有资源算是一个策略;又比如这个package允许/product分区的overlay package
     * 覆盖的所有资源又是一个策略。std::unordered_set<uint32_t>表是这个策略允许覆盖的所有
     * 资源的id
     */
    std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
    std::unordered_map<std::string, std::string> overlayable_map_;
}
           

AssetManager2

中还有一个非常重要的结构:

AssetManager2::PackageGroup

,它的概念和

ResTable::PackageGroup

相同,都是为了存储RRO中的target package和overlay package而提出的。不过,它的实现和以前已经有了不同:

// framework/base/libs/androidfw/include/androidfw/AssetManager2.h

/**
 * 这个结构体可以认为是一个缓存,为了加快获取资源的速度
 * 代表一个ResTable_typeSpec中,符合设备当前配置的所有ResTable_type
 * 这里的符合表示ResTable_config的match方法返回true
 */
struct FilteredConfigGroup {
    //该ResTable_typeSpec中符合设备当前配置的所有的config
    std::vector<ResTable_config> configurations;
    ///该ResTable_typeSpec中符合设备当前配置的所有的ResTable_type
    std::vector<const ResTable_type*> types;
};

/**
 * 这个结构体我们可以简单理解为LoadedPackage + 缓存
 */
struct ConfiguredPackage {
    // A pointer to the immutable, loaded package info.
    const LoadedPackage* loaded_package_;

    // A mutable AssetManager-specific list of configurations that match the AssetManager's
    // current configuration. This is used as an optimization to avoid checking every single
    // candidate configuration when looking up resources.
    /**
     * 我们在获取资源的时候,要根据设备的当前配置信息,去选择最合适的资源项
     * 这个过程我们之前讲过,要经过match、isBetterThan、isMoreSpecificThan
     * 等比较的过程。现在为了加快获取资源的速度,在加载完资源后,系统就会先选出match
     * 设备当前配置的资源,存放在filtered_configs_中。当我们获取资源的时候,就可以
     * 跳过这个步骤了。filtered_configs_中的每一项代表一个ResTable_typeSpec中
     * 符合设备当前配置的所有ResTable_type
     */
    ByteBucketArray<FilteredConfigGroup> filtered_configs_;
}

//代表一个PackageGroup
struct PackageGroup {
    // The set of packages that make-up this group.
    /**
     * PackageGroup中的所有package(我们可以简单认为就是LoadedPackage对象)
     * 换句话说就是target package 和它的所有overlay package
     * 如果一个Package没有overlay package,那么它应该独占一个PackageGroup
     */
    std::vector<ConfiguredPackage> packages_;

    // The cookies associated with each package in the group. They share the same order as
    // packages_.
    /**
     * ApkAssetsCookie就是一个32位的int值
     * cookies 表示PackageGroup中的所有package(我们可以简单认为就是LoadedPackage对象)
     * 所在的ApkAssets对象在AssetManager2::apk_assets_中的索引
     * 这里需要注意的是,一个ApkAssets对象中是有可能包含多个LoadedPackage对象的
     */
    std::vector<ApkAssetsCookie> cookies_;

    // A library reference table that contains build-package ID to runtime-package ID mappings.
    //DynamicRefTable,就是ResTable::DynamicRefTable.用来保存
    //资源共享库的编译时id和运行时id的映射关系
    DynamicRefTable dynamic_ref_table;
};
           

        我们看到

AssetManager2::PackageGroup

ResTable::PackageGroup

相比,概念还是原来的概念,不过实现上有了一些变动:把对Bag资源的缓存移动到了

AssetManager2::cached_bags_

中,另外为了加快资源的查找速度,

AssetManager2::PackageGroup

还会提前缓存好符合设备当前配置的资源信息。另外,如果一个resources.arsc中包含了多个package,也就是一个

LoadedArsc

中包含了多个

LoadedPackage

的话,那么这些

LoadedPackage

虽然不一定会被分到同一个

AssetManager2::PackageGroup

中,但它们的cookie值会是相同的,毕竟它们来自同一个资源包。可能这句话不太好理解,不过没关系,在后面的文章中我们会详细解释的。最后我们看一下AssetManager2中主要的数据结构和resources.arsc中主要数据结构的对应关系:

Android资源管理框架-------之AssetManager2总述(一)

        图片比较大,建议点击一下,放大了看,这样比较清晰。我们对这个图稍作说明:

  • 图中最右面的那个表示一个资源包(我们可以简单理解为一个APK啦,dex什么的请无视)中的主要数据结构
  • 剩下的数据结构都是AssetManager2中为了方便资源管理而定义的
  • PackageGroup::cookies

    AssetManager2::configuration_

    ApkAssets::idmap_asset_

    三个变量我们没有画出其内部的数据结构图。其中

    PackageGroup::cookies

    AssetManager2::configuration_

    一个表示资源包的索引值,一个表示设备当前的配置信息,很好理解,我们不再详说;

    ApkAssets::idmap_asset_

    对应这个资源包的idmap文件(这个时候这个资源包是一个overlay package,idmap文件位于/data/resource-cache/目录下),我们后面介绍RRO的时候会详说,这里也不再详述。
  • 一个

    AssetManager2

    中包含多个

    ApkAssets

  • 一个

    ApkAssets

    对应一个资源包(也就是APK啦),并且它内部会有一个

    LoadedArsc

  • 一个

    LoadedArsc

    对应一个资源包中的resources.arsc文件,它内部会有一个Global String Pool,对应于resources.arsc中的Global String Pool;它内部还会有一个或者多个

    LoadedPackage

  • 一个

    LoadedPackage

    对应于resources.arsc中的一个

    ResTable_package

    ,它内部会有一个Type String Pool、一个Key String Pool、N个typeSpec、一个记录其引用的资源共享库的数据结构(

    dynamic_package_map_

    )、一些RRO策略相关的数据结构(

    overlayable_infos_

    等),它们都对应于resources.arsc中相应的数据段。
  • 一个

    AssetManager2

    中还会包含多个

    PackageGroup

    对象。

    PackageGroup

    在resources.arsc中并没有数据结构与其对应,它是Android为了方便管理RRO中的target package 和它所有overlay packages而定义的一个结构体。target package 和它所有overlay packages会被放到同一个

    PackageGroup

    中,它里面会有多个

    ConfiguredPackage

    ,而每一个

    ConfiguredPackage

    中都会有一个

    LoadedPackage

    对象作为成员。也就是说,

    PackageGroup

    LoadedPackage

    除了

    LoadedArsc

    –>

    ApkAssets

    外的另外一种组织形式,如图中的红色虚线箭头。我们可以认为图中的橙色箭头+黑色箭头是一种组织形式;红色虚箭头+ 黑箭头是另外一种组织形式。当然,

    PackageGroup

    中还会记录target package引用的资源共享库的编译时索引和运行时索引之间的映射关系,也就是

    PackageGroup::dynamic_ref_table

    。它的生成需要依赖target package中的

    dynamic_package_map_

    ,自然也就会依赖resources.arsc中的

    ResTable_lib_header

    等数据结构。