Android系统资源访问机制的探讨

徐仙明:我是来自友盟的徐仙明,现任友盟Android SDK开发工程师今天我希望给大家带来关于Android平台上怎么样管理资源。

友盟是一家主要是针对开发者提供平台式服务公司,友盟是一家针对开发者的平台式服务公司,它为开发者提供统计分析工具、开发工具以及类似于“应用联盟”的SDK。

首先我介绍一下什么是资源,就是说在Android里面资源是什么东西,然后接下来简单地讨论一下都有哪些资源,资源的种类,以及如何去定义这些资源,然后接下来我们讨论一下在Android系统都提供哪些资源,就是我们这儿所说的系统更多的是指在Framework提供的资源,包括我们应用是什么资源,以及怎么处理这些资源,讨论完了以后我跟大家探讨一下一个比较有意思的话题就是换肤,对于应用以及对于系统怎么样更换皮肤,换肤这个事情在国外不是讨论的特别多,但是在国内用户对于皮肤就比较感兴趣了,比如说有兴趣换一个不一样的界面。

Android系统中什么是资源?

到底什么是资源呢?在Android系统中,资源主要指图片和MP3类型的声音文件,也是用户UI包含的所有元素。我们今天的话题主要是讨论跟UI相关一些东西,所以我把我的话题范围缩小到描述UI这些元素上面。比如说我们图片,比如说我们应用的图表还有布局文件,我们的SDK里面包含哪些部分,我们的字符串,还有一些RAW,其中很重要一部分是通过XML形式描述这些资源。

我个人认为谷歌设计Android系统的时候,他把界面的设计和代码逻辑分离开来,界面设计通过XML的形式来描述,后具体的程序的应用逻辑,通过代码来实现,所以他把代码和逻辑分离开来,其实界面更多是一个偏向UI工程师的一个工作,逻辑更多的是一个程序员所做的事情,是码农像我这样做应用的逻辑开发,这个和我们外部开发非常类似。我们外部开发会有前端工程师,怎么样写HTML、CSS、JS,我们的后端工程是用JSP、JAVA去实现它。

Android系统中有什么语言呢?

就是你能够发现有一个RES的文件夹,这个文件夹就是里面有各种各样的资源。我讲一个比较特别的叫values,如果大家看Framework的源代码,会发现Framework有一个volues

—,它可能跟语言相关,就是测算系统用中文还是英文,有可能跟分辨率有关,有可能和版本有关,所以当我们去开发一个应用想把这个应用做的比较普及,能够在不同样式的手机上都跑起来的话,我们可以针对每种不同的手机配制做资源。

简单打一个比方说我们的应用的用户既有中文用户也有英文用户,我们这个应用对于英文的用户打开的时候他想看到这上面的字符串是英文的,然后我们就可以把这个英文字符串放在values—下面。中文的我们可以建一个values,我们刚才说到Android里面是用XML形式来描述的。XML是用来描述数据的,所谓描述数据无非就是说我这个数据有一些属性,然后会有一些值,就是说它会有一个属性和值的一个结合。然后在Android里面就很好的利用了这些特性,因为Android里面我们所看到UI界面的东西其实就是标识属性。对于XML就是有一些限制,对于attr—values里面可以包含那些属性,至于具体属性的值会在哪里呢?就是我用它的时候存储数据的时候我会用在layout或者是style。我们的这些值在Android里面一些简单的类型,比如说我的E.g.TextView。

比如说E.g.TextView里面有一个小组件,TextView在这个文件下面,这是我从Framework源代码里面找出来的,它就描述了TextView的属性,在这里面它所拥有的一些属性,比如说这个叫BagText,下面就可以找到Text。这个过程是怎么样绑定的呢?右边就是TextView,比如说a是TypedArray,就是通过这个可以访问这个数值。

资源访问在Android性能架构中处于何种地位?

首先我给大家设一幅图,这个图是所有做Android开发的人见过的,几乎是跟Android相关的讲座会设计的一张图。这部分就是跟Android有关的,下面这部分是Linux跟Android没有关系。主要这部分是Android RUNTIME,这个是Java的一些语言库。对于这部分就是我们在做Android的开发中会用到的,比如说Framework .jar,还有一个Framework—res.apk,这个就是我们说的Framework所提供的资源包,上面是系统所提供的这个类的一些文件。对于我们核心的跟资源相关的一个组件是叫Resource Manager,通过这个访问一些文件系统。

我们回头看一下源码中的位置,左边是我们拿到的SDK的文档,里面显示我们所拿到的类包,我们几乎所有跟Resources,它的源码也在下面会有。在这儿我给大家推荐一个网站,其实大家有时候会想到说去看一看Android的源码,说把Android下下来比较大比较麻烦,Platform可以在上面看一看。右边这个就显示了我跟资源相关的东西都放在哪儿,就是Framework系统提供的Resources所提供的一些资源。

我们谈到系统资源,接下来说一下应用所提供的资源。当我们安装一个APK的时候不管是从网上下载还是从手机下载下来的,它会做一些检查,比如说对你的权限一些检查,但是本质上过程是非常简单,就是说它就把APK复制到你目录上。

这是我们自己开发的APK,对于系统所使用的APK,比如说一般Android操作系统出厂之后会带一些系统级别的应用,比如说打电话的应用、发短信的应用这都是核心的应用,会放在Syston下面。如果有的下付费应用会看到这个应用。

对于一个具体的APK里面包含什么内容呢?这张图解释了APK的结构,APK本身是一个很简单的文件格式,它只是一个压缩文件夹。我们可以通过解压压缩文件夹把这个APK解压出来,首先有一个META INF,这就是APK的原数据,可能我们打包APK的时候会有一些签名,签名的一些信息在这里面。还有MANIFEST这个大家很熟悉了。还有一个RES目录,这个里面有图片的资源,Layout也会用到,如果你的应用有XML也可以用。这个里面其实和你的工程目录还是有所不一样的。这个ARSC在网上关于格式的描述很少,可能这个本身没有开源。你可以通过AAPT可以把这个东西档出来,D表示档,把RES相关信息档出来,就可以看到和APK的东西了,每一个APK复原了一个值,对于系统资源它都以01开头,对于应用资源他都用07开头。对于CLASSES.DEX这个是W虚拟机专用的格式,本质上就是java变异之后的一些文件,这些文件会进行一些优化,生成一个CLASSES.DEX。这些文件在你安装的时候,实际上系统会把这个文件抽出来解压出来以后放在我的DALVIK—CACHE里面。这个目录里面有很多DEX的文件,如果说我要打开一个应用的时候,系统会做什么呢?打开一个应用的时候,系统加载这个系统对应的类,如果这个系统加载这个系统对应的类的时候他去APK的时候需要解压,这个效率比较低,所以他先会把这个DEX的文件放在这个上面,然后我加载起来就会比较快。

刚才讲到系统的资源和应用的资源,我们要知道其实在Android系统每一个应用都当做一个进程来处理,但是每一个应用都可以去访问Framework的资源,但是如果这样做,岂不是很带内存吗?就是我对不同的系统启动的时候用不同的资源,只有这个资源共享才能减少我内存的浪费。对于W虚拟机到底是怎么样做到,怎么样共享Framework的资源。对于应用的进程怎么启动?

首先系统启动以后会执行这个RC文件,这个ZYGOTE翻译过来就是一个卵,可以孵化出很多的其他进程。我们看细胞受精了会分裂,一分二,二分四,所以这个ZYGOTE也是非常类似的概念,它会本身FORK出一个SYSTEMSERSVER进程,ZYGOTE进程在启动的时候同时会加载Framework前面定义的类,还有加载那个资源,但是实际上它并没有说把Framework所定义的所有的资源都加载进去,因为对于Framework来说这些东西会比较大,而且Framework里面定义的资源和类并不是每一个应用都会用到的,他可能会把这些应用可能会更常用的独立出来,资源也是类似,就是说在这下面有一个表明了系统启动的时候加载了Framework的资源。当我一个应用启动的时候,ZYGOTE就会把自身FORK一遍,复制之后再去创建我应用对应一个进程,加载应用的一些代码,开始应用的运行。

我们看一下这个图,这个图本身是从有一本书叫Android内核剖析截取过来的,我们可以看DalvikVm 它启动的时候创造ZYGOTE进程,ZYGOTE会加载Framework共享类和共享资源,同时它会通过开启Socket服务端,每一个应用会创建对应的进程。我们会发现说,其实每一个进程它都认为自己有一份关于Framework共享类以及共享资源,但是实际上他们存在一段物理内存空间,我在整个物理内存空间里面只有一份关于我系统级别的资源,而当我从一个新的进程出来以后,我对于每一个内存没有修改我们可以共用一个内存,但是Framework类和资源是只读的,而且对于Android操作系统设计之初本身没有硬盘的虚拟内存换进换出机制,所以这一点对于Android来说非常重要,它节省了很多很多内存。我们看一下这个过程具体是怎么样实现的呢?

我摘抄了几段源代码出来。当我启动一个应用的时候,ZYGOTE所有应用的祖宗,它会复制自身,创建一个新的进程,就是PID。这个PID是运行在子进程里面加一些对应的类和资源就开始子进程的界面了。我们进入子进程以后,我们就开始应用运行了,比如说我要打开一个APK,他会调用我们这个APK对应的类,会调用这个入口函数,在入口函数里面会调用比如说我应用启动打开的时候我能看到我应用的界面,从而我这个应用对于用户来说就启动起来了,就开始运行,在我应用的进程空间里。

对于具体应用来说它怎么样访问资源呢?其实有两种方式,一种是通过Context,相信很多人用过Conext.getResources,在应用的时候它会调用一个函数,它会有一个管理资源的搜索中心下面,对于应用来说就可以访问这个资源了,就是在SDK文档里面画的图。这个是访问应用内部资源的时候这么做。我还可以访问其他的应用资源,其实大家可能会有疑问说,不是说每一个应用都有一个进程,每一个APK对应有一个进程,这句话听着貌似是正确的,实际上你可以使得不同的APK放在同一个进程里面,就是我们下面所讲的。

这个有什么意义呢?就是说我们做一些资源放在另外一个APK里面,安装别的APK这个应用可以通过,可以访问,我们可以把我们的皮肤放在别的APK里面,我们可以从网上下载APK,从而访问那个APK那个资源,从而达到换肤的目的,换肤也是大家比较感兴趣的话题,我先抛砖引玉一下。

先说对于应用级别的换肤,一种是说我简单的内置,非常非常简单的逻辑就是说我可能是说用户选择了设置了我使用第一种皮肤,皮肤无非就是图片这一类的资源,就是说我可能背景图片改一改,样子改一改,然后如果用户使用了第一种皮肤,我就使用第一张图片,如果用户用第二种皮肤,我就用第二种图片,所以我只需要通过一个简单的代码逻辑去实现,这要求我把这些资源都放在APK之内。另外一种方式就是说我这个皮肤外置可以下载,我可以复制,也可以动态替换。两种方式,一种是通过APK的方式,其实我们刚才在前一张里面讲到,不同的APK可以放在同一个进程里面,我在一个进程里面访问资源,就是说我这个资源必须在我这个进程里面,如果在别的进程里面就需要通过APC通讯很麻烦的方式。我把我对应的资源放在进程中,Android本身提供这个东西,如果我有两个APK,我在他们Android Manifest. XML文件中就可以访问资源了。

我们看这一段代码可以通过一个应用访问另外一个应用的包名,那个包名APK主要存放皮肤资源,我就访问这个包,通过这个包可以得到另外的包的资源,所以可以把皮肤的资源放在另外一个包里面。我们现在在市场上见到很多的桌面应用程序,有的就是通过这种方式来实现的—换肤。还有一种就是说我不用APK,我就普普通通的,比如说我要一个Skin目录板,放在这下面,我都是一直在这个下面去取资源,如果下面没有我要的资源,就调用我的默认资源。当用户换了一个皮肤的时候,这个应用就会下载新的皮肤文件,同时把原来的文件替换掉。我们所见到比如说墨迹、还有搜狗输入法,这些应用都是通过类似于这种方式去实现换肤功能。

说到应用级别的换肤 墨迹换肤的实现办法

我们看一下墨迹到底是怎么实现的。墨迹会有一个工具的页面,有一个皮肤小库,比如说这个里面有一个情人节的皮肤,我看一下我的目录文件发生什么变化呢?结果发现在SDK里面墨迹建立了一个文件夹,下载一个叫103.ZIP这个资源包,他把这个ZIP资源包解压Skin103这个下面,这是皮肤包含的资源文件。我定义这个文件名一定要跟系统默认的一模一样,我可以比系统里面的少,如果我找不到这些系统资源,可以到系统默认里面去找。

这是针对应用级别的换肤的方案,对于系统皮肤就是在网上找一些方案,相对来说比较复杂,对于系统来说很难去写一个APP,给系统设置什么主题这是很难做到的。我们想做到更换系统主题,就是涉及到修改Framework源代码了。一种方式我们简单谈到系统访问资源的时候是通过AssetManager这个类,提供Framework res.apk,如果我们要更换系统资源的时候可能是说去修改这个函数使得它去别的地方找资源,这样可能达到一个系统主题的目的,接下来MIUI工程师会更详细讨论这个问题。

另外一种方式因为我们在应用这个级别去访问资源的入口都是通过这个类来,这个类就是Java是通过一个类,如果我们想换系统主题,就让这个函数查找资源的时候,别让他去找Framework下面的资源,第一是01开头的话我们就返回,可以复制到我们自己的主题上面那样也可以。董红光会更详细解释这两种技术。主要是讲了在Android系统怎么样访问资源,系统的工程怎么样共享资源的,对于系统和应用它的资源访问有什么不一样,以及有什么相同之处。后面谈了一下关于应用和系统的换肤,这个是我今天所讲的全部内容。谢谢大家!


-------------------------------------------------------------------------------------------------

我先简单自我介绍一下。我叫董红光,是北航本科毕业的,所以今天来到北航的地盘感觉信心爆棚了。我现在是在小米担任MIUI系统工程师,主要负责的是MIUI主题这块,就是被很多人称为最酷最绚这一块。很多人对如何换肤很感兴趣,可能还有更多深入可以挖掘的点,所以今天跟大家分享一下。然后希望能有人受到一些启发可以做出更酷更绚的产品。

我今天的主题是MIUI主题风格,这是一种Android系统换肤工程的设计思路。

说到主题首先大家要想到主题是什么?比如说QQ有QQ的主题,Windows可以换自己的主题,比如说QQ有什么红橙黄绿青蓝紫,各种各样的主题,甚至论坛有的普通主题、文艺主题等等。主题在手机是什么样的呢?我们先回顾一个系统。

这个是塞班系统S60的界面,我现在随便在网上下载的主题包截图,我们看风格完全不同的,这个是被很多人称为明日黄花的塞班系统。

我们现在回顾一下比较火的系统,一个就是Android,一个是iOS,这些系统他们支持不支持,首先说iOS答案就是不支持,如果你要越狱还是可以做到这一点。Android支持不支持?Android其实它标称是支持的,它是怎么支持的呢?它实际上再一个XML声明写一个Style,然后在应用程序里面制定你这个主题用到什么样的Style,这个是程序显示指定的样式,就是我这个程序用Android 4.0等等一些主题风格。它能更改哪些东西?一般来说可以更改字体、颜色、长宽、间距等等这些东西,实际上真正的主题不是这些。所以说这个地方看到其实Android原生提供的对于可替换的资源类型是十分有限的。

这两个截图就是它不同主题下的展现的方式。右面那个图已经参杂了我们MIUI的元素了,比如说上面这个图表,这因为是小米手机上截屏,这个是原生的,其实它一般来说就是换一些现代颜色、背景的颜色等等这些,其实图片都可以不换的。这个就是我们目前市场占有率第一大的操作系统可以做到的效果。

刚才我说到这个是不足够的,回过头来我们说一下主题是什么东西?主题我自己认为的一点就是说所有影响用户界面效果的属性的集合都称之为主题。比如说音效、图片、切换动画都算,这些东西当然了之前提到的字体、颜色、长宽、间距等等这些,另外最重要的就是图片,其实我们看到的一个程序给大家一个整体的感受,可能更多就是它的一些背景图,按钮的样式,所以说图片这个地方是非常重要的。当然很多人是做程序开发的,大家也都懂,我们做程序开发有一个通用的办法就是把一些图片资源和样式称之为资源,在Android中刚才友盟工程师讲的非常透彻,实际上Android也有资源,实际上在我们认为系统和程序称之为资源。之前Android的一些应用程序本身是由开发者去制定的,我要用4.0,我要长成一个白色的样子,或者是黑色的样子,实际上真正需求远远不止这些。比如说我是又宅又孵的,你设计出来是比较清新的风格,我可以换一个风格搭配我的使用。第二点就是即时生效,就是说我如果换一个主题,不能说等很久,比如说Android系统是待着没事儿杀进程,这样的用户体验是很不好的。

接下来问题来了,就是怎么做?这个怎么做不是指主题包本身怎么做,因为平时我跟大家介绍我的工作的时候可能大家都会问你在做什么,我说我做主题,他们说你不是学计算机的吗?怎么回过来做美工了呢?其实不是。怎么做?有两方面,一个是主题报告怎么做,我们怎么样画出漂漂亮亮的图片,我们怎么样有各种各样的风格。另外一个方面就是我们系统如何支持他用了这样的主题,在我们的系统之上我们就能把他的图片显示出来。

首先我们就要看一下Android的资源管理机制。其实我写这张PPT的时候也很纠结,因为我不知道在座的同学们Android相关的知识多与少,因为可能有的人相关的知识很多,我介绍这些其实还是挺浪费大家时间的,不过后来我发现原来在我之前还有一个讲师,就是友盟的讲师先给大家做了一个快速的入门,我就假设大家是五十分钟精通了Android资源管理机制了,我就简单过一下,可能一些我们主题可能涉及到的一些点。

首先第一个就是资源的类型,刚才也提到了,资源的类型比如说Drawable、layout、style、string、COLOR,通过R.什么什么东西,系统同志RES这个类拿到你需要使用的东西是个颜色还是一个图片等等的。平常我们用的时候就是用Context.getresources这一类的东西,实际上它的底层就是这个。

回到刚才的问题就是我们怎么去做?首先想到第一个思路,刚才说了这么一大堆东西,之前友盟工程师也花了将近一个小时的时间讲我们整套的机制,大家首先想到的是什么?就是复杂。相当的复杂和麻烦,我第一个解决方法可不可以绕过这套复杂的机制,答案是可以。就是直接读取外部资源文件,你这一面是用R又是用RES什么的,我不用这些东西,你给我提供了Drawable就可以读取,比如说你写了一个按钮,按钮的背景你需要运行初始化的时候去传一个图片或者是传一个颜色。其实这个缺点就出来了,就是说满屏幕你为主题、你为你的皮肤都要写类似的代码。

看一下它的特点,首先第一点就是主题包格式非常灵活,就是说这个格式完全你自己去定义,你愿意定义成一个文件夹放所有也没问题,你愿意定义成自己压缩的格式也都没有问题,反正解析的操作是你自己来做的。

第二点刚才我也提到了,就是你自己需要手动管理,你在你的程序中你要去设置。不过它也有优点,它的优点就是可控换肤。比如说搜狗的输入法是支持皮肤的,他的皮肤可能只允许你换一部分东西,如果不是这样的话,我把搜狗的LOGO换成百度了,主题应用完了之后就成了百度输入法,这肯定不是搜狗想看到的,所以这个有很大的优点,就是LOGO不允许你去换。

下面一个缺点是什么呢?就是没有办法给系统和其他的程序进行换肤,为什么这么说呢?就是因为所有的代码都是写在你自己的程序中,你不可能影响到系统,也不可能影响其他的应用程序。所以最终我们觉得这个东西只适合APP级别的换肤。

说了这样一个思路,至少我们已经打开了一点思路了,我们再回头分析一下它。我们看到这么多东西,有什么东西可以改进,首先一点我们看到这个手动解析资源,可能大家不是很明白这是什么意思,其实Android的那一套资源管理有自己的组织方式,然后它也提供了相应的解析逻辑,如果你自己去指定这个主题包,你自己要去解析这个逻辑,大家说没有问题,反正我自己定,但是有些问题,比如说Android最常见就是可以给不同的机型运行程序的,可能有的机型分辨率很高,有的机型分辨率很低,有的及其DPI很高,有的是DPI很低的,这样Android提供了一套机制就是说在Drawable下面挂了很多的目录,这些目录分别去放为每种分辨率设计的它的图片,因为你可能高分屏要设计质量比较高的图片,低分屏要设计质量比较低的图片这很正常。所以这一部分东西,Android会帮你去做这些事情。比如说你是在一个低分屏下,但是你的程序并没有给低分屏做图片,不代表它压根不显示这个图片,Android会给你算,会优先读这些资源,这些事情Android已经默默帮我们做了,我们想抛弃它没有这么简单的。

我就在想,能不能把手工解析这个地方去掉,我让Android还去帮我做这个事情,但是我不让他帮我管理加载哪些资源,这就是我们的思路二。说来简单,但是我们需要找到技术支持,搜遍了SDK的方法终于找到了一个,就是PackageManager,比如说购物桌面,我没有看到它的代码,我觉得它的机制是这样的,就是它可以读其他第三方的一些为它做的一些主题,而其他第三方都是一些APK格式,装到你的系统中,它会通过这个方式,把第三方的资源全都弄进来,他所用的就是RES,而且这个东西并不受签名的影响,因为它是可以跨不同的作者、不同的签名去访问的。

拿到了它之后,我们成功一半了,因为拿到了其他人的RES,我们可以拿GET Drawable等等的,接下来就是我们自己去做的,如果有主题文件那么我们就去那个里面去找,如果没有主题文件我们就从我们自己里去拿。它的相对于前一种思路的优点出来了,首先它的包就是APK包,它的缺点就是制作门槛比较高一点,因为APK还涉及到打包、签名一些操作,可能一般的平面、UI设计师他们不会做这些东西,他们最多打一个技术包,系统会帮你解析这些所有的资源。

下面这些特点其实和上面的差不太多,手动管理、可控换肤,而且它也没有办法给系统和其他换肤,所以最后这个东西只是适合给APP级别做换肤。

如何才能真正实现Android系统换肤?

其实我们努力了半天,想了两种办法都没有做到给系统级别或者给其他的APP换肤。首先想到的一个就是偷换概念,什么叫偷换概念呢?你帮我买一个小米一代,我就给你拎一袋小米回去。实际上他管我要的是某一个资源ID,但是我假装不知道,我给他是另外一个,这个地方大家可以很容易想到,为什么我们可以做?资源是有规律的。O1开头的都是系统的ID,这样我们就可以做一些事情了,就是说我发现如果是0X01开头,我把这位从1变成7,我自己做一个APK,然后里面它的ID和这个是一一对应的,只是前面这个开头是不一样的,这是可以做得到的,这样别人给我要这个资源ID我就给他返回另外一个。而这个是要和PackageManager合作的。它的特点就出来了。下面就是自动管理、自动换肤,这个时候你的应用程序不需要感知我怎么样拿皮肤相关的一些东西了,我只要管RES去要,我要某一个ID的RES,这个时候我自己去解析一个映射文件,我把他的ID映射到那个文件资源ID,所以可以实现自动换肤。

有了这样一个特点之后,就可以系统换肤了,其实我们改的也是系统代码,因为我们是做ROOM的,所以有了Android的代码还是可以改一些东西的,所以我们改的是RES。但是它也可以为其他的APP换肤,但是比较麻烦。还是回到R的机制上来说,R本身是由APP编出来的,系统的还好,系统在编的时候还会出现一个文件囊,他会把系统常用暴露出来的ID,这样你影射很好影射,但是如果你没有这样的ID,你增加资源和删除资源这个ID会变的,变了就映射错了,你明明是为0X01做的,但是这面0X01已经变了,这个有很大的风险。在资源ID变化的时候它的解决是很麻烦的。

另外一种就是资源缺失的时候,我们做了什么?就是把这个由1换成7,读完了以后重新返回一个数据,你原来的资源在新的主题包没有怎么办?你可能判断一下这个主题包有没有,有的话再返回这个,没有你就返回原生里面了,所以还要做一些额外的处理。

这个看起来是可行的,但是有很多麻烦的地方,而且为第三方APP换肤麻烦这是我们当时考虑到最重要一个缺点,所以说我们把它砍掉了。砍掉了之后怎么办呢?再往底层走。

再往底层走RES这一种方式不行,下一就是AssetManager,我们也看了AssetManager的代码,它有一个AddAssetPath,每一个程序启用的时候,就是用它路径实现过来,然后把自己的RES加进去,一般是这两个,当然额外也有别的情况。

比如说这样一个东西以后我们想到了,对于系统换肤来说,第一个特点这个主题包必须是APK的可以自动解析,可以自动管理,也可以实现自动换肤,这个最主要一点就是可以为系统和其他的APP换肤,就是我直接把原来的APK换掉了,任何一个系统或者是一个APP资源都是存在APK里面,我把APK替换了。

在底层整合过程中也许会出现一些问题,主题包资源缺失时怎么办?某些地方出现空白甚至图片无法显示时怎么办?

实际上两种办法,一种办法就是资源缺失在做主题的时候先把之前所有的这些全都解开,开开之后我需要哪一个就用哪一个,全部打包过去,这个对于主题包的负担很重的。第二个办法就是我在最底层做,在上层也要做,我一旦资源缺失了我再到新地方去找,这个改动是很大很大的,因为上层的话不会判断同样的ID怎么去处理了,因为你可能他俩虽然是不同的APK,但是他们两个同样一个资源共用一个ID,这个时候系统处理不了,我们还要进行各种各样的处理,很麻烦。这种办法也不是很好。有没有更好的办法呢?

刚才说的是对整个资源包进行一个重新设计,接下来就想我们可不可以为单独某一个文件进行设计,实际上我要的是一个小米手机,我回头其实给了你一个大米手机,你这面其实要是人的话肯定不行了,是你坑我,但是实际上程序是可以的,因为程序它是非常信任这个Framework,所以在这个地方我要还是要原来的东西。我要了原来的东西,我在RES这一层,我直接不读APK,我到一个新的地方去读,这是在思路三和思路四之间一个折中。思路三是我改你的资源ID,思路四就是我不改你的ID,我把底层全改掉,现在这个折中办法出来了,就是我不改你的ID,但是我对你的底层进行管理,你可以自己定一套格式,所以既支持手动又支持自动的解析资源。它的特点就是自动管理、自动换肤,同时也可以为系统和其他APP换肤,因为所有的APP都要通过RES这个入口去访问资源。这个时候资源一旦缺失的时候,它的解决方法非常简单,因为我们在RESGET的时候GET不到我就什么都不做就直接返回。

这个是我刚才提到五种思路,这个地方列了一个表,大概对比了一下。第一排就是主题包的格式,这三种是APK,这两种是任意,也可以用APK的方式,APP对显式管理,前两种是是,后两种是否。系统换肤前两者不支持,最后三种是支持。

在资源缺失时MIUI又会采取何种策略呢?

MIUI怎么做呢?其实刚才也分析了半天了,给了大家五种解决方法,我们采用思路5,重定向资源文件路径,就是把入口里面的逻辑改了,我们的入口就是RES类,我们更改它,截获所有资源的请求,首先在我自己里面判断,如果有就返回,如果没有交给系统去做。我们的主题包是自定义格式了,我们通过一些优化做一些处理。我们还是为了方便起见,我们每个安排APP一个资源包,主题包没有资源就返回原生资源。

这是很简单的一个结构图,APP通过RES这是我们改过的接口,现在这个地方加了一个HOOK调动我们自己的代码,背后相对有一套逻辑,但是这个地方只有一行,加进来我们去判断是不是存在,如果存在就这一个地方去找,找到就返回,如果不存在就交给原来的。

刚才说到主题包,分析一下这个主题包格式,主题包格式不是APK,但是实际上我们为了方便主题方式有点类似,我们也是用ZIP包的格式,但是不用签名也不需要变异相关的操作,很简单就是一个ZIP,ZIP里面是一个局部项目集合,这个局部项目很多了,有一方面是大家经常用到的一些东西,比如说图表、壁纸、来电铃声、开机动画和音乐这些东西,这些东西走的不是那些机制,这些东西都有自己独特的方式,如果有时间我可以继续给大家讲。其他的一些资源就是我们刚才所提到的比如说你给QQ换肤,这个是以APP为单元组织的,每个APP是一个小的主题包,这个主题包它也是一个ZIP格式,它里面包含了哪些呢?一方面就是Drawable,就是刚才说到的图片,它的层次结构也是一样的,Drawable下面如果你给高分屏做了一些东西,你就放在Drawable—HDPI,这个也是为了让我们通过Android一些东西去获取,不需要自己去算,考虑到这个问题。

刚刚提到颜色等等都统一在THEME values.XML,这个也是考虑到主题制作者对这一套结构完全不感冒,他们觉得怎么简单怎么来。很多人原来就没有编程的知识。

我们看一下THEME—Values.XML是什么东西。书写方式与Android的COLORS.XML dimens.XML Strings.XML,对于程序制作者来说是完全透明的,我们只是解析它做一个替换。

刚才提到这一个,可能如果要是做Android开发比较多的有一个问题就是说其实你这个东西局限性很大了,比如说你REFERENCE类型是不支持的,你也不支持多值属性,比如说我把一个ARROYS、style里面的数值替换掉这个也不支持。我想把自己设计一个风格支持不支持?不支持。我们考虑到主要是一个BAR原则,大部分APP视觉的元素和主题的元素大部分定义在刚才说到的这些地方,比如说COLOR这个是比较常用的,另外就是图片,所以我们做了一些取舍,反正这个地方也是可以做的,只是说这个地方的代码不能拿Android自己的代码了,我们可能还要自己重新写,重新解析。

大家说你不支持多值属性,layout你最得支持吧?我这个布局、大框架、上面是抬头、下面是按钮,这个我们也可以做到,但是我们不支持。为什么不支持呢?还是涉及到一个安全的考虑。比如说他随便调整了一个位置,他把确定和取消给换了一个位置,可能大家觉得没什么,但是使用者会觉得特别扭,你不小心点了弹出框架你习惯性点右边就不好了,所以这个地方利用不好就会有很多潜在的风险。比如说他改了一些拨号键盘的位置,1234567890完全错开了位置,程序就没法用了。

说到刚才主题包的格式以及它的局限性,接下来说主题包怎么解析。主题包解析两大部分刚才也提到了,第一部分就是独立的元素,图表、字体、壁纸、音效、开机动画等等,这些有Android的接口就可以像图表、壁纸、音效,但是有的得改更底层的代码,比如说字体和开机动画这些东西。

我们看其他的APP包,我们一次性解析THEME—VALUES的所有值,为什么一次性把所有东西都加进来,也可以做到你用什么就加什么,但是一个正常的主题包为一个APP设计的更改的样式不是特别多,所以我们一次性解析了可能提高效率。

接下来就是Drawable,图片不能一次性都解析,这个地方跟Android是一样的,它只有在使用的时候才去进行加载,加载之后Android的方式实际上在GETDrawable读一个图片的时候,它有一个缓存一个表,它会看一下这个地方是否之前已经加载过了,如果没有加载过他再去加载,实际上我们这个地方就是,如果没有加载过,我们先用我们的东西去加载,加载不到再用他的去加载。加载完了之后他会统一把这个加载之后的东西放在系统缓存中,下次再访问的时候就会很快。这是他的机制我们充分利用了他的机制。当然我们也可以在系统缓存之上自己去建立一套自己的缓存机制也是可以的,但是当时我们觉得没有必要的。

刚才说到的一点就是每个APP实际上每个都是ZIP包,ZIP包打开非常耗时,所以我们对于每个程序来说它其实用到APP包是有限的,用到的资源包是有限的,我们会有一个文件池,这个文件池会存他所有需要用到的APP包的距离,这个也是最近经常使用的方式这样一套逻辑,如果不常使用的东西就直接释放掉了。

刚才说到了那些东西大家可能觉得说我们整个主题的功能应该基本上可以满足了,其实也差不多可以满足大部分功能,但是还有一些点是需要考虑的,首先第一个点就是局部更改全局样式,这是什么意思呢?比如说短信里面经常用到系统的一些资源,它的列表它可能用系统的一些资源,但是我为短信专门做了一个皮肤,这个时候我不希望把系统整个所有的列表全都换掉,如果都换掉因为我只给短信做了这点东西,你把短信换了,其他程序就不搭了,我们引用了一个OVERLAY机制,还有主PACKAGE这个概念,主PACKAGE本身是一个主题包,这个主题包中如果有什么东西我是系统或者是其他第三方APP,但是我不希望他们用到,我直接定义在这里面,这里面是一个目录,然后用同样一个结构,然后我们会优先去看,他自己有没有这样一个资源,如果自己有这个资源我就优先加载这个,如果自己没有这个资源我再加载系统主题的图表,这个就是主PACKAGE的概念。

第二个相对更深入一点就是说家单说一点MIUI这边是分几块,一个是原生资源,就是Android给了我们这些图和样式,但是MIUI大家不知道用没用过,如果用过它和原生还是不太一样,我们修改很多东西,这个其实是我们定义到自己的资源里面去了。接下来就是用户他可能要去换这些主题,换这些图片等等这些东西,但是我们又不希望说他那个主题包没做到就去读原生,我们不希望做到这个,我们希望是如果他没有了读这个,所以这个地方引入了分层概念,就是主题包实际上分多层的,每次找的时候是从上到下一直找,如果没有就用原生。不同的层级可以指定接受资源类型,这个不详细展开了。

接下来刚才提到了有一点,怎么去更改,第一个是用户指定,我们刚才说到就是可以做到了,就是用户指定把一个主题包扔过来,你就帮我显示,另外一点就是即时生效,我怎么做到我应用之后它就会生效。回顾一下刚才五种思路,其实这五种思路都有一个共同存在的问题就是资源的刷新问题,实际上我换了这个资源我期待着系统是把这个图片给我换了,但是其实不是这样的,系统做了很多资源访问的优化,他有大量的缓存,还有不同层次的缓存,比如说APP级别还有预加载的资源,这些预加载的资源其实是不同进程共享的物理空间,还有大量的缓存怎么办?我们要清掉它,清掉它怎么办?一般通用的办法很简单就是杀进程,杀进程你起来以后会重读你的资源,但是这些值是适用于这个层次,而预加载这些东西是不变的,所以你再杀进程,这个进程起来以后读的东西还是来的。最后我们没有办法只能重启手机,我们MIUI当时主题风格最开始那版一直沿用的方法,因为没有找到太多太好的办法。当然了后来有一天我们找到了一个宝贝。叫Configuration,这个是什么呢?横屏竖屏我可能有不同的布局,横竖屏一切换就会帮你重新更改数据,或者是键盘显隐,这个就是在Configuration去实现。如果你们不去做这些东西,系统很简单,没什么别的,充气你的Activity,这个Activity是一个站。有了这样一个东西,其实我们当时就觉得黑夜中的一盏明灯,我们由此想到给主题定义一个新的Configuration类型,这个问题就来了,实际上大家写程序,当然我们是很希望以后大家都用MIUI的SDK开发,但是很可惜不是,大家一般写程序还是基于Android原生的开发,这样不会管我们新定义的Configuration,他也不会去注册,我们自己去维护这个Configuration,我们自己判断你换完了之后,比如说你现在在用短信,用到我这个新的主题包没有短信,我们不需要重启,也不需要管理了,如果你主题包有短信,没办法我重启你的Activity,这样就会重新读资源,当它去读的时候,这个时候由我们自己的代码去接管,这时候就可以读到新的资源了。这个地方就是我们资源刷新的机制。

两点第一点用户自己制定,第二点即时刷新。这个就是我们核心的两个功能点。最后大家可以看到我们可以实现五彩斑斓的主题风格。



  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值