小米多主题思路分析-重定向资源篇

来源:互联网 发布:淘宝鹏飞运动怎么样 编辑:程序博客网 时间:2024/05/18 05:30

首先声明一下,该文章分析参考小米MIUI多主题一文中的介绍:Android系统如何实现换肤及MIUI
文档名称:《MIUI主题风格_一种Android系统换肤功能的设计》大家可自行搜索下载。
另外考虑到排版的问题,在分析源码的过程中只会留下关键的点,太长的源码会以“代码段x.x{}”替代省略。但是主要的逻辑我会描述清楚。
1 什么是系统主题?
这一段这样的对话: 领导问我,主题能替换哪些资源啊?
我不加思索的回答:文字,颜色,图片,恩…… 还有大小。
领导说: 还有呢?
我思考了一会儿,内心来了一段对白: 折腾了这么久,就支持这几个? 是不是忽略了哪些地方,但是实在没有想到别的。
我就回答: 是的。
领导走了,剩下我呆呆的看着车载界面,试图从里面找到我忽略的地方?
不知道多久,突然我反应过来, 界面除了文字,颜色,图片,大小,还有别的吗?
恩是的,这就是主题,就是替换系统和应用图片,文字,颜色,大小,使其展示一种新的风格。

2 要实现系统主题应该思考哪些问题呢?
2.1 资源重定向:什么是资源重定向呢?
简单来说就是在应用通过Resources类获取资源的过程中,把应该返回的图片A/颜色A ,替换成主题的 图片B/颜色B。从而实现界面风格的变化。
2.2 即时刷新:什么是即时刷新呢?
在切换主题的时候,设备的当前页面以及之前用户打开过的页面,都要刷新一下,保证这些页面切换为新的主题风格。
因为不方便分析已经实现的源码,所以接下来的两篇文章都只是分析Android源码以及思路。先来看资源重定向的思路。

3 资源重定向:
先看一张重定向流程图:
这里写图片描述
其实思路就是这样的应用通过Resources类来获取资源的时候拦截一下,返回主题的资源。要想知道如何拦截,必须了解一下Resources类, 通过它取资源的流程,然后分析重定向的思路。
按照以下4个方面来分析:
3.1 Android apk资源的一些理解。
3.2 访问资源Resources类的构建。
3.3 通过Resources获取资源的流程(以获取图片为例)。
3.4 关于资源重定向思路分析

3.1 Android apk资源的一些理解:
R文件: 应用在编译完成后会生成一个R文件,在应用中定义的资源基本在R文件中对应一个Id,使用资源的时候都是通过这个ID来查找的。每个ID由三部分组成: packageID+TypeID+EntryID
resources.arsc文件: 这个文件是一个资源配置表,通过R文件中生成的资源ID可以从这个资源配置表中找到这个ID对应配置信息。这些配置信息中可以知道ID的资源类型,对应的名称/资源路径等等。
这里大概了解这两点就行了,足以应对主题功能了,其他更多的大家可以取看罗升阳的博客关于资源的介绍。

3.2 访问资源Resources类的构建:
应用都是通过Resources类来访问资源的,而主题就是希望能偷梁换柱替换资源,所以有必要了解一下Resources这个类。先看一张图整体了接Resources类相关:
这里写图片描述
ResourcesManager: 一个进程可以运行多个应用,也就是说可能存在多个Resources对象,所以ResourcesManager负责管理当前进程的所有Resources对象。
mActiveResources: ResourcesManager的成员变量,保存Resources对象。
Resources: 其实它只是对外提供一个访问资源的封装类,封装了AssertManager对象。
AssertManager(java层): 他只是封装了native层的AssertManager对象。
AssertManager(native层): 实际访问资源的类,下面是他的三个成员变量。
AssetPath: 表示资源的路径,一般包含两个, 一个是Framework-res.apk系统资源的路径(默认添加)另一个是,App所在的路径。
ResTable: 资源表,第一次获取资源的时候,会根据AssetPath给定的路径,读取路径中resources.arsc文件。
ResTable_Config: 当前设置的资源配置信息,在获取资源的过程中,会检查当前的配置信息,获取正确的资源信息。这个配置信息的值就是,语言,屏幕分辨率,橫竖屏等等。因为Android是支持多语言的,多分辨率的,想要找到合适的资源,就需要先设置这个Config。

总结一下:实际Resources就是构建了这三个成员变量,
其中AssetPath指明了资源从哪里找?
ResTable:找到后放在哪里?
ResTable_Config:如何找到合适当前设备的资源?
这里考虑文章的篇幅,不做源码分析,如果取分析ResourcesManager的getTopLevelResources函数你会发现实际上构造一个Resources类,就是为了构造Native层AssertManager的那三个成员变量。

3.3 通过Resources获取资源的流程(以获取图片为例)。
Resources是用来获取资源的,通过他获取图片资源的接口如下:
public Drawable getDrawable(@DrawableRes int id)
这个函数只是调用了另外一个getDrawable的函数

3.3.1 获取Drawable

    public Drawable getDrawable(int id, Theme theme){        TypedValue value;        getValue(id, value, true);        final Drawable res = loadDrawable(value, id, theme);        return res;    }

这个函数主要做了两件事情,第一件事情:调用getValue函数获取一个 TypeValue。 第二件事情: 用TypeValue作为参数,调用loadDrawable函数获取Drawable对象。

3.3.2 getValue函数

    public void getValue(int id, TypedValue outValue, boolean resolveRefs){      boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);    } mAssets是前面讲过的java层的 AssertManager类的实例。具体函数如下    final boolean getResourceValue(int ident,int density,TypedValue outValue,boolean resolveRefs)    {        int block = loadResourceValue(ident,  (short) density,  outValue,  resolveRefs);           ....省略的代码和获取图片资源流程无关        return false;    }

这里loadResourceValue实际上是一个jni的函数,这个函数在android_util_assetmanager.cpp文件中。

3.3.3 loadResourceValue函数:

static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,jint ident,   jshort density,   jobject outValue, jboolean resolve){    AssetManager* am = assetManagerForJavaObject(env, clazz);    const ResTable& res(am->getResources());    Res_value value;    ResTable_config config;    uint32_t typeSpecFlags;    ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);    uint32_t ref = ident;    if (resolve) {      block = res.resolveReference(&value, block, &ref,typeSpecFlags, &config);    }    if (block >= 0) {      return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);    }    return static_cast<jint>(block);}

前面讲过java层的AssetManager是封装了Native层的AssetManager对象。实际上就是Java层的AssetManager对象的成员变量mObject对应的就是Native层的一个地址,这个地址就是Native层的AssetManger对象。
a: 这里首先调用assetManagerForJavaObject把mObject转换成Native层的AssetManager
b: 调用AssetManger的getResources函数获取ResTable,前面分析过ResTab读取的是resources.arsc的信息,而resources.ars保存了资源的配置信息。
c: 调用ResTable的getResources函数获取C++层的Res_value,这个就是资源的配置信息,在C++层的表示。
d: 调用ResTable的resolveReference是处理引用类型的资源。在Android中定义可以资源可以引用另外一个资源,
f: 最后调用copyValue函数把Res_value 复制到TypeValue中。
分析到这里其实已经关联上了ResTable和TypedValue,具体的如何获取ResTab,ResTab如何通过iden和config匹配到合适的资源,如何处理引用类型的资源,这些过程会很长,而且分析流程就是为了找到Hook点,其实没有必要拦截到下面去,我曾分析过,试图从上层设置一个参数,在底层实现主题资源切换,但是没找到合适的地方。大家感兴趣可以继续分析。
接下来就看这个TypedValue是怎么用的。

3.3.4 loadDrawable()获取Drawable对象。
这个函数是重点分5个代码段来分析:

    Drawable loadDrawable(TypedValue value, int id, Theme theme) {        //代码段1{}        final boolean isColorDrawable;        final DrawableCache caches;        final long key;        if (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;        }

代码段1{}
mColorDrawableCache: 用于缓存颜色资源的。
mDrawableCache: 用于缓存图片资源
通过前面获得的TypedValue的成员变量,assetCookie和data生成一个key。
这里再分享一个看代码的经验:如果不知道assetCookie和data表达什么意思,不打紧。主要是他们可以组合成一个key,通过分析下面的代码可以知道这个key就是用于缓存的。如果一定要和自己死磕看明白这两个变量是什么意思,你会发现,又会牵扯出大量的信息,信息量越大,越不利于分析代码逻辑,通常会大大降低看代码的效率。
这里还是简单说一下,不想了解的可以忽略:
assetCookie : 一般AssetManager有两个资源表,一个是Framework-res.apk,另外一个就是应用本身,放在一个数组里面。 当assetCookie=0 表示的是Framework-res.apk的资源,如果assetCookie = 1, 表示的是应用本省的资源。
data: 表示的是一个位置或者资源的值。当前这个资源在字符串资源池哪个block的位置。
type: 表示当前资源的类型。

当type为COLOR时: 为什么只用data为key呢?
原因是,可能在在应用定义多个Color但是他们表达的颜色是一样的,这样应用只要缓存一份就可以了,不用缓存多份。

        //代码段2{}        if (!mPreloading) {            final Drawable cachedDrawable = caches.getInstance(key, theme);            if (cachedDrawable != null) {                 return cachedDrawable;            }        }

代码段2:
检查当前资源是否已经缓存过,如果缓存过直接放回。

        //代码段3{}        final ConstantState cs;        if (isColorDrawable) {           cs = sPreloadedColorDrawables.get(key);        } else {           cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);        }

代码段3:
sPreloadedColorDrawables:预加载的Color资源
sPreloadedDrawables:预加载的图片资源
Android系统会预加载一些常用的公共资源,类等。以提高运行效率。这些预加载的资源是在Zygote进程中加载的。而Android应用的进程都是由zygote进程fork出来的。因为Linux的fork特性,这些进程fork出来以后,预加载的资源就已经到内存里面了。
这段代码就是检查当前图片资源是否预加载的缓存中。

        //代码段4{}        Drawable dr;        if (cs != null) {            dr = cs.newDrawable(this);        } else if (isColorDrawable) {            dr = new ColorDrawable(value.data);        } else {            dr = loadDrawableForCookie(value, id, null);        }

这段代码是如果缓存前面的缓存里面存在,就直接取出drawable,如果没有就调用loadDrawableForCookie函数加载drawable。这个函数稍后分析。

        //代码段5{}   if (dr != null) {      dr.setChangingConfigurations(value.changingConfigurations);      cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);    }

这段代码主要是把代码段4获取的Drawable缓存起来。保证第二次获取的时候就直接从缓存中读。
return dr;
}

代码段4{} loadDrawableForCookie函数:

 private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {        final String file = value.string.toString();        final Drawable dr;      if (file.endsWith(".xml")) {                final XmlResourceParser rp = loadXmlResourceParser(file, id, value.assetCookie, "drawable");                dr = Drawable.createFromXml(this, rp, theme);                rp.close();            } else {              final InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);              dr = Drawable.createFromResourceStream(this, value, is, file, null);              is.close();            }        return dr;    }

TypedValue的成员变量string: 原来当资源是图片是,string表示的文件名。
当文件名是.xml文件时,调用另外一个函数取解析获取drawable,实际上你会发现,这种情况一班是,shape,selector之类的资源,里面还是Drawable最终还是会调用到下面else的流程来获取Drawable。
当文件名不是以.xml文件时,通过AssetManager获取drawable文件的InputStream,然后创建Drwable对象。
总结一下: 在图片资源获取流程中,首先是获取该资源的TypedValue,然后通过TypedValue的成员变量assetCookie和data 生成图片资源的key, 再通过key检查当前的图片资源是否已经在缓存中,如果没有缓存,就通过TypedValue的成员变量string找到当前图片资源的文件名。通过AssertManager对象找到文件的InputStream创建Drawable对象。
这个TypedValue是一个很重要的变量。大家分析其他资源,比如string,color,dimen,的获取流程,会发现这些资源的值,就保存在他的成员变量data中。

3.4 关于资源重定向思路分析
前面的分析已经大概了解Resources类,以及通过他获取资源的流程。而文档中也给出了,重定向资源,就是在应用通过Resources类来获取资源的时候拦截一下,返回主题的资源。
对于图片资源而言,其实就是在上面的流程中找一个地方,把代码段4{} loadDrawableForCookie函数分析中,通过AssertManager拿到InputStream创建的Drawable替换掉,具体替换的点,大家可以自己选,但是有一个问题应该考虑,那就是你也要遵从Android 对资源的缓存机制。 或者说自己加一套缓存,遵从图片获取的流程。
对于其他资源而言,也分析过,TypedValue的data成员变量,保存了他们值。

4 主题资源的存放格式:
在《MIUI主题风格_一种Android系统换肤功能的设计》 的文档里面已经说的很清除了,他也提到,主题资源需要手动解析的?这个手动解析是什么意思呢?
正常我们定义的string,color资源,在aapt编译的时候,就把这些资源对应的值已经解析出来了。我们可以通过Resources类的接口把值读出来。
因为主题的资源是没有被系统编译过的,所以需要手动解析,解析出来的值,应该和aapt编译string,color,dimen解析出来的值一样。
如果大家在手动解析过程中遇到什么问题,可以参考aapt是如何编译string,color,dimen资源的。
欢迎关注个人微信公众号,定期更新个人工作心得
这里写图片描述

阅读全文
0 0