Android插件化框架使用心得 (使用篇)

时间:2021-09-25 14:10:45

最近项目中,有些子功能需要按需加载,于是考虑使用插件化方案实现,看过几个插件化方案后,最终选择了360之前开源的DroidPlugin方案。

在使用中,还是有一些心得体会,网上的源码分析,详细原理分析的文章一经很多(文末会有链接),本篇文章,主要从使用的角度入手,简单描述下DroidPlugin原理,也记录下使用中心得体会。

基本原理

阅读了部分DroidPlugin的源码,也看了部分网上的文章和android组件启动过程的分析。对于此插件化框架的基本的原理,个人理解如下:

插件化目的是为了从宿主中,启动插件中组件,不是同一个应用,也不是同一个进程,如果按照android原有的机制运行API,中途必然不能运行下去。

为了实现这种特殊的目的,插件化框架中Hook了大量系统的API, 通过改写入参,返回值的方式,使得API的行为发生变化,绕过系统部分规则和检查,完成组件加载,功能实现等。

简单描述下Hook的思路,顾名思义,就是像钩子一样,将程序段中的部分代码挂住,之后,改写程序段的部分行为。

通过Java的动态加载机制,在结合反射,就能实现hook的大致功能。
最终通过实现 InvocationHandler 接口中的 invoke 方法,达到改写某些系统原生api的行为。而通过反射,可以在运行时,动态改写部分类或者方法的如参,配合被改写的api共同实现,绕过系统系统限制,加载插件的目的。

框架使用心得

在使用DroidPlugin框架过程中,还是有部分心得体会,简要记录下,插件化框架的原理十分复杂,但是如果简单使用的话,从API调用层面来看,其实并不是很复杂。

首先是插件初始化的过程,在这流程中,主要完成插件化框架,加载到宿主APP上,安装了所有的hook等操作。

PluginHelper.getInstance().applicationOnCreate(getBaseContext());
PluginHelper.getInstance().applicationAttachBaseContext(base);

下面这两个API, 是需要在宿主APP, 启动组件前需要调用的,和框架里使用了AIDL方式进行交互,所有要使用各种API, 首先要创建服务的连接。

PluginManager.getInstance().isConnected();
PluginManager.getInstance().addServiceConnection(mServiceConnection);

这三个API, 是处理插件包相关的常见API,安装的过程,其实是把插件包的信息,写入到插件化框架中,专门为维护插件相关的信息的一段数据结构中,后续的get流程,和删除流程,实际上都是对这个数据结构作操作。

PluginManager.getInstance().installPackage(item.apkFile, 0);
PluginManager.getInstance().getInstalledPackages(0);
PluginManager.getInstance().deletePackage(item.packageInfo.packageName, 0);

这边需要特别注意,官方文档中的一个提示,插件框架中的 manifest 文件中 provider 的 authorities 一定要记得修改,不然真的会引起冲突。

All provider's authorities value in DroidPlugin's Libraries\DroidPlugin\AndroidManifest.xml default to be com.morgoo.droidplugin_stub_P00, e.g. :

  <provider
          android:name="com.morgoo.droidplugin.stub.ContentProviderStub$StubP00"
          android:authorities="com.morgoo.droidplugin_stub_P00"
          android:exported="false"
          android:label="@string/stub_name_povider" />

You'd better change it to avoid conflict with other instances, e.g.:

  <provider
          android:name="com.morgoo.droidplugin.stub.ContentProviderStub$StubP00"
          android:authorities="com.example.droidplugin_stub_P00"
          android:exported="false"
          android:label="@string/stub_name_povider" />

and change PluginManager.STUB_AUTHORITY_NAME to your value:

  PluginManager.STUB_AUTHORITY_NAME="com.example.droidplugin_stub"