Android 插件化开发——手写基于静态代理的插件化框架(启动Activity)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/lmq121210/article/details/102636502

自学插件化有一段时间了,这篇博客算是对前几个月学习的“开花结果”。先给自己鼓个掌!其实这段时间是真累,白天工作,晚上九点下班,到家再学习插件化,早上早起学习插件化,真的是累的不行,但是还是比较充实的。好了,步入正题:基于静态代理的插件化

前言

说到插件化,我是被那个斗地主吸引了,不知道各位有没有玩过这个游戏,在游戏大厅中:
在这里插入图片描述

想玩斗地主的话,就点击下载,然后想玩麻将的话,就点击下载麻将,下载加载完毕之后,就可以愉快的玩游戏了。 这里用到的就是插件化原理,在我们需要的时候再下载 插件。这里的游戏大厅就相当于一个:宿主APP;而斗地主+麻将等小游戏就是:插件。 这些插件都是在用户需要用的时候下载加载的。 这样的话 我们的宿主APK包就可以小很多。

在实现插件化的过程中,我们的插件APP是可以单独运行的,也就是说,既可以作为插件,也可以作为APP。看下图:
在这里插入图片描述

讲解之前,我们再熟悉一个东西:我们是不是看过皮影戏? 没看过,电视上总看过吧?! 每个皮影人物是不是都有人在背后操作,我们看到的的皮影,而实际上真正的幕后老板是:皮影手!

不熟悉代理模式的请转:Android 插件化开发——基础底层知识(代理模式)

静态代理实现插件化原理

我们知道,实际上我们加载外部的插件APK,启动插件中的Activity,面临的第一个问题就是:AMS检查,要多过AMS的检查,我们可以做到:Hook 底层,但是就算“”躲过“了AMS的检查,onPause, onResume等方法也不会执行,因为这些方法都是跟AMS直接交互的,有AMS控制这些方法的执行时机的,在宿主APP中,都不把它当做Activity看待,所以也就是:躲得了初一躲不了十五。
这样的话 ,我们可以能不能找个真正的Activity代替插件中的“假”Activity呢? 答案是可以的! 我们创建一个StubActivity作为占位Activity,然后通过StubActivity实际控制“假”Activity的生命周期执行。看下图:在这里插入图片描述

在上图中我们可以看出,我们看到是“假”Activity在执行一系列生命周期 ,但是那都是假象,实际上背后是有:StubActivity在操控的。那么问题来了:StubActivity是怎么操控呢?通过什么操控呢?答案是:面向接口编程

我们可以创建一个IPluginActivity接口,里面包含定义了所有Activity.java的要执行的方法, 而我们的插件中的“假”Activity都要实现这个接口,之后我们在StubActivity通过ClassLoader对象加载这个“假”Activity,也就是IPluginActivity对象 ,通过StubActivity持有的这个IPluginActivity对象操控:“假”Activity。 看下图:
在这里插入图片描述

注意:静态代理实现插件化,真实的本质是:StubActivity和StubActivity之间的跳转!

Github地址:

在开始之前,先附上GitHub项目地址:
在开始之前,先附上GitHub项目地址: 传送门
功能点:

  • 实现宿主到插件跳转
  • 支持同一插件之间跳转
  • 支持加载不同的插件
  • 支持不同插件之间跳转
  • 支持对插件的启动模式、(注意是:插件内启动模式)

接下来会根据这些功能点进行详细的实现讲解

方案实施

在在此奉上 = - =: 传送门

1:先看下目录结构:
在这里插入图片描述
app也就是宿主, pluginmodel和pluginmodule2都是插件,而基类BaseLibrary是三个都需要依赖的。

2:重要类讲解:

我们看下比较关键的PluginManager类:

    /**
     * 加载插件APK
     * @param apkPath APK或者jar或者dex的目录
     */
    public void loadPluginApk(String apkPath) {
        //Dex优化后的缓存目录
        File odexFile = context.getDir("odex", Context.MODE_PRIVATE);
        //创建DexClassLoader加载器
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath, odexFile.getAbsolutePath(), null, context.getClassLoader());
        //创建AssetManager,然后创建Resources
        Resources resources = null;
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
            method.invoke(assetManager, apkPath);
            resources = new Resources(assetManager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //存储下不同插件(插件路径,该插件的ClassLoader,该插件下的资源)
        pluginItemHashMap.put(apkPath, new PluginItem(apkPath, dexClassLoader, resources));
    }

在占位StubActivity中,我们需要个根据不同的插件,选择不同的ClassLoader以及Resource。

   @Override
    public ClassLoader getClassLoader() {
        if(getIntent() == null) {
            return  super.getClassLoader();
        }
        return stubActivityImp.getClassLoader() == null ? super.getClassLoader() : stubActivityImp.getClassLoader();
    }

    @Override
    public Resources getResources() {
        if(getIntent() == null) {
            return super.getResources();
        }
        return stubActivityImp.getResources() == null ? super.getResources() : stubActivityImp.getResources();
    }

3:实现宿主到插件跳转

在这里插入图片描述

传递DEX_PATH:作为StubActivity选择对应ClassLoader的标记
传递REALLY_ACTIVITY_NAME : 是为了让第一步选中的ClassLoader“假”的Activity。
传递LAUNCH_MDOEL: “假”Activity的启动时模式。

接下来从对应的ClassLoader中加载出“真实”的Activity:
在这里插入图片描述
可以看出我们得到了IPluginActivity的对象:iPluginActivity,根据这个接口调用“假”Activity对应的相关方法。可以看成是:面向接口编程。

4:支持加载不同的插件
要支持加载不同的插件,原理就是:我们把加载的插件保存起来,然后在StubActivity中具体选择要使用哪一个ClassLoader或者Resources

在Pluginmanager类的loadPluginApk方法中:
在这里插入图片描述
然后在StubActivityImpl中具体取出:在这里插入图片描述
这样的话,就完成了加载不同插件的类。

5:LanchModel的支持
回想一下:其实我们在插件之间跳转的时候,本质就是:宿主到StubActivity,或者StubActivity到StubActivity。这样的话,我们就不能再Manifest中具体执行StubActivity的启动模式了。

如果我们想要支持LaunchModel呢? 回想一下:你们面试的时候是不是 被问到或者问过一个问题:“怎样安全退出APP中所有的Activity?”。我相信你们肯定回答过:“在基类的BaseActivity中保存已经启动过的Activity,然后在退出的时候,依次销毁这些Activity。”

对我们这里就是这个思路:我们每次打开一个插件Activity,都会根据要打开的“假”Activity的启动模式,做对应的操作,也就是用集合存起来,然后根据启动模式做具体的操作。也可以说成是:自定义Android的启动模式。

Demo中的ActivityStackManager类就是完成上述功能,更类内部 维护了一个List集合,保存已经打开的插件Activity信息。然后在每次启动新的插件Activity之前,判断LaunchModel:
在这里插入图片描述
然后处理StubActivity的返回键:
在这里插入图片描述

5:Demo运行步骤
这个的细看啊!!!

  • 首先 clone Demo
  • 分别打包插件1和插件2
  • 将插件1APK和插件2APK放入/sdcard/下
    在这里插入图片描述
  • 运行宿主APP程序到模拟器(手机)
  • 最后要打开手机的权限(读写权限!!!)

重要事情说三遍:记得打开权限!!!!
运行结果:

本篇博客的知识点就讲解完了,希望感兴趣的小伙伴,可以clone Demo,然后对照Demo具体看博客。

注:看本篇博客比较吃力的童鞋,建议先看下:

手写Android热修复

Android中的类加载器——ClassLoader

在此感谢:

  1. 包建强的《Android插件化开发指南》
  2. 任玉刚的DL框架

猜你喜欢

转载自blog.csdn.net/lmq121210/article/details/102636502