深入Android系统(八)Android的资源管理
Android的优势之一是它几乎能运行在任何尺寸的设备上,为了能让同一个apk在不同设备上正常运行,Android设计了一套资源管理系统来完成目标。
Android
并不是简单地将UI布局和图片进行扩大和缩小来匹配不同配置的设备,而是通过复杂的资源定义方式来保证每种设备都可以有对应的资源文件,从而让用户体验最佳。
资源系统简介
Android
应用适应不同设备的方法是尽量为每种类型的设备提供一套资源。理论上虽然可以这样做,但实际上却行不通,我们只能为常见的几种设备类型提供完整的资源,否则应用的占用空间会膨胀到无法接受的程度
常用术语和单位
大部分来自官网,应用资源概览
Android
中常用的单位:
dpi
:屏幕密度,即每英寸的像素点数。160 dpi
表示屏幕每英寸包含160个像素点px
:像素,1 px
表示一个物理的像素点。px
不被推荐使用,但是如果需要通过像素点来控制UI
,也可以使用。dp
:dp
是一个虚拟像素单位,1 dp
约等于中密度
屏幕(160dpi
;“基准”密度)上的1像素
。对于其他每个密度,Android
会将此值转换为相应的实际像素数。sp
:sp
多用于表示字体大小上。sp
和dp
概念相似。区别是Android
在系统配置中定义一个scale
值,sp
和dp
的换算关系是sp=dp*scale
,通常scale
值为1
。(官方说法:默认情况下,sp
单位与dp
大小相同,但它会根据用户的首选文本大小来调整大小。)
Android
把屏幕尺寸归为4类:
small
:尺寸类似于低密度 VGA 屏幕的屏幕。小屏幕的最小布局尺寸约为320x426 dp
。例如,QVGA 低密度屏幕和 VGA 高密度屏幕。normal
:尺寸类似于中等密度 HVGA 屏幕的屏幕。标准屏幕的最小布局尺寸约为 320x470 dp。例如,WQVGA 低密度屏幕、HVGA 中等密度屏幕、WVGA 高密度屏幕。large
:尺寸类似于中等密度 VGA 屏幕的屏幕。大屏幕的最小布局尺寸约为 480x640 dp。例如,VGA 和 WVGA 中等密度屏幕。xlarge
:明显大于传统中等密度 HVGA 屏幕的屏幕。超大屏幕的最小布局尺寸约为 720x960 dp。在大多数情况下,屏幕超大的设备体积太大,不能放进口袋,最常见的是平板式设备。此项为 API 级别 9 中的新增配置。
Android
把屏幕密度(dpi
)分为:
ldpi
:低密度屏幕;约为 120dpi。mdpi
:中等密度(传统 HVGA)屏幕;约为 160dpi。hdpi
:高密度屏幕;约为 240dpi。xhdpi
:超高密度屏幕;约为 320dpi。此项为 API 级别 8 中的新增配置xxhdpi
:绝高密度屏幕;约为 480dpi。此项为 API 级别 16 中的新增配置xxxhdpi
:极高密度屏幕使用(仅限启动器图标);约为 640dpi。此项为 API 级别 18 中的新增配置nodpi
:可用于您不希望为匹配设备密度而进行缩放的位图资源。tvdpi
:密度介于 mdpi 和 hdpi 之间的屏幕;约为 213dpi。此限定符并非指“基本”密度的屏幕。它主要用于电视,且大多数应用都不使用该密度 — 大多数应用只会使用 mdpi 和 hdpi 资源,而且系统将根据需要对这些资源进行缩放。anydpi
:此限定符适合所有屏幕密度,其优先级高于其他限定符。这非常适用于矢量可绘制对象
。此项为 API 级别 21 中的新增配置
系统资源定义
对于下面的Layout
资源定义:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical">
</LinearLayout>
上面的定义中有各种各样的符号,例如LinearLayout
、layout_width
、orientation
等,这些符号在哪里定义的呢?语法规则优势什么呢?
Android
的资源系统并不想看上去那么简单,Android
利用了xml
定义了一套完整的资源语言,我们来具体看下
定义属性
在Framework
资源目录frameworks/base/core/res/res/values
的目录下,有一个attrs.xml
文件,在这个文件里,Android
定义了资源的属性值,以LinearLayout
为例,相关的定义如下:
<declare-styleable name="LinearLayout"><attr name="orientation" /><attr name="gravity" /><attr name="baselineAligned" format="boolean" /><attr name="baselineAlignedChildIndex" format="integer" min="0"/><attr name="weightSum" format="float" /><attr name="measureWithLargestChild" format="boolean" /><attr name="divider" /><attr name="showDividers"><flag name="none" value="0" /><flag name="beginning" value="1" /><flag name="middle" value="2" /><flag name="end" value="4" /></attr><attr name="dividerPadding" format="dimension" /></declare-styleable>
从文件中不难看出,LinearLayout
中所使用的各种属性正是在这里定义的。不但定义了属性的名称,还有属性值的格式也做了定义
对于UI的基本元素widget而言,最重要的是获得预先定义好的各种属性值。于是在attrs.xml
文件中,通过declare-styleable
的方式定义了一套属性的集合。我们再看下LinearLayout
类的构造方法:
public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);......final TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);if (index >= 0) {setOrientation(index);}......a.recycle();}
- 在构造函数中,
LinearLayout
调用方法obtainStyledAttributes()
来创建一个属性集合 a.getInt()
用来获取属性- 第一个参数是
com.android.internal.R.styleable.LinearLayout
,正是attrs.xml
文件中定义的属性集合名称 - 还有一个参数是缺省值
-1
,如果在编写layout文件时必须给每个widget的所有属性都赋值,这会变得很繁琐,因此在读取每个属性时,代码中都会给出一个缺省值
- 第一个参数是
给属性赋值
LinearLayout
的构造方法中有一个defStyleAttr
和defStyleRes
用来指定使用的style
,style
就是一些预定义的属性值,例如:
<style name="Widget.Button"><item name="background">@drawable/btn_default</item><item name="focusable">true</item><item name="clickable">true</item><item name="textAppearance">?attr/textAppearanceSmallInverse</item><item name="textColor">@color/primary_text_light</item><item name="gravity">center_vertical|center_horizontal</item></style>
这段xml
定义了一个名为Widget.Button
的style
。使用style
的方式是在layout定义中加入下面的语句:
style="@style/Widget.Button"
对于style
的命名,为什么要用.
分割呢?Android
用这种方式表示一种继承关系。Widget.Button
的含义是当前style
继承了Widget
的所有属性值
除了通过名字来表示继承关系外,还可以通过<style/>
标签的parent
属性来指定,如:
<style name="Widget.PopupMenu" parent="Widget.ListPopupWindow"></style>
主题Theme
theme
就是所有UI
属性的集合。作为基础定义的Theme
本身是一个很庞大的style
,而其他theme
基本上都是通过Theme
派生出来的,我们看下frameworks/base/core/res/res/values/themes.xml
文件定义:
<style name="Theme"><item name="isLightTheme">false</item><item name="colorForeground">@color/bright_foreground_dark</item><item name="colorForegroundInverse">@color/bright_foreground_dark_inverse</item>......</style><!-- Variant of {@link #Theme} with no title bar --><style name="Theme.NoTitleBar"><item name="windowNoTitle">true</item></style>
资源类型
在Android应用的源码目录下,通常有两个和资源相关的目录:
assets
和res
目录。这两个目录下的文件都会被打包进APK
文件中
Android
规定各类资源存放在res/
目录,目录中支持的资源目录表如下:
目录 | 资源类型 |
---|---|
animator
|
用于定义属性动画的 XML 文件。 |
anim
|
用于定义渐变动画的 XML 文件。(属性动画也可保存在此目录中,但为了区分这两种类型,属性动画首选 animator/ 目录。) |
color
|
用于定义颜色状态列表的 XML 文件 |
drawable
|
位图文件(.png、.9.png、.jpg、.gif)或编译为以下可绘制对象资源子类型的 XML 文件:位图文件、九宫格(可调整大小的位图)、状态列表、形状、动画可绘制对象、其他可绘制对象 |
mipmap
|
适用于不同启动器图标密度的可绘制对象文件 |
layout
|
用于定义用户界面布局的 XML 文件 |
menu
|
用于定义应用菜单(如选项菜单、上下文菜单或子菜单)的 XML 文件 |
raw
|
需以原始形式保存的任意文件。如要使用原始 InputStream 打开这些资源,请使用资源 ID (即 R.raw.filename )调用 Resources.openRawResource() 。但是,如需访问原始文件名和文件层次结构,则可以考虑将某些资源保存在 assets/ 目录(而非 res/raw/ )下。assets/ 中的文件没有资源 ID ,因此您只能使用 AssetManager 读取这些文件。
|
values
|
包含字符串、整型数和颜色等简单值的 XML 文件。 |
xml
|
可在运行时通过调用 Resources.getXML() 读取的任意 XML 文件。各种 XML 配置文件(如可搜索配置)都必须保存在此处。
|
font
|
带有扩展名的字体文件(如 .ttf 、.otf 或 .ttc ),或包含 <font-family> 元素的 XML 文件。
|
对于上面的animator
、anim
、color
、drawable
、layout
、menu
、raw
、values
、xml
的9种目录下存放的是缺省资源
,每种资源都可以有候选资源
,候选资源
的存放格式类似<resources_name>-<qualifier>
的目录下:
<resources_name>
就是前面的这9种缺省资源
的目录名<qualifier>
是一些限定符的组合,用来区分候选资源类型。- 关于候选资源的命名规则和匹配算法,在Android的官网上有着详细描述,大家可以参考这里:官网传送门
assets目录
assets
目录下保存的文件不能通过存取资源的方式在代码中访问到。访问assets
目录下文件的方式更像是打开一个文件,例如:
AssetManager am = Context.getAssets();
InputStream is = am.open(filePath);
assets
目录下可以再建目录,没有限制,目录下存放的文件在编译过程中不会被改动,会被原封不动的打包进APK
中。
raw目录
res
目录下有个raw
目录,放置在该目录下的文件也不会被Android
改动,但是raw
目录下不能在创建子目录。
访问raw
目录下资源的方式和访问其他资源是一致的:
InputStream is = getResources().openRawResource(R.id.filename);
同时,raw
目录下的资源也能像其它资源一样有备选资源,能够被overlay
目录中的资源覆盖掉,这也是assets
目录下的文件不具备的特性。
Android 资源管理的实现原理
上面我们了解到Android
中的资源是如此的纷繁复杂,那么
Android
是如何加载、管理资源的呢?- 我们知道
Android
应用可以使用的资源有3中来源:本apk、系统Framework、其他apk,Android
如何统一管理控制呢? - 不同
apk
间的资源包如何共享呢? - 对于资源加载,反复加载会浪费内存吗?资源加载会有效率问题吗?
关于这几个问题的答案,我们需要从原理上来寻找!
Resources
类的作用
Android
资源的装载是通过Resources
类来完成的。
还记的
Zygote
进程的预加载
么?
就是通过Resources.getSystem().startPreloading();
来实现的,我们来看看Resources
这个类到底做了什么
我们先看下核心的成员变量:
class Resources{// Resources的实例对象static Resources mSystem = null;// 真正实现类ResourcesImpl的实例对象private ResourcesImpl mResourcesImpl;
}
class ResourcesImpl{// 预加载相关资源private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables= new LongSparseArray<>();private static final LongSparseArray<android.content.res.ConstantState<ComplexColor>>sPreloadedComplexColors = new LongSparseArray<>();// Drawable 和 ColorStateList 的缓存private final DrawableCache mDrawableCache = new DrawableCache();private final DrawableCache mColorDrawableCache = new DrawableCache();// XML文件的缓存 XML_BLOCK_CACHE_SIZE=4,写死的private int mLastCachedXmlBlockIndex = -1;private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE];private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE];private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE];// AssetManager实例的final AssetManager mAssets;
}
上面的代码中,Resources
类有两个重要的变量:
Resources mSystem
:Resources
类的实例对象,用来管理系统资源
ResourcesImpl mResourcesImpl
- 在
9.0
版本的源码中,Resources
类具体业务交给了ResourcesImpl
来实现
- 在
我们下面具体来看下
mSystem
的初始化
mSystem
是一个引用Resources
类自身实例的静态变量。我们看下mSystem
变量是如何初始化的:
public static Resources getSystem() {synchronized (sSync) {Resources ret = mSystem;if (ret == null) {ret = new Resources();mSystem = ret;}return ret;}}
静态方法getSystem
在mSystem
为空的情况下会创建一个Resources
对象的实例。我们看下这个无参构造:
/*** class:Resources.java* Only for creating the System resources.*/private Resources() {mClassLoader = ClassLoader.getSystemClassLoader();final DisplayMetrics metrics = new DisplayMetrics();metrics.setToDefaults();final Configuration config = new Configuration();config.setToDefaults();mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,new DisplayAdjustments());}/*** class:ResourcesImpl.java*/public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {mAssets = assets;mMetrics.setToDefaults();mDisplayAdjustments = displayAdjustments;mConfiguration.setToDefaults();updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());}
这个两个构造方法比较简单,我们需要重点关注的是
Resource
类私有构造方法的注释:Only for creating the System resources.
- 在创建
ResourcesImpl
实例时传入的AssetManager
对象,是通过静态方法AssetManager.getSystem()
来获取的- 这里暂不深究,后面会单独讲解
AssetManager
类哈
- 这里暂不深究,后面会单独讲解
我们接下来就来找找Resource.getSystem()
方法最早是在什么地方调用的?
大家在frameworks
目录下grep
一下就会发现,最早的函数调用其实也就是在Zygote
进程初始化时:
/*** preloadResources 函数调用关系展示*/
class ZygoteInit{public static void main(String argv[]) {preload();}static void preload(...) {preloadResources();}private static void preloadResources() {mResources = Resources.getSystem();mResources.startPreloading();......preloadDrawables(ar);......preloadColorStateLists(ar);......mResources.finishPreloading();}private static int preloadDrawables() {......mResources.getDrawable(id, null);......}private static int preloadColorStateLists() {......mResources.getColorStateList(id, null);......}
}
由此,在Zygote
进程初始化时mSystem
就被赋值了。我们知道,所有的应用程序都是从Zygote
进程fork
而来的,这意味着所有进程中的mSystem
引用的Resource
实例在整个Android
系统中是共享的,这部分其实也就是Framework
的资源
getDrawable
的过程
在Zygote
的preloadResources
方法中得到系统的Resources
实例后,会调用这个实例的getDrawable
和getColorStateList
方法去加载系统的Drawable
资源和ColorStateList
资源。
以getDrawable(id,null)
方法为例,我们来看下具体干了啥:
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme){// 请注意此处传入的 density=0return getDrawableForDensity(id, 0, theme);}public Drawable getDrawableForDensity(...) {return mResourcesImpl.loadDrawable(this, value, id, density, theme);}
最后还是调用了具体实现类ResourcesImpl
的loadDrawable
方法,我们来看下整体流程:
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,int density, @Nullable Resources.Theme theme)throws NotFoundException {// getDrawable 传入的参数density = 0// 所以对于Zygote来说,useCache始终为truefinal boolean useCache = density == 0 || value.density == mMetrics.densityDpi;try {final boolean isColorDrawable;final DrawableCache caches;final long key;// 判断要加载的 Drawable 类型,普通资源还是Colorif (value.type >= TypedValue.TYPE_FIRST_COLOR_INT&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {isColorDrawable = true;caches = mColorDrawableCache;key = value.data;} else {isColorDrawable = false;caches = mDrawableCache;key = (((long) value.assetCookie) << 32) | value.data;}// 非预加载阶段才会走到这里// 检查要加载的资源是否已经存在于cache中// 有的话直接返回,没有找到继续检查预加载相关的cacheif (!mPreloading && useCache) {final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);if (cachedDrawable != null) {......return cachedDrawable;}}......// 检查预加载相关的cache// ConstantState这是一个抽象类// 每个Drawable的子类都要实现它,可以用来生成对应的Drawable实例// 很经典的一个类final Drawable.ConstantState cs;if (isColorDrawable) {cs = sPreloadedColorDrawables.get(key);} else {cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);}......Drawable dr;boolean needsNewDrawableAfterCache = false;if (cs != null) {// 在预加载cache中找到资源// 生成对应的Drawable实例dr = cs.newDrawable(wrapper);} else if (isColorDrawable) {// 预加载cache中没有找到资源// 直接创建Color类的资源,不是我们这次分析的重点哈dr = new ColorDrawable(value.data);} else {// 预加载cache中没有找到资源 // 通过 loadDrawableForCookie 从XML中加载资源dr = loadDrawableForCookie(wrapper, value, id, density);}......// 对Drawable实例进行简单的配置工作if (dr != null) {dr.setChangingConfigurations(value.changingConfigurations);if (useCache) {// 通过 cacheDrawable 进行资源缓存cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);......}}return dr;} catch (Exception e) {}}
从上面代码看,当缓存中没有找到需要的Drawable
资源,会通过loadDrawableForCookie
来加载资源,最后根据需要把资源缓存到cache
中。
先来看下loadDrawableForCookie
的核心代码如下:
if (file.endsWith(".xml")) {// 加载XML类型的drawable资源final XmlResourceParser rp = loadXmlResourceParser(file, id, value.assetCookie, "drawable");dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);rp.close();} else {// 加载图片类型的drawable资源final InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);AssetInputStream ais = (AssetInputStream) is;dr = decodeImageDrawable(ais, wrapper, value);}return dr;
资源装载分成了xml
和image
两种:
xml
:通过loadXmlResourceParser()
来加载,流程主要分为两部分:- 检查缓存中是否已经存在:
// First see if this block is in our cache. final int num = cachedXmlBlockFiles.length; for (int i = 0; i < num; i++) {if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null&& cachedXmlBlockFiles[i].equals(file)) {return cachedXmlBlocks[i].newParser();} }
- 缓存中不存在,通过
AssetManager
来装载文件并缓存
// Not in the cache, create a new block and put it at // the next slot in the cache. final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file); if (block != null) {final int pos = (mLastCachedXmlBlockIndex + 1) % num;mLastCachedXmlBlockIndex = pos;final XmlBlock oldBlock = cachedXmlBlocks[pos];if (oldBlock != null) {oldBlock.close();}cachedXmlBlockCookies[pos] = assetCookie;cachedXmlBlockFiles[pos] = file;cachedXmlBlocks[pos] = block;return block.newParser(); }
image
:通过mAssets.openNonAsset
来加载二进制流,并通过decodeImageDrawable()
来转化为相应的Drawable
对象
到这里,对于Resource
类的作用我们就梳理的差不多了,主要的功能是缓存Drawable
、ColorStateList
和XML
文件等资源,而具体的加载细节,都和一个叫AssetManager
的类有关系。
AssetManager
等下再看,我们可以先总结一下:
- 代表系统
Framework
资源的Resources
实例系统只有一份,通过Resources
类中的静态成员变量mSystem
引用。 - 系统中预加载的
Drawable
资源和ColorStateList
资源也是通过Resources
类中的静态成员变量引用。并且在整个系统中共享,不会浪费内存 - 应用中装载的
Drawable
、ColorStateList
和XML
资源在Resources
中会被缓存,因此反复装载这些资源的操作不会真正的重复加载,不会浪费额外的内存
AssetManager
类的作用
AssetManager
类是Android
中实际管理资源的模块,在Java层
和native层
都有代码。我们先看Java层
的AssetManager
类
Java层
的AssetManager
类
我们先看下AssetManager
类中的变量:
// Not private for LayoutLib's BridgeAssetManager.@UnsupportedAppUsage@GuardedBy("sSync") static AssetManager sSystem = null;@GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0];@GuardedBy("sSync") private static ArraySet<ApkAssets> sSystemApkAssetsSet;// Pointer to native implementation, stuffed inside a long.@UnsupportedAppUsage@GuardedBy("this") private long mObject;// The loaded asset paths.@GuardedBy("this") private ApkAssets[] mApkAssets;
sSystem
是和Resources
类中mSystem
对应的AssetManager
对象,用来管理系统Framewrok
的资源- 大家可以回顾上面的
mSystem
的初始化 部分
- 大家可以回顾上面的
sSystemApkAssets
、sSystemApkAssetsSet
都是和系统资源相关的ApkAssets
的集合,在Zygote
初始化时就已经创建完成- 有没有很好奇
ApkAssets
到底是啥?- 官方说明如下:
/** * The loaded, immutable, in-memory representation of an APK. * * The main implementation is native C++ and there is very little API surface exposed here. The APK * is mainly accessed via {@link AssetManager}. * * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, * making the creation of AssetManagers very cheap. * @hide */ public final class ApkAssets {@GuardedBy("this") private final long mNativePtr;@GuardedBy("this") private final StringBlock mStringBlock; } private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)throws IOException {mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); } public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/); }
ApkAssets
主要是与native
层进行联系,通过各种native
调用加载相关资源ApkAssets
还有一个功能是把native层
加载字符串资源保存到mStringBlock
中
sSystemApkAssets
是一个ApkAssets
的数组,通常一个ApkAssets
管理的是一个资源包中的资源,用数组的话说明可能会管理多个资源包
- 有没有很好奇
mObject
:用来存储在native层
创建的AssetManager
对象指针mApkAssets
:记录所有已经加载的asset
资源,也包括sSystemApkAssets
- 相关的实现过程可以参照
AssetManager
的setApkAssets()
函数
- 相关的实现过程可以参照
还记得
Resources
初始化时调用的AssetManager.getSystem()
函数么?我们来看下具体干了些啥?
注释比较详细:
private AssetManager(boolean sentinel) {// nativeCreate()的作用是创建一个native层的AssetManager实例,并返回对象指针// 从9.0的源码看,创建的是AssetManager2的实例mObject = nativeCreate();}public static AssetManager getSystem() {synchronized (sSync) {createSystemAssetsInZygoteLocked();return sSystem;}}private static void createSystemAssetsInZygoteLocked() {if (sSystem != null) {return;}final ArrayList<ApkAssets> apkAssets = new ArrayList<>();// 通过 ApkAssets 的 loadFromPath 来加载 Framework 资源// 最终是通过 nativeLoad() native函数来完成apkAssets.add(ApkAssets.loadFromPath("/system/framework/frmework-res.apk", true /*system*/));// 省略一些资源的额外操作......// 分别赋值给 sSystemApkAssetsSet 和 sSystemApkAssets 集合sSystemApkAssetsSet = new ArraySet<>(apkAssets);sSystemApkAssets = apkAssets.toArray(newApkAssets[apkAssets.size()]);// 创建系统专用的AssetManager实例// 最终调用的是 nativeCreate 函数sSystem = new AssetManager(true /*sentinel*/);// 设置资源到已加载集合,并赋值给mApkAssets变量sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);}
关于AssetManager
的创建我们比较清晰了,我们会发现大多数的过程都是通过native
调用来完成的,这也是Java层
的AssetManager
的主要作用:担当通过native层的桥梁
我们以Resources
的getString()
方法,来追踪下调用过程:
// Resources.javapublic String getString(@StringRes int id) throws NotFoundException {return getText(id).toString();}public CharSequence getText(@StringRes int id) throws NotFoundException {// 调用的AssetManager的getResourceText函数CharSequence res = mResourcesImpl.getAssets().getResourceText(id);if (res != null) {return res;}......}// AssetManager.javaCharSequence getResourceText(@StringRes int resId) {synchronized (this) {// 通过 TypedValue 对象来接收转换数据final TypedValue outValue = mValue;if (getResourceValue(resId, 0, outValue, true)) {return outValue.coerceToString();}return null;}}boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,boolean resolveRefs) {Preconditions.checkNotNull(outValue, "outValue");synchronized (this) {//通过 nativeGetResourceValue 函数查找resId资源对应ApkAssets的cookie值//并把相关数据更新到TypedValue对象中//资源cookie值映射的是Java层的已加载资源mApkAssets集合的index值final int cookie = nativeGetResourceValue(mObject, resId, (short) densityDpi, outValue, resolveRefs);if (cookie <= 0) {return false;}......if (outValue.type == TypedValue.TYPE_STRING) {// 当数值类型判断正确后// 通过ApkAssets对象的getStringFromPool读取数据// 需要注意的是outValue.data// 它表示字符串在对应ApkAssets对象中的mStringBlock数组的index值outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);}return true;}}// ApkAssets.javaCharSequence getStringFromPool(int idx) {synchronized (this) {// 从mStringBlock获取字符串数据return mStringBlock.get(idx);}}
调用过程很清晰了哈!
可以看出AssetManager
的功能实现依赖了很多的native
函数,接下来我们就看看Android
在native
层是怎么设计的吧!
native
层的AssetManager
从上面我们知道,native
层的AssetManager
才是真正装载资源的部分。
Android 资源管理
这部分是Google
爸爸折腾最多的地方,对比9.0
和5.0
的源码真滴是天差地别啊
5.0
的书籍已经用不上了,差别太大。。。。。不过,从上面Java
层的知识我们已经知道:
- 装载资源(就是加载
framwork-res.apk
)的入口是ApkAssets.loadFromPath()
- 最后将资源整合到体系中的函数是
AssetManager.setApkAssets()
我们就先从这两个函数入手看看
通过ApkAssets
装载资源
ApkAssets.loadFromPath
调用的是ApkAssets
的私有构造函数:
// ApkAssets.java
private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)throws IOException {mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
ApkAssets
的私有构造函数通过nativeLoad()
本地调用来实现真正的资源加载
nativeLoad()
函数调用示意:
// android_content_res_ApkAssets.cpp
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system, jboolean force_shared_lib, jboolean overlay) {.......// 调用了 native层的 ApkAssets 的 Load 函数apk_assets = ApkAssets::Load(path.c_str(), system);......return reinterpret_cast<jlong>(apk_assets.release());
}
// ApkAssets.cpp
std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {// 奇怪的C++智能指针// 真正进行加载的是 LoadImpl() 函数return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
}
nativeLoad()
函数调用了native
层ApkAssets
类的LoadImpl
函数。从这个函数开始,就开始对resources.arsc
文件进行操作了,所以,我们先配上一幅结构图再一步一步的看函数实现
描述资源的resources.arsc
文件
图片来源于大神LinJW博客,受益匪浅:
resources.arsc
是以一个个Chunk块
的形式组织的,Chunk
的头部信息记录了这个Chunk
的类型、长度等数据。从整体上来看,其结构为:资源索引表头部
+字符串资源池
+N个Package数据块
简单了解即可,让我们从LoadImpl
函数开始跟踪吧
ApkAssets::LoadImpl()
函数
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(......) {// 一个用来指向解压后的资源文件的指针::ZipArchiveHandle unmanaged_handle;// 解压资源文件::OpenArchive(path.c_str(), &unmanaged_handle);// 将解压后的资源文件包装成ApkAssets对象// Wrap the handle in a unique_ptr so it gets automatically closed.std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path));// resources.arsc 就是我们apk中的资源索引表::ZipString entry_name("resources.arsc");::ZipEntry entry;// 从解压后的apk资源中查找 resources.arscresult = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);if (result != 0) {// There is no resources.arsc, so create an empty LoadedArsc and return.loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();// 对于一个Java coder,std::move真的是一个SAO操作return std::move(loaded_apk);}// Open the resource table via mmap unless it is compressed.// This logic is taken care of by Open.// 通过Open函数加载ARSC文件到resources_asset_,英文注释也就是方法的执行逻辑了loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);// Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.// 其实这行代码主要是给loadOverlay情况用的,其他情况下都相当于赋值为null// loadOverlay中已经包含了一个idmap,所以在这里需要保留下来原来的loaded_apk->idmap_asset_ = std::move(idmap_asset);// 将resources_asset_(加载完成的ASRC数据)打包成StringPiece对象const StringPiece data(reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),loaded_apk->resources_asset_->getLength());// 调用 LoadedArsc 的 Load 函数解析转化为资源数据,我们等下重点看下loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);......// Need to force a move for mingw32.return std::move(loaded_apk);
}
上面的代码注释比较详细哈,不再赘述。接下来重点关注下LoadedArsc::Load()
函数。
LoadedArsc::Load()
函数解析resources.arsc
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,const LoadedIdmap* loaded_idmap, bool system,bool load_as_shared_library) {// Not using make_unique because the constructor is private.// 创建 LoadedArsc 实例std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());loaded_arsc->system_ = system;// 遍历StringPiece对象(也就是加载好的arsc)ChunkIterator iter(data.data(), data.size());while (iter.HasNext()) {const Chunk chunk = iter.Next();switch (chunk.type()) {case RES_TABLE_TYPE:// 如果数据类型是 RES_TABLE_TYPE // 通过 LoadTable 将对应资源加载到 table 中if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {return {};}break;}}return std::move(loaded_arsc);
}
从上面可以看到,资源的加载处理调用到了LoadTable()
函数,我们来看下
LoadedArsc::LoadTable()
函数解析resources.arsc
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,bool load_as_shared_library) {const ResTable_header* header = chunk.header<ResTable_header>();......ChunkIterator iter(chunk.data_ptr(), chunk.data_size());while (iter.HasNext()) {const Chunk child_chunk = iter.Next();switch (child_chunk.type()) {case RES_STRING_POOL_TYPE:// Only use the first string pool. Ignore others.// 如果是字符常量池类型的资源// 数据保存到global_string_pool_中,这是全局字符串// Java层ApkAssets中的mStringBlock指向的就是这部分数据status_t err = global_string_pool_.setTo(child_chunk.header<ResStringPool_header>(), child_chunk.size());break;case RES_TABLE_PACKAGE_TYPE: {// 如果是pacakge类型的资源// 使用 LoadedPackage::Load() 进行加载......std::unique_ptr<const LoadedPackage> loaded_package =LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);packages_.push_back(std::move(loaded_package));} break;}}......return true;
}
从上面的代码看,字符串资源放到了global_string_pool_
中,对于RES_TABLE_PACKAGE_TYPE
类型的资源通过LoadedPackage::Load()
函数
LoadedPackage::Load()
函数解析resources.arsc
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(......) {// 创建LoadedPackage实例std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());......// 设置是否为系统资源loaded_package->system_ = system;// 根据packageID 判断是否为共享资源loaded_package->package_id_ = dtohl(header->id);if (loaded_package->package_id_ == 0 ||(loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) {// Package ID of 0 means this is a shared library.loaded_package->dynamic_ = true;}......while (iter.HasNext()) {const Chunk child_chunk = iter.Next();switch (child_chunk.type()) {case RES_STRING_POOL_TYPE: // 这里会解析两种字符串池类型// 一种是资源类型字符串池,比如:ainim、attr、bool、color等// 另一种是资源项名称字符串池,比如:app_name、activity_main等......case RES_TABLE_TYPE_SPEC_TYPE: ......case RES_TABLE_TYPE_TYPE: ......case RES_TABLE_LIBRARY_TYPE: const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count);for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {......// 添加package_name和packageId到loaded_package的dynamic_package_map_中loaded_package->dynamic_package_map_.emplace_back(std::move(package_name),dtohl(entry_iter->packageId));}......}}......return std::move(loaded_package);
}
这部分函数的核心就是把chunk
按类型进行解析处理,然后返回解析完成的loaded_package
指针。
到这里一个res.apk
的装载过程基本就完成了,过程中忽略了很多细节,不要着急哈,让我们先搞清整体流程。
资源装载的整体流程
让我们从ApkAssets
开始来梳理下流程:
- 调用时序图:
- 按照上图的调用流程走完
LoadedArsc
对象中的global_string_pool
和package
相关的资源已经完成加载ApkAssets
对象中的resource_asset_
和loaded_arsc_
也加载完成
- 按照上图的调用流程走完
- 关系类图:
这样,一个res.apk
的装载过程我们清楚了,但是AssetManager
是怎么组织它的呢?
我们来看下AssetManager.setApkAssets()
函数
通过AssetManager.setApkAssets()
添加资源
我们先看AssetManager.setApkAssets()
函数:
public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {......ApkAssets[] newApkAssets = new ApkAssets[sSystemApkAssets.length + apkAssets.length];// Copy the system assets first.System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);// Copy the given ApkAssets if they are not already in the system list.int newLength = sSystemApkAssets.length;for (ApkAssets apkAsset : apkAssets) {if (!sSystemApkAssetsSet.contains(apkAsset)) {newApkAssets[newLength++] = apkAsset;}}......mApkAssets = newApkAssets;nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);}
Java
部分比较简单:
setApkAssets()
会先把sSystemApkAssets
中的资源拷贝到一个全新数组newApkAssets
中- 然后检查
sSystemApkAssets
没有的资源,并添加到数组newApkAssets
- 更新已加载资源集合
mApkAssets
- 调用
nativeSetApkAssets
处理native层
的资源
我们看下NativeSetApkAssets
函数做了啥:
static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,jobjectArray apk_assets_array, jboolean invalidate_caches) {// 根据Java层传递的数值长度,创建对应的ApkAssets集合const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);std::vector<const ApkAssets*> apk_assets;apk_assets.reserve(apk_assets_len);// 将 Java 层保存的ApkAssets指针取出并添加到集合中for (jsize i = 0; i < apk_assets_len; i++) {jobject obj = env->GetObjectArrayElement(apk_assets_array, i);jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));}ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));// 调用 AssetManager2 的 SetApkAssets 函数assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}
NativeSetApkAssets()
函数对Java层
的数据进行简单处理后,调用了AssetManager2
的SetApkAssets
函数,函数如下:
bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,bool invalidate_caches) {// 将ApkAssets赋值给apk_assets_apk_assets_ = apk_assets;// 构建动态资源引用表BuildDynamicRefTable();// 重新构建符合当前设备的资源,即 filtered_configs_RebuildFilterList();......return true;
}
真的简洁。。。。。。核心函数也很突出,为了方便理解接下来的BuildDynamicRefTable()
函数,我们先来看AssetManager2
类的相关定义
AssetManager2
的关键定义
class AssetManager2 {//用来存储该AssetManager2已经加载的所有APK包std::vector<const ApkAssets*> apk_assets_;/*** 用来将apk_assets_分组,主要还是用来处理Runtime Resources Overlay的*/std::vector<PackageGroup> package_groups_;/*** 它的key表示APK包也就是ApkAssets的id* 它的value表示APK包也就是ApkAssets所在的PackageGroup在package_groups_中的索引*/std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;//表示设备当前的配置信息ResTable_config configuration_;/*** 用来缓存资源的Bag* 它的key表示一个资源的id,比如一个style,一个array* 它的value 表示已经从resources.arsc中解析出来了的,该资源的所有Bag*/std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
}// A collection of configurations and their associated ResTable_type that match the current// AssetManager configuration.struct FilteredConfigGroup {// 该ResTable_typeSpec中符合设备当前配置的所有的configstd::vector<ResTable_config> configurations;// 该ResTable_typeSpec中符合设备当前配置的所有的ResTable_typestd::vector<const ResTable_type*> types;};// Represents an single package.struct ConfiguredPackage {// A pointer to the immutable, loaded package info.const LoadedPackage* loaded_package_;// 我们在获取资源的时候,要根据设备的当前配置信息,去选择最合适的资源项// 这个过程要经过match、isBetterThan、isMoreSpecificThan 等比较的过程// 现在为了加快获取资源的速度,在加载完资源后,系统就会先选出匹配设备当前配置的资源存放在filtered_configs_中。// 当我们获取资源的时候,就可以节省筛选步骤// filtered_configs_中的每一项代表一个ResTable_typeSpec中符合设备当前配置的所有ResTable_typeByteBucketArray<FilteredConfigGroup> filtered_configs_;};using ApkAssetsCookie = int32_t;// Represents a logical package, which can be made up of many individual packages. Each package// in a PackageGroup shares the same package name and package ID.// 大家留意上面的注释struct PackageGroup {// 相同loaded_package name的集合,包括target package和overlay package// 如果一个Package没有overlay package,那么它应该独占一个PackageGroupstd::vector<ConfiguredPackage> packages_;// The cookies associated with each package in the group. They share the same order as// packages_.// 表示 ConfiguredPackage 所代表的 ApkAssets 在 AssetManager2 的 apk_assets_ 集合中的位置// cookies_集合的存放顺序与packages_的顺序一一对应std::vector<ApkAssetsCookie> cookies_;// A library reference table that contains build-package ID to runtime-package ID mappings.// 一个用来描述资源共享库的编译时id和运行时id的映射关系表DynamicRefTable dynamic_ref_table;};
结合上面的定义,我们来看下BuildDynamicRefTable()
干了啥
BuildDynamicRefTable()
构建动态资源引用表
void AssetManager2::BuildDynamicRefTable() {// 简单的初始化操作package_groups_.clear();package_ids_.fill(0xff);// 0x01 is reserved for the android package.int next_package_id = 0x02;const size_t apk_assets_count = apk_assets_.size();// 遍历处理apk_assets_集合for (size_t i = 0; i < apk_assets_count; i++) {// 获取已经解析加载完成的arsc资源,即loaded_arscconst LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();// 从loaded_arsc中取出解析完成的package资源,即loaded_packagefor (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {// Get the package ID or assign one if a shared library.int package_id;if (package->IsDynamic()) {// 如果是共享资源,分配一个特殊的package idpackage_id = next_package_id++;} else {// 不是共享资源,使用原有的package idpackage_id = package->GetPackageId();}// Add the mapping for package ID to index if not present.uint8_t idx = package_ids_[package_id];if (idx == 0xff) {// 0xff 说明package_ids还未记录package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());// 先向package_groups_的尾部添加一个新的PackageGroup对象package_groups_.push_back({});// 然后获取对象动态资源引用表的指针DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table;// 赋值 package_id,干啥用的后面看看再说ref_table.mAssignedPackageId = package_id;// 设置当前是否为共享资源,常量值 0x7F 值得研究研究ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;}// 取出PackageGroup对象,开始进行数据填充PackageGroup* package_group = &package_groups_[idx];// Add the package and to the set of packages with the same ID.// 设置 packages_ 和 cookies_package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});// 留意下这个变量i,其实就是当前ApkAsset在apk_assets_中的数组下标package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));// Add the package name -> build time ID mappings.for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {// 解析loaded_package中的 dynamic_package_map_ 一个packageName:packageID格式的集合String16 package_name(entry.package_name.c_str(), entry.package_name.size());// 并设置到 package_group 中的动态资源引用表中package_group->dynamic_ref_table.mEntries.replaceValueFor(package_name, static_cast<uint8_t>(entry.package_id));}}}// 到这里,ApkAssets集合中的数据就被解析的差不多了// package_groups_和package_ids_集合中的数据也被填充的差不多了// Now assign the runtime IDs so that we have a build-time to runtime ID map.// 接下来的这部分是将资源的 Build ID 与 runtime ID 关联起来// 还记得前面生成的 mAssignedPackageId 么,在这里就起到作用了const auto package_groups_end = package_groups_.end();for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {// 跟踪addMapping函数就会发现,这部分的操作是:// 根据 package_name 查找的 build packageID// 然后把 build packageID 作为索引,关联runtimeID,也就是 mAssignedPackageIditer2->dynamic_ref_table.addMapping(package_name, iter->dynamic_ref_table.mAssignedPackageId);}}
}
BuildDynamicRefTable()
函数基本上把AssetManager2
定义的关键数据都填充完了,整体流程注释的比较详细,不再补充啦
这里还是要吐槽一下,
Android
资源相关的struct嵌套有点多,短时间理解起来还是有难度的。好在资源加载部分的业务逻辑应该很少遇到改动需求,庆幸一下下先
到这里整个framework
资源的加载流程就差不多完成了,我们简单总结下
AssetManager
加载过程的简单总结
资源管理部分还有比较重要的一个知识点是
RRO
,就不在这里介绍了,AssetManager2
中的很多数据定义都是为了RRO
而设计的,需要的话可以先从Google官网-RRO了解先
我们还是先通过调用时序图来看下资源加载过程,从AssetManager.getSystem()
开始,流程如下:
资源加载完后的整体结构,如下图:
看完这两张图后思路有木有更清晰了呢,哈哈哈
AssetManager
的资源查找
了解了AssetManager
的资源加载过程,查找过程就比较容易了,AssetManager
的资源查找入口可以简单分为两种:
open
方式:像open()
、openNonAsset()
等getResource
方式:像getResourceText()
、getResourceArray()
等
我们来简单看下
open
方式
对于open
类的接口,基本上处理的都是一些xml
或者文件
形式的数据,从函数调用来看,最后都走到了native
层的OpenNonAsset
函数,这部分处理比较简单,源码如下:
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,Asset::AccessMode mode,ApkAssetsCookie* out_cookie) const {for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);if (asset) {if (out_cookie != nullptr) {*out_cookie = i;}return asset;}}......
}
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,ApkAssetsCookie cookie,Asset::AccessMode mode) const {......// 从这里就可以看出 ApkAssetsCookie 只是一个下标索引// 如果之前查找过对应的资源,就记录下来对应的数组下标// 下次获取时就不需要像上面一样用for循环去查找了return apk_assets_[cookie]->Open(filename, mode);
}
函数根据cookie
需求做了一个简单的重载,但是最终都是调用到了ApkAssets
的Open()
函数。
这个函数我们在资源加载时遇到过,作用就是加载资源,如果是压缩数据会进行相应的解压处理。
这里面还涉及到了Asset
类,大家感兴趣可以深入了解下
getResource
方式
跟踪getResource
类型的函数调用,我们最终找到的native
层接口是AssetManager2::FindEntry()
,简要流程如下:
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,bool /*stop_at_first_match*/,FindEntryResult* out_entry) const {......// 根据resid解析出对应的pacakge信息 const uint32_t package_id = get_package_id(resid);const uint8_t type_idx = get_type_id(resid) - 1;const uint16_t entry_idx = get_entry_id(resid);const uint8_t package_idx = package_ids_[package_id];if (package_idx == 0xff) {// 资源对应的package为空,返回LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);return kInvalidCookie;}// 查找到资源对应的 PackageGroup 对象,PackageGroup 对象中包含着一个一个的loaded_pacakgeconst PackageGroup& package_group = package_groups_[package_idx];// 记录 PackageGroup 对象中的loaded_package_数量const size_t package_count = package_group.packages_.size();......// 开始从资源对应的 PackageGroup 中查找最合适的资源for (size_t pi = 0; pi < package_count; pi++) {......// If desired_config is the same as the set configuration, then we can use our filtered list// and we don't need to match the configurations, since they already matched.// 如果获取资源时的配置信息和资源load时的配置信息一致的话,可以使用快速查找逻辑const bool use_fast_path = desired_config == &configuration_;// 获取最适合当前配置的资源集合const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];if (use_fast_path) {// 快速查找,直接使用filtered_configs_中的配置资源即可} else {// This is the slower path, which doesn't use the filtered list of configurations.// Here we must read the ResTable_config from the mmapped APK, convert it to host endianness// and fill in any new fields that did not exist when the APK was compiled.// Furthermore when selecting configurations we can't just record the pointer to the// ResTable_config, we must copy it.// 需要查找资源对应的所有类型,从中找出匹配的资源,过程中还会涉及到内存拷贝,比较缓慢......}}......
}
原函数还有很多细节没有展示,不过整体流程已经梳理出来了。再详细的部分就涉及到ARSC
中的数据结构了,为了避免篇幅过大就先到这里啦!
感觉要想真正搞懂资源管理这部分还是要去熟悉arsc
文件的格式,以及加载后的内存模型
学到这里算是找到了进入Android资源管理
世界入口和钥匙了,后面再来深入学习研究吧(挖坑ING…)
结语
对于Android资源管理
这部分的学习来说,进度比以往慢了很多,书中5.0
的资源管理与当前系统差异着实不小,少了一个好的向导,学习起来迷茫了一些,好在对比着源码磕磕绊绊的理清了。
总结下来,还只是刚刚入门,像资源适配
、RRO
等知识还未详细涉及,但为了不拖延大局,只能先到此为止了,等完成Android
系统的整体学习后再来补充吧
先附上参考链接以供分享学习之用:
- 大神博客-resources.arsc相关知识
- 大神博客-Android 资源管理框架
- Android官网-RRO
- Android官网-屏幕兼容性
下一篇学习SystemServer
进程
深入Android系统(八)Android的资源管理相关推荐
- Android系统架构-[Android取经之路]
摘要:本节主要来讲解Android的系统架构 阅读本文大约需要花费10分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢 ...
- android 服务端技术,移动应用服务器端开发(基于JSP技术)-2017 Android系统构架 Android系统构架.docx...
Android系统构架 PAGE 1 目 录 TOC \o "1-3" \h \z \u 一.Android系统构架 1 二.Linux内核层 2 三.系统运行库层 3 (一)系统 ...
- 【android系统】android系统升级流程分析(二)---update升级包分析
接下来我们将通过几篇文章来分析update.zip包在具体Android系统升级的过程,来理解Android系统中Recovery模式服务的工作原理.今天让我先来分析下升级包update.zip. 一 ...
- 【android系统】android系统升级流程分析(一)---recovery模式中进行update包升级流程分析
今天我们直接来看下android中具体的升级过程是如何的. 升级流程概述 升级的流程图: 升级流程分析 第一步:升级包获取 升级获取可以通过远程下载,也可直接拷贝到指定目录即可. 第二步:准备升级 然 ...
- android log抓取方法,Android系统之Android抓取各种log的方法
Android系统之Android抓取各种log的方法 2018年11月25日 | 萬仟网移动技术 | 我要评论 android之android抓取各种log的方法 1.logcat (四类log b ...
- Android系统架构-----Android的系统体系架构
一.Android的系统体系结构 Android其本质就是在标准的Linux系统上增加了Java虚拟机Dalvik,并在Dalvik虚拟机上搭建了一个JAVA的application framewor ...
- android系统语音合成,android 语音合成报错
发现了2个问题 第一个貌似是复制离线的资源出错了(已经核对过读写等权限): 12-19 19:54:49.739 32006-32159/com.zhanglf.youxuanz I/NonBlock ...
- Android 系统(71)---Android系统build.prop文件生成过程
Android系统build.prop文件生成过程 Android系统build.prop生成过程 这个文件类似于windows的注册表文件,定义了系统初始的一些参数属性,功能的开放等,通过调整或增加 ...
- Android系统(62)-----Android 7.1 新特性之 Shortcuts 介绍
Android 7.1 新特性之 Shortcuts 介绍 Android 7.1 允许 App 自定义 Shortcuts,类似 iOS 的 3D touch.通过在桌面长按 App 弹出 Shor ...
- Android 系统(11)---android 系统权限大全
收集到的android权限都很实用的(permission)大全 1.android.permission.WRITE_USER_DICTIONARY 允许应用程序向用户词典中写入新词 2.andro ...
最新文章
- 成立 5 周年:一文览尽 Facebook 人工智能研究院历年重要成果
- 05_专家的快速入门、加载MNIST数据集、数据集切分和混淆、定义类的方式构建模型、选择优化器和损失函数、训练模型和测试模型准确率
- Django搭建个人博客:上传头像图片
- 设置函数环境——setfenv(转)
- 视频点播服务器实现视频转码和视频播出功能
- Spring的XML解析原理,java软件开发面试常见问题
- Java动态代理之InvocationHandler最简单的入门教程 1
- 加速你的开发环境[VS2003]
- Codeforces Round #334 (Div. 2) A. Uncowed Forces 水题
- eclipse中jsp页面%@page import=javax.servlet.http.*%报错
- 用selenium爬取斗鱼信息
- TCP压测工具 终极 全新版
- 互联网公司吹牛逼指南
- ICM-20602 IMU ACCEL/GYRO/TEMP I2C/SPI LGA
- redis 学习笔记
- 如何搭建DHCP服务器及自动获取IP地址及相关操作
- java对外接口安全问题_怎么保证对外暴露接口的安全性(调用频率限制)
- python代码风格指南_记录Python代码:完整指南
- 广东2018年6月计算机一级试题,2018年9月计算机等级考试一级试题及答案.doc
- m蜂窝移动通信系统中越区切换的matlab仿真分析