Android插件化之资源动态加载

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,这就出错了。
解决方案有,在插件中如果有添加新的资源,则其命名要安装字典排序在原有的资源下递增。当然也有其他方案,自己琢磨吧。

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

(0)

相关推荐

  • 亲自动手实现Android App插件化

    Android插件化目前国内已经有很多开源的工程了,不过如果不实际开发一遍,很难掌握的很好. 下面是自己从0开始,结合目前开源的项目和博客,动手开发插件化方案. 按照需要插件化主要解决下面的几种问题: 1. 代码的加载 (1) 要解决纯Java代码的加载 (2) Android组件加载,如Activity.Service.Broadcast Receiver.ContentProvider,因为它们是有生命周期的,所以要特殊处理 (3) Android Native代码的加载 (4) Andro

  • Android应用开发中Fragment的静态加载与动态加载实例

    1.Fragment的静态使用 Fragment是作为Activity的UI的一部分,它内嵌在Activity中,多个Fragment可以把一个Activity分成多个部分,这在大屏幕手机或者平板电脑中会比较多的用到,这样就不用使用多个Activity来切换这么麻烦了.当然Fragment也可以不显示,只在后台处理一些数据,这篇文章中就暂时不谈到这个.以下来看怎么静态地在Activity的布局文件中添加Fragment. 自定义的Fragment通常要继承Fragment这个类,也有一些特殊的是

  • JavaScript插件化开发教程 (一)

    一,开篇分析 Hi,大家!今天这系列文章主要是说说如何开发基于"JavaScript"的插件式开发,我想很多人对"插件"这个词并不陌生, 有的人可能叫"组件"或"部件",这不重要,关键是看如何设计,如何做一个全方位的考量,这是本文的重点阐述的概念.我想大家对 "jQuery插件的方式"有一定的了解,我们结合这个话题一起讨论一下,最终给出相关的实现方案,来不断提高自己的谁能力. 二,进入插件正题 一般来说,j

  • Android动态加载Activity原理详解

    activity的启动流程 加载一个Activity肯定不会像加载一般的类那样,因为activity作为系统的组件有自己的生命周期,有系统的很多回调控制,所以自定义一个DexClassLoader类加载器来加载插件中的Activity肯定是不可以的. 首先不得不了解一下activity的启动流程,当然只是简单的看一下,太详细的话很难研究清楚. 通过startActivity启动后,最终通过AMS进行跨进程回调到ApplicationThread的scheduleLaunchActivity,这时

  • android动态加载布局文件示例

    一.布局文件part.xml: 复制代码 代码如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="

  • Android 中动态加载.jar的实现步骤

    首先第一个是 jar 文件的制作,Java 里面直接把 .class 文件打包到 .jar 文件里面就可以了,但是 Android 的 Dalvik VM 是不认 Java 的 byte code 的,所以不能直接这么打包,而要用 dx 工具转成 Dalvik byte code 才可以.当然,dx 工具转了之后,jar 包里面就不 是 .class 文件了,而是 .dex 文件. 第二个是,Android 里面虽然也提供了 URLClassLoader 的实现,但是并不能用.要动态加载其它类,

  • Android实现Listview异步加载网络图片并动态更新的方法

    本文实例讲述了Android实现Listview异步加载网络图片并动态更新的方法.分享给大家供大家参考,具体如下: 应用实例:解析后台返回的数据,把每条都显示在ListView中,包括活动图片.店名.活动详情.地址.电话和距离等. 在布局文件中ListView的定义: <ListView android:id="@id/maplistview" android:background="@drawable/bg" android:layout_width=&qu

  • JavaScript插件化开发教程 (三)

    一,开篇分析 前面两篇文章我们主要讲述了以"jQuery的方式如何开发插件",以及过程化设计与面向对象思想设计相结合的方式是 如何设计一个插件的,两种方式各有利弊取长补短,本系列文章是以学习为导向的,具体场景大家自己定夺使用方式.那么今天从这篇文章开始,我们就以实例的方式带着大家由浅入深的开发属于自己的插件库.嘿嘿嘿,废话少说,进入正题.直接上实际效果图: 大家看到了吧,这是一个选项卡插件,在我们日常做那种单页应用("SPA")的时候或许会接触到,就拿今天的例子来说

  • Android动态加载布局

    ListView我们一直都在用,只不过当Adapter中的内容比较多的时候我们有时候没办法去设置一些组件,举个例子: 可以看到京东的故事里面的这样一个布局,这个布局可以说是我目前见到的内容比较多的了,它的每一项都包含头像.姓名.分类.内容.图片.喜欢.评论.分享以及喜欢的头像.分析了一下布局之后我们不难发现,除了喜欢头像这部分,其余的都很好实现. 那么下面着重说一下这个头像这部分怎么实现? 第一种方案:我们可以用GridView来实现,GridView和ListView的用法是一样的,俗称九宫格

  • Android实现listview动态加载数据分页的两种方法

    在android开发中,经常需要使用数据分页,比如要实现一个新闻列表的显示,或者博文列表的显示,不可能第一次加载就展示出全部,这就需要使用分页的方法来加载数据,在android中Handler经常用来在耗时的工作中,它接收子线程发送的数据,并使用数据配合更新UI,AsyncTask是在一个线程中执行耗时操作然后把结果传给UI线程,不需要你亲自去管理线程和句柄. 一.使用Handler+线程方法 1.基础知识 Handler在android系统中,主要负责发送和接收消息,它的用途主要有以下两种:

随机推荐