文章首发:Android应用程序插件化研究之DexClassLoader|大利猫

最近在研究Android应用的插件化开发,看了好几个相关的开源项目。插件化都是在解决以下几个问题:

  • 如何把插件apk中的代码和资源加载到当前虚拟机。
  • 如何把插件apk中的四大组件注册到进程中。
  • 如何防止插件apk中的资源和宿主apk中的资源引用冲突。

就这几个问题,我开始研究插件化开发实现的相关技术。在上篇文章中我讲了如何把插件apk中的class加载到当前进程的问题,本篇文章主要讲第一点的第二点:如何加载另一个apk中的资源到当前应用中。


AssetManager介绍

当我们在组件中获取资源时使用getResource获得Resource对象,通过这个对象我们可以访问相关资源,比如文本、图片、颜色等。通过跟踪源码发现,其实 getResource 方法是Context的一个抽象方法, getResource的实现是在ContextImp中实现的。获取的Resource对象是应用的全局变量,然后继续跟踪源码,发现 Resource 中有一个AssertManager的全局变量,在Resource的构造函数中传入的,所以最终获取资源都是通过 AssertManager 获取的,于是我们把注意力放到AssertManager上。我们要解决下面两个问题。
一、如何获取 AssertManager 对象。
二、如何通过 AssertManager 对象获取插件中apk的资源。
通过对 AssertManager 的相关源码跟踪,我们找到答案。
一、AssertManager 的构造函数没有对 api 公开,不能使用 new 创建;context .getAssets() 可用获取当前上下文环境的 AssertManager;利用反射 AssetManager.class.newInstance() 这样可用获取对象。
二、如何获取插件 apk 中的资源。我们发现 AssetManager 中有个重要的方法。



/** Add an additional set of assets to the asset manager. * This can be either a directory or ZIP file.* Not for use by applications. Returns the cookie of the added asset, * or 0 on failure.*@{hide}*/public native final int addAssetPath(String path);



我们可以把一个包含资源的文件包添加到assets中。这就是AssertManager查找资源的第一个路径。这个方法是一个隐藏方法,我们可以通过反射调用。



AssetManager assetManager = AssetManager.class.newInstance() ; // context .getAssets()?AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath);



上文提到,我们可以获取当前上下文的 AssetManager,也可以通过反射创建一个 AssetManager。我们这里本文分析的是后一种。第一种能不能呢,这个问题留给读者先去思考,后续文章会单独讨论。 详细了解可以参考老罗的文章 《Android应用程序资源管理器(Asset Manager)的创建过程分析》 。下面我们来写一个demo:获取插件的文本资源和图片资源。



创建一个插件apk工程module:apkbeloaded

我把插件的包名命名为:laodresource.demo.com.loadresourcedemo。
第一步:
这个工程中我们可以不用写任何代码。在drawable目录下放一张图片:icon_be_load.png。
在string中定义字符串:<string name="text_beload">I am from apkBeLoaded</string>。
第二步:
打包生成apk:apkbeloaded-debug.apk。

拷贝到测试机文件路径下:$ adb push <你的路径>/apkbeloaded-debug.apk sdcard/


创建一个宿主apk工程


在布局文件中定义一个文本控件和一个图片控件,分别用来显示插件中获取的文本和图片。



<ImageView android:layout_width="wrap_content"           android:src="@mipmap/ic_launcher"           android:id="@+id/icon"           android:layout_height="wrap_content"/><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:id="@+id/text"    android:text="Hello World!"    android:layout_below="@+id/icon"    android:layout_centerInParent="true"    />



MainActivity.java
onCrease:
@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    ImageView imageView = (ImageView) findViewById(R.id.icon);    TextView textView = (TextView) findViewById(R.id.text);    /**     *  插件apk路径     */    String apkPath = Environment.getExternalStorageDirectory()+"/apkbeloaded-debug.apk";    /**     *  插件资源对象     */    Resources resources = getBundleResource(this,apkPath);    /**     *获取图片资源     */    Drawable drawable = resources.getDrawable(resources.getIdentifier("icon_be_load", "drawable",            "laodresource.demo.com.apkbeloaded"));    /**     *  获取文本资源     */    String text = resources.getString(resources.getIdentifier("text_beload","string",            "laodresource.demo.com.apkbeloaded"));    imageView.setImageDrawable(drawable);    textView.setText(text);}


创建Resource对象:


public Resources getBundleResource(Context context, String apkPath){    AssetManager assetManager = createAssetManager(apkPath);    return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());}



创建AssetManager对象:


private AssetManager createAssetManager(String apkPath) {    try {        AssetManager assetManager = AssetManager.class.newInstance();        AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(                assetManager, apkPath);        return assetManager;    } catch (Throwable th) {        th.printStackTrace();    }    return null;}

到这里,我们完成了插件研究的第一个问题:加载插件apk的class和资源。下一篇文章开始研究插件中的组件注册问题。

markdown

Demo源码: https://github.com/liuguangli/LoadResourceDemo

更多相关文章

  1. [置顶] 基于Proxy思想的Android插件框架
  2. 百度语音合成(TTS) 在Android的使用方法
  3. Android通讯录管理(获取联系人、通话记录、短信消息)(一)
  4. Android(安卓)Apk瘦身指南大全
  5. Android学习笔记(一)---一定要看的 Android(安卓)资源目录的相关知
  6. android代码混淆压缩、资源压缩全解析
  7. android高手之路--删除工程里面无用的代码和资源
  8. android如何将资源打入jar并对资源进行保护
  9. 提升Android应用开发性能的十大要点(1)

随机推荐

  1. 一次重拾Android(安卓)Studio开发的经历
  2. [原] Android持续优化 - 提高流畅度
  3. Android(安卓)UI 之CheckBox的妙用
  4. 【Android】Android的与服务器端传送信息
  5. android 中如何添加新的键值
  6. Android属性动画完全解析(下) Interpolat
  7. 【项目中】Android(安卓)小米红米手机无
  8. 嵌入式设备Android带来的奇迹
  9. Android(安卓)使用 Jenkins 实现自动化打
  10. 在Android中,如何判断当前是否在桌面上