当前位置: 移动技术网 > 移动技术>移动开发>Android > Android插件化之资源动态加载

Android插件化之资源动态加载

2019年07月24日  | 移动技术网移动技术  | 我要评论

android插件化之资源动态加载

一.概述

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

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

二.原理分析

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

1.资源链

 

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


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

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

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

三.问题的解决方案

1.加载插件资源

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


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

 

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


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

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

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

 

其中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:

 

instrumentation:

 

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


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

 

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

 

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

a: 获取activitythread对象

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

 

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


b: 获取activitythread里的instrumentation对象 


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

 

myinstrumentation:

 

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


代码 


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

五.存在问题

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

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

 

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


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

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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网