Android 插件化技术,你值得一看

在工作中会遇到关于Android插件化开发出现的问题,随着对这种技术的了解,我发现在插件开发的一些基础问题上下需要花费更多的时间。就比如在主工程Context和插件Context的区别所在,权限必然要在主工程申明,本着学习的心态,对插件的历史花了些时间进行了解,随后便写了两个Demo进行总结。这篇文章主要是通过对插件的实现原理进行直观的说明,以此来对插件内开发进行更进一步的加深和了解,所以这篇文章也是主要对核心逻辑进行更多的探讨,所以也就不会对其背景和原理进行一个深入的探讨.

原理和背景:

首先从android插件化的技术起源开始,插件化最初是从免安装运行apk这个方向的一个初步想法从而发展出来的技术,插件也可以称之为一个免安装的apk,app在运行过程中因为其支持插件化,从而可以将aap中一些平常不适应的模块做成我们所需要的插件,以此来实现app中的动态扩展功能,更是进一步减少了其安装包的大小,当然需要实现其插件化,主要是要如何解决这三个问题:
载类与主程序如何进行相互调动
资源的加载如何与主工程进行相互访问
组件的生命周期如何管理
我将相对来说比较有名的开源插件化框架进行了时间排序,以此来了解它们的原理,如何实现,这样我们就可以看出插件化技术的发展历程,我将其插件化框架进行了分层,根据其实现原理划分很了三代

第一代:dynamic-load-apk相较于其他插件框架,它最先使用了ProxyActivity静态代理技术,由ProxyActivity进行控制插件PluginActivity生命周期。但是这种方式对的缺点很明显,插件里activity就必须要继承PluginActivity,所以如果要进行开发就必须要仔细处理context。相对而言DroidPlugin就是通过Hook系统服务进行启动该插件中的Activity,这样我们在开发插件的过程与普通的app开发的是一样的,区别不大,但hook会过多的进行系统服务,会显得复杂而且相对而说就不够稳定。
第二代:VirtualAPK为了跟普通app一样开发插件并且让插件开发达到低侵入性,同时也让其拥有框架的稳定性,所以在实现原理上都会尽量选择比较少的hook,在通过manifest中实现对四大组件的插件化进行一个预埋。而且各个框架都从设计的思想进行了一定程度的扩展,其中更是将Small做成了一个具有跨平台,组件化功能的开发框架。
第三代:VirtualApp相对于前面两代就比较厉害了,不仅能够app的运行环境进行完全模拟,还能够讲app的免安装运行和双开技术进行实现。Atlas是阿里19年开源出来的一个具有热修复技术和结合组件化的一个app的基础框架,至今仍然应用于阿里系的app中,因此也将其号称为一个容器化框架。
下面详细介绍插件化框架的原理,分别对应着实现插件化的三个核心问题。

类加载:

从外部apk中类的加载来看,通常在Android中常用的共有两种类加载器,PathClassLoader和DexClassLoader,这两种类加载器都继承于BaseDexClassLoader。
Android对于外部的dex文件,主要通过 DexClassLoader 类加载,因此,只需要给定插件的路径,就可以构造对应的类加载器

// DexClassLoaderpublic 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文件。
那么我们就可以使用DexClassLoader拿去加载外部的apk,所使用的方法如下
//第一个参数为apk的文件目录//第二个参数为内部存储目录//第三个为库文件的存储目录//第四个参数为父加载器new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent)

资源加载:

Android系统是通过Resource对象进行加载资源,下面代码的生成过程展示了该对象
所以也就说明现在插件Activity中就不会有所限制:

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

r = new Resources(assets, metrics, getConfiguration(), compInfo);

所以,我们只要将插件apk中的路径加入到AssetManager之后,就可以实现访问其插件资源。
在具体实现时,因为AssetManager跟public并不是一类,所以就需要通过反射让其去创建,而且部分Rom会对其创建的Resource类进行修改,那么我们就有必要考虑不同Rom的兼容性。

生命周期:

在Android进行开发中会有一些特殊的类,全是才系统创建而来,并且会让系统来管理生命周期。
对生命周期的管理在插件化中是较为复杂的,就如同常用的四大组件,Service,Activity,ContentProvider和BroadcastReceiver。其中插件化中Activity是最为繁琐的,其中早期的dynamic-load-apk所采用的基本上是用代理的方式,主要是通过使用一个空壳的Activity让其成为代理(Proxy),系统会将Activity的回调都一一映射到这个Activity插件,这样的话就可以将插件的生命周期实现通过系统来进行管理,不得不说这种方式及其直观,但其所拥有的插件Activity需要继承这样代理的PluginActivity (Demo中的命名),不仅拥有侵入性强的特点,更可以结合我们后面的例子并加以理解。

代理实现:

首先我们将建立一个 PluginManager 类从而来实现插件的加载:

public class PluginManager {
    static class PluginMgrHolder {
        static PluginManager sManager = new PluginManager();
    }

    private static Context mContext;

    Map<String, PluginApk> sMap = new HashMap<>();

    public static PluginManager getInstance() {
        return PluginMgrHolder.sManager;
    }
    public PluginApk getPluginApk(String packageName) {
        return sMap.get(packageName);
    }

    public static void init(Context context) {
        mContext = context.getApplicationContext();
    }

    public final void loadApk(String apkPath) {
        PackageInfo packageInfo = queryPackageInfo(apkPath);
        if (packageInfo == null || TextUtils.isEmpty(packageInfo.packageName)) {
            return;
        }
        // check cache
        PluginApk pluginApk = sMap.get(packageInfo.packageName);

        if (pluginApk == null) {
            pluginApk = createApk(apkPath);
            if (pluginApk != null) {
                pluginApk.packageInfo = packageInfo;
                sMap.put(packageInfo.packageName, pluginApk);
            } else {
                throw new NullPointerException("PluginApk is null");
            }
        }
    }
private PluginApk createApk(String apkPath) {
        String addAssetPathMethod = "addAssetPath";
        PluginApk pluginApk = null;
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);
            addAssetPath.invoke(assetManager, apkPath);
            Resources pluginRes = new Resources(assetManager,
                    mContext.getResources().getDisplayMetrics(),
                    mContext.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值