DIY Android之一--原生Android系统主题支持的设计和实现
口号:Android只是个Demo。
智能手机何其多,Symbian、WP、Android...,问题是原生的Android系统不支持主题定制。
于是我等看着花哨的主题资源包在市场上泛滥,前提:你先下载一个运行这些主题资源的应用程序APK包先。
但是...... 原生Android系统是不愿意还是不能够支持主题呢?以后会不会支持呢?
不管了,既然看Android原生主题支持功能不够,本文就来尝试一下如何通过修改Android原生代码来实现主题支持。
============================= 环境与步骤=================================
软件版本:Android JellyBean
步骤:
I.原生Android主题支持需要涉及的部分
II.涉及部分在原生系统中的逻辑及修改
III.对周边模块的影响和支持
=============================原生Android主题支持需要涉及的部分=================================
既然是要支持主题,也就是说系统需要支持和管理多套主题资源包,并且应用可以用相同的资源ID来访问不同资源包中对应的资源。
因此我们首先分析一下Android系统的资源访问流程。
对应用来说,资源访问主要有下面三种方式:
第一、比较普遍的方式是使用xml定义,并且通过AAPT工具生成一个R文件,列出资源的索引来让Android系统自己去遍历整个资源树的方式来访问。
第二、通过Resources接口来访问,使用Resources类的getDrawable、getString等接口来获取资源。
第三、通过AssetManager类的接口去访问,使用这个类的open方法来返回一个InputStream对象得到资源。
其实这三个访问方式只是Android资源访问中整个流程中在不同层次对外提供的三个接口,到底层的实现都是殊途同归的。
第一种的xml定义的资源解析的逻辑在Resources.java文件的loadXmlResourceParser函数中,实际上也是调用的第三类接口,如下图:
因此我们实际上需要修改的部分主干是在访问资源具体路径前,按照当前系统主题设置访问不同资源APK下的文件
即是把原生Android中资源ID和资源文件路径之间一对一的关系改为一对多的关系。
例子如下:如果原有资源ID和资源文件路径关系为:
R.drawable.image01 = 0x7F020001
通过系统的资源查找之后找到文件路径为 /system/app/frameworks-res.apk下的res/drawable_hdpi/icon.png
在AssetManager native中去读取资源并上传。
那么我们需要做的是在传入路径去读取资源时把文件路径替换为/data/app/SystemTheme01.apk下的res/drawable_hdpi/icon.png
=============================涉及部分在原生系统中的逻辑及修改=================================
为了完成上篇所提到的修改,我们依上图分析一下原生系统中从传入资源ID到生成需要访问的资源文件的路径的过程:
Java层:
Resources类:
Android在Java层为应用层访问不同类型的资源提供了一系列接口,这些接口被封装在Resources类中,例如访问字符串资源的接口getString(),访问Drawable类型资源的接口getDrawable()等等。该类还管理着另外两个重要的类:Configuration和AssetManager。
Configuration类:
Configuration类中主要保存了当前的系统配置信息,例如字体、语言等,在应用调用Resources类的接口去获取资源的时候,Resources类会通过Configuration类来读取当前的系统配置信息,再结合接口中收到的应用请求获取的资源ID来获取对应的资源。
AssetManager类:
AssetManager类提供了以数据流的方式访问应用程序资源的方法。它主要是通过Native层的方法来实现访问资源的。AssetManager管理的资源主要有两个来源:一是应用程序的资源,通过访问应用程序的APK文件来得到;另外一个是系统资源,是通过访问包含系统资源的APK文件来获得。
Native层:
AssetManager Native类:
AssetManager Native类提供了对资源文件路径以及资源文件的操作支持,提供了诸如扫描APK的res路径下的文件夹和文件、对APK进行解压缩、维护ResTable类以及部分Cache加速文件的功能。
Asset类:
Asset类是所有类型资源类的基类,它提供了绝大部分对资源的操作实现和定义,是Android资源访问和文件访问层之间的桥梁,也是需要修改主干部分的核心。它封装了整个Android资源访问中对资源文件的路径查找以及资源从文件到数据流的转换过程。在应用传入资源ID来请求资源的时候,AssetManager会使用该类的create函数去生成一个新的资源对象,并使用读出的资源数据流赋给该对象,最后返回给上层应用。
下图列出了系统原有逻辑及需要修改的部分:
上面的部分已经分析了资源访问过程中需要修改的部分,但是除了访问流程的主干之外,
系统在以下几个部分对资源访问效率做的优化同样会受到我们修改资源ID对应关系的影响。
系统的预加载资源流程
原有逻辑:
系统在启动的过程中会把系统常用的资源进行预载入,
此逻辑位于frameworks/base/core/java/com/android/internal/os/ZygoteInit.java -- preloadResources函数
在此函数中会调用Resources.java中函数来对com.android.internal.R.array.preloaded_XXX的资源数组中对应的资源进行预载入,
这个流程中会通过for循环预加载三类资源Drawable、ColorStateList、ColorDrawable,
并且存在对应的sPreloadedDrawables、sPreloadedColorStateLists、sPreloadedColorDrawables三个LongSparseArray的数组中。
修改逻辑:
因为主干部分已经对系统资源即framework-res.apk的资源进行了分别的处理,所以开机预载入的部分可以不做处理,
但是在主题变化之后,预载入的资源还是系统启动时候的数据,所以需要对三个数组进行清除或者启动后台服务重新进行预载入流程。
修改文件:Resources.java
实现功能:在系统使用的主题包变化之后清除预载入资源数组即上面提到的三个LongSparseArray数组(可以考虑后台重新载入新主题资源数据到数组中)。
系统访问中的资源缓存机制(清除cache)
原有逻辑:
在系统每次访问资源的过程中,查找资源的顺序是 预载入资源->Cache资源->资源文件
并且在每次通过资源文件的方式查找到资源之后会把此资源加入到Cache中,加速下次访问速度。
修改逻辑:
在主题设置被用户修改之后,原有缓存中的数据是上个主题包的文件内容,需要清除。
============================= 对周边模块的影响和支持=======================================
另:需要提供用户可以管理主题包的应用程序
原有逻辑:(无)
修改逻辑:
用户在安装了主题包之后,需要提供一个应用来维护当前系统上已经存在的资源主题包。
应用需要实现功能:扫描、预览、应用、卸载当前手机上已经安装的主题资源包。
主要涉及:PackageManager接口、systemProperty读写。
主题改变之后通知应用的广播
原有逻辑:
系统原有通知机制是ACTION_CONFIGURATION_CHANGE的Intent来通知应用系统配置改变,
如果应用不处理,系统会默认为应用更新系统配置(Activity资源、布局等的重新载入)。
修改逻辑:
在系统判断是否需要发送CONFIGURATION_CHANGE的逻辑中加入如下逻辑:
如果当前系统主题资源包发生变化,需要发送CONFIGURATION_CHANGE。
草草完成Demo设计和实现,仅仅算是把功能实现了,欢迎大家继续优化、讨论。