Android apk动态加载研究

前言

近期工作中遇到两个问题。

  • 换应用皮肤
  • 加载插件apk中的view

Android 换肤技术一文中已经详细说明了如何进行应用换肤。而加载插件apk中的view,利用前文提到的换肤技术,居然无法实现!仔细重新研究Android apk动态加载机制,有了新的发现,而且还可以提高插件加载效率。

布局加载

Android 换肤技术文中提到的加载插件图片方法,无法加载插件的布局文件。怎么尝试都是失败。布局文件是资源中最复杂的,需要解析xml中的其它元素,虽然布局文件的id可以获取,但xml中其它元素的id或者其它关联性的东西仍然无法获取,这应该就是加载插件布局文件失败的原因。

宿主应用无法直播加载插件中的xml布局文件,换一个思路,插件将xml解析成view,将view传递给宿主应用使用。

插件apk需要使用插件context才能正确加载view,宿主如何生成插件context呢?

  public abstract Context createPackageContext(String packageName,
        @CreatePackageOptions int flags)

  context.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY | 
        Context.CONTEXT_INCLUDE_CODE);

使用上述方法可正确创建插件context。

除此之外还有一种方法(本人没有验证过),activity的工作主要是由ContextImpl来完成的, 它在activity中是一个叫做mBase的成员变量。注意到Context中有如下两个抽象方法,看起来是和资源有关的,实际上context就是通过它们来获取资源的,这两个抽象方法的真正实现在ContextImpl中。也即是说,只要我们自己实现这两个方法,就可以解决资源问题了。我们在代码中可以创建activity继承类,重写对应方法即可。具体可参考 下文

/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
public abstract Resources getResources();

动态加载方式

目前动态加载方式均是使用DexClassLoader方式获取对应的class实例,再使用反射调用对应接口,代码如下:

  DexClassLoader loader = new DexClassLoader(mPluginDir, getActivity().getApplicationInfo().dataDir, null, getClass().getClassLoader());
  String dex = getActivity().getDir("dex", 0).getAbsolutePath();
  String data = getActivity().getApplicationInfo().dataDir;
  Class<?> clazz = loader.loadClass("com.okunu.demoplugin.TailImpl");
  Constructor<?> constructor = clazz.getConstructor(new Class[] {});

这种方式存在一个问题,较为耗时,如果宿主管理着许多插件,这种加载方式就有问题,使用下面这种方式可加快插件的加载。

public void getTail2(Context pluginContext){
    try {
        Class clazz = pluginContext.getClassLoader().loadClass("com.okunu.demoplugin.TailImpl");
        Constructor<?> localConstructor = clazz.getConstructor(new Class[] {});
        Object obj = localConstructor.newInstance(new Object[] {});
        mTail = new IPluginProxy(clazz, obj);
    } catch (Exception e) {
        Log.i("okunu", "ee", e);
        e.printStackTrace();
    }
}

注意,一定要使用插件的context为参数,它和插件的其它类使用同一个classloader,既然能获取插件classloader,则可以获取插件中的其它类。如果不使用插件context为参数,则上述方法一定会报错。

总结

针对插件资源加载,其实分为两种形式。

  • 宿主直接使用插件资源,比如使用插件图片、字符串等
  • 宿主间接使用插件资源,比如在宿主中启动插件activity或者显示插件的view

第1种形式,可以在宿主应用中构造AssetManager,添加插件的资源路径。

第2种形式,宿主创建插件context并传递给插件,插件使用自己的context则可自由调用自己的资源了,如何创建插件context前文详述了两种方法。

注意一点,宿主中肯定无法直接调用插件的R文件的。

动态加载apk,也有两种方式。

  • 使用DexClassLoader加载插件路径,获取插件的classLoader。
  • 使用已经创建好的插件context,获取插件的classLoader,效果和第1种一样,但速度要更快

动态加载apk机制还有很多东西可以深入研究,比如说插件activity的生命周期等等,这些内容后续补充。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,467评论 25 708
  • 动态加载技术 介绍 在程序运行的时候,加载一些程序自身原本不存在的可执行文件并运行这些文件里的代码逻辑。 动态调用...
    冰点k阅读 4,099评论 1 11
  • 首先引入一个概念,动态加载技术是什么?为什么要引入动态加载?它有什么好处呢?首先要明白这几个问题,我们先从应用程序...
    CHSmile阅读 1,812评论 0 10
  • 最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优...
    斜杠时光阅读 4,004评论 1 36
  • 想得多做的少是我一直有的恶习,有时候对某些事物有坚持力,但学习这一方面偏弱。今天听了一篇文章,说钱,时间,和关注力...
    D不疯不成魔D阅读 230评论 0 0