Android

android插件化介绍

2020-11-17  本文已影响0人  Lee_5566
image.png
转载:https://blog.csdn.net/suyimin2010/article/details/80958742

插件化介绍

image.png

插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件。

支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展。

想要实现插件化,主要是解决下面三个问题:

  1. 插件中代码的加载和与主工程的互相调用
  2. 插件中资源的加载和与主工程的互相访问
  3. 四大组件生命周期的管理

插件化技术

技术的发展,根据实现原理可以将这几个框架划分成了三代。


image.png
第一代

dynamic-load-apk最早使用ProxyActivity这种静态代理技术,由ProxyActivity去控制插件中PluginActivity的生命周期。

该种方式缺点明显,插件中的activity必须继承PluginActivity,开发时要小心处理context。

而DroidPlugin通过Hook系统服务的方式启动插件中的Activity,使得开发插件的过程和开发普通的app没有什么区别,但是由于hook过多系统服务,异常复杂且不够稳定。

第二代

为了同时达到插件开发的低侵入性(像开发普通app一样开发插件)和框架的稳定性,在实现原理上都是趋近于选择尽量少的hook,并通过在manifest中预埋一些组件实现对四大组件的插件化。

另外各个框架根据其设计思想都做了不同程度的扩展,其中Small更是做成了一个跨平台,组件化的开发框架。

第三代

VirtualApp比较厉害,能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术。

Atlas是阿里今年开源出来的一个结合组件化和热修复技术的一个app基础框架,其广泛的应用与阿里系的各个app,其号称是一个容器化框架。

插件化技术原理

类加载

Android中常用的有两种类加载器,DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader。


// DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {    
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {        
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}
// PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {    
    public PathClassLoader(String dexPath, ClassLoader parent) {     
       super(dexPath, null, null, parent);
    } 
 
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {    
       super(dexPath, null, libraryPath, parent);
    }

}

DexClassLoader多传了一个optimizedDirectory参数,这个目录必须是内部存储路径,用来缓存系统创建的Dex文件。

而PathClassLoader该参数为null,只能加载内部存储目录的Dex文件。

Android对于外部的dex文件,主要通过 DexClassLoader 类加载。

//第一个参数为apk的文件目录
//第二个参数为内部存储目录
//第三个为库文件的存储目录
//第四个参数为父加载器
new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent)

类加载器的构造函数代码例子:

private DexClassLoader createDexClassLoader(String apkPath) {
    File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
    DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),
            null, mContext.getClassLoader());
    return loader;
}
插件调用主工程

在构造插件的ClassLoader时会传入主工程的ClassLoader作为父加载器,所以插件是可以直接可以通过类名引用主工程的类。

主工程调用插件

若使用多ClassLoader机制,主工程引用插件中类需要先通过插件的ClassLoader加载该类再通过反射调用其方法。

插件化框架一般会通过统一的入口去管理对各个插件中类的访问,并且做一定的限制。

若使用单ClassLoader机制,主工程则可以直接通过类名去访问插件中的类。

注意:该方式有个弊病,若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错,所以要通过一些规范去避免该情况发生。

资源加载

Android系统通过Resource对象加载资源。

因此,只要将插件apk的路径加入到AssetManager中,便能够实现对插件资源的访问。


//创建AssetManager对象 
AssetManager assets = new AssetManager();
 //将apk路径添加到AssetManager中
  if (assets.addAssetPath(resDir) == 0){              
    return null;  
}
 //创建Resource对象
 
r = new Resources(assets, metrics, getConfiguration(), compInfo);

由于AssetManager并不是一个public的类,需要通过反射去创建.

资源路径处理

和代码加载相似,插件和主工程的资源关系也有两种处理方式:

合并式

由于AssetManager中加入了所有插件和主工程的路径,因此生成的Resource可以同时访问插件和主工程的资源。

但是由于主工程和各个插件都是独立编译的,生成的资源id会存在相同的情况,在访问时会产生资源冲突。

独立式
各个插件的资源是互相隔离的,不过如果想要实现资源的共享,必须拿到对应的Resource对象。

Context处理

通常我们通过Context对象访问资源,光创建出Resource对象还不够,因此还需要一些额外的工作。

对资源访问的不同实现方式也需要不同的额外工作。

以VirtualAPK的处理方式为例。

创建Resource
if (Constants.COMBINE_RESOURCES) {
    //插件和主工程资源合并时需要hook住主工程的资源
    Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
    ResourcesManager.hookResources(context, resources);  
      return resources;
} else {  
      //插件资源独立,该resource只能访问插件自己的资源
    Resources hostResources = context.getResources();
    AssetManager assetManager = createAssetManager(context, apk);  
        return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
关联resource和Activity
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
//设置Activity的mResources属性,Activity中访问资源时都通过mResources
 
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources())

四大组件支持

Android开发中有一些特殊的类,是由系统创建的,并且由系统管理生命周期。

如常用的四大组件,Activity,Service,BroadcastReceiver和ContentProvider。

仅仅构造出这些类的实例是没用的,还需要管理组件的生命周期。

其中以Activity最为复杂,不同框架采用的方法也不尽相同。下面以Activity为例详细介绍插件化如何支持组件生命周期的管理。

大致分为两种方式:

  1. ProxyActivity代理
  2. 预埋StubActivity,hook系统启动Activity的过程
ProxyActivity代理

ProxyActivity代理的方式最早是由dynamic-load-apk提出的,其思想很简单,在主工程中放一个ProxyActivy,启动插件中的Activity时会先启动ProxyActivity,在ProxyActivity中创建插件Activity,并同步生命周期。

下图展示了启动插件Activity的过程:


image.png

具体的过程如下:

首先需要通过统一的入口(如图中的PluginManager)启动插件Activity,其内部会将启动的插件Activity信息保存下来,并将intent替换为启动ProxyActivity的intent。

ProxyActivity根据插件的信息拿到该插件的ClassLoader和Resource,通过反射创建PluginActivity并调用其onCreate方法。

PluginActivty调用的setContentView被重写了,会去调用ProxyActivty的setContentView。由于ProxyActivity重写了getResource返回的是插件的Resource,所以setContentView能够访问到插件中的资源。同样findViewById也是调用ProxyActivity的。

hook方式
image.png
  1. Activity1调用startActivity,实际会调用Instrumentation类的execStartActivity方法,Instrumentation是系统用来监控Activity运行的一个类,Activity的整个生命周期都有它的影子。
  2. 通过跨进程的binder调用,进入到ActivityManagerService中,其内部会处理Activity栈。之后又通过跨进程调用进入到Activity2所在的进程中。
  3. ApplicationThread是一个binder对象,其运行在binder线程池中,内部包含一个H类,该类继承于类Handler。ApplicationThread将启动Activity2的信息通过H对象发送给主线程。
  4. 主线程拿到Activity2的信息后,调用Instrumentation类的newActivity方法,其内通过ClassLoader创建Activity2实例。
上一篇下一篇

猜你喜欢

热点阅读