欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android插件化之资源动态加载

程序员文章站 2024-02-27 16:24:27
android插件化之资源动态加载 一.概述 android插件化的一个重要问题就是插件资源访问问题,先列出会面对的问题 1.如何加载插件资源 2.如何...

android插件化之资源动态加载

一.概述

android插件化的一个重要问题就是插件资源访问问题,先列出会面对的问题

1.如何加载插件资源
2.如何处理插件资源与宿主资源的处突:插件化资源问题要做到的效果是,如果我们要获取的资源在插件中找得到,则加载优先加载插件的,如果找不到,则到宿主资源中找。这样能做到动态更新的效果。
3.如何确保插件和宿主使用到的是被修改过的资源。

二.原理分析

在做一件事之前必须先弄清楚原理,所以,这里先要弄清楚android的资源体系原理。

1.资源链

 Android插件化之资源动态加载

context:一个apk里面其context的个数为application+activity+service的总和,因为他们都是继承context的,然而context只是一个抽象类,其真正的实现类是contextimpl,那。拿activity来说,在activity的启动流程中,会在activitythread的performlaunchactivity()方法中调用activity的attach方法把contextimp实例传给activity(即赋值给activity内的成员变量mbase)。

Android插件化之资源动态加载

resources:contextimpl内有一个resources的成员变量mresources,代表的是应用的资源,我们平时在调用getresources()方法获取到的是该resources。

assetmanager:resources内部的一个重要成员是assetmanager(massets),其指向的是apk的资源路径,资源的获取最终都是通过它来得到的。这里需要注意的是assetmanager并不是resources独立持有的,也就是说系统在获取资源的时候不一定是通过resources获取的,有时候是直接通过assetmanager来获取,比如typedarray,之前就踩过这个坑。 

2.android是如何构造一个应用的资源的,并且是如何传递给我们使用的,这个要讲的东西非常的多,可以看另一篇文章,这里主要讲资源插件化。

三.问题的解决方案

1.加载插件资源

资源的加载最后是通过assetmanager内的一个方法addassetpath(string path)

Android插件化之资源动态加载

该方法接收的参数是插件apk的路径,内部会调用native方法把插件apk对应的资源加载进来。然而该方法是hide的,我们不能直接调用,所有只能通过反射。

 Android插件化之资源动态加载

这样就成功构造出一个指向插件资源的assetmanager。当然这时候还不能使用,还要调用assetmanager的ensurestringblocks()方法来初始化其内部参数,同样得使用反射。

Android插件化之资源动态加载

2.如何解决插件资源与宿主资源的处突

如果使用到的资源,插件和宿主都同时存在,则使用插件的资源;如果使用到的资源只有插件有,则使用插件的;如果使用到的资源只有宿主有的,则使用宿主的。

assetmanager的addassetpath()方法调用native层assetmanager对象的addassetpath()方法,通过查看c++代码可以知道,该方法可以被调用多次,每次调用都会把对应资源添加起来,而后来添加的在使用资源是会被首先搜索到。可以怎么理解,c++层的assetmanager有一个存放资源的栈,每次调用addassetpath()方法都会把资源对象压如栈,而在读取搜索资源时是从栈顶开始搜索,找不到就往下查。所以我们可以这样来处理assetmanager并得到resources

 Android插件化之资源动态加载

其中dexpath2为宿主apk路径,dexpath为插件apk路径,superres为宿主资源,resources为融合插件与宿主的资源。

3. 如何确保插件和宿主使用到的是被修改过的资源:
这是很重要的一步,之前我们已经成功获取资源并对其进行修饰,现在要做的是用它替换掉android为我们生成的那个资源,这就是hook的思想。

使用到资源的地方归纳起来有两处,一处是在java代码中通过context.getresources获取,一处是在xml文件(如布局文件)里指定资源,其实xml文件里最终也是通过context来获取资源的只不过是他一般获取的是resources里的assetmanager。所以,我们可以在context对象被创建后且还未使用时把它里面的resources(mresources)替换掉。之前说过,整个应用的context数目等于application+activity+service的数目,context会在这几个类创建对象的时候创建并添加进去。而这些行为都是在activitythread和instrumentation里做的。

以activity为例,步骤如下:

a: activity对象的创建是在activitythread里调用instrumentation的newactivity方法

activitythread:

 Android插件化之资源动态加载

instrumentation:

 Android插件化之资源动态加载

b: context对象的创建是在activitythread里调用createbasecontextforactivity方法
activitythread: 

Android插件化之资源动态加载

c: activity绑定context是在activitythread里调用activity对象的attach方法,其中appcontext就是上面创建的context对象
activitythread:

 Android插件化之资源动态加载

d: activity的oncreate()方法的回调是在activitythread里调用instrumentation的callactivityoncreate()方法
activitythread:

 Android插件化之资源动态加载

替换掉activity里context里的resources最好要早,基于上面的观察,我们可以在调用instrumentation的callactivityoncreate()方法时把resources替换掉。那么问题又来了,我们如何控制callactivityoncreate()方法的执行,这里又得使用hook的思想了,即把activitythread里面的instrumentation对象(minstrumentation)给替换掉,同样得使用反射。步骤如下

a: 获取activitythread对象

activitythread里面有一个静态方法,该方法返回的是activitythread对象本身,所以我们可以调用该方法来获取activitythread对象

 Android插件化之资源动态加载

然而activitythread是被hide的,所以得通过反射来处理,处理如下: 

Android插件化之资源动态加载

b: 获取activitythread里的instrumentation对象 

Android插件化之资源动态加载

c: 构建我们自己的instrumentation对象,并从写callactivityoncreate方法
在callactivityoncreate方法里要先获取当前activity对象里的context(mbase),再获取context对象里的resources(mresources)变量,在把mresources变量指向我们构造的resources对象,做到移花接木。

 Android插件化之资源动态加载

myinstrumentation:

 Android插件化之资源动态加载

d: 最后,使activitythread里面的minstrumentation变量指向我们构建的myinstrumentation对象。 

Android插件化之资源动态加载

代码 

Android插件化之资源动态加载

四.应用
资源动态加载的一个应用当然就是android插件化方面的使用。还有一个应用就是换肤功能,只需要在在工程里添加这些代码(当然还要处理一些逻辑),然后用户想要给应用换皮肤,主题等,即可从后台下载插件apk,放在指定文件夹就可以关系应用的资源,起到换肤的效果。当然,资源动态加载还有其他应用方法,自己琢磨咯!!!

五.存在问题

1.兼容性问题,因为hook要使用反射,从而来获取系统hide或类的私有属性。把它们隐藏是因为它们的不稳定性,如果哪天google觉得那个变量的名称起的不吉利给改了,那就报错了。当然解决方法还是有的,就是为不同的api写不同的代码。

2.r方面的问题。当我们添加了一个资源(如在string.xml里添加了一个string),则系统会为我们在r里面为该资源生成一个int型的id与之对应,使用的时候是根据该id找到对应的资源。资源id是按照资源名称的字典顺序来递增的。拿string来说。
假如我们的string.xml里声明了名称为za,zb的资源

 Android插件化之资源动态加载

则会在r里面生成相应的id 

Android插件化之资源动态加载

基于上面的观察,我们会发现一个问题:举个例子
宿主资源情况为:存在za(id=0x7f060004)  zb(id=0x7f060005)
插件资源情况为:存在za(id=0x7f060004)  zab(id=0x7f060005)   ab(0x7f060006)

这时候在宿主里获取资源zb,则根据上面所说,会根据id=0x7f060005先存在插件资源,这时候得到的是zab而不是zb,这就出错了。
解决方案有,在插件中如果有添加新的资源,则其命名要安装字典排序在原有的资源下递增。当然也有其他方案,自己琢磨吧。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。