##1 插件化
>一般定义是:对现有的程序添加额外的功能组件
通俗理解:加载外部APK
##2 这样的优势
+ 国内App的版本碎片较为严重,减少升级成本。
+ 解决App方法数超65536问题。在谷歌官方的Multidex方案没有出现时,可以采用插件方式解决:
+ 减少App包大小。宿主App包含了主要功能,其余放到插件中实现,动态下发;
+ 模块解耦,协同开发。一个超级App,可以拆分成不同的插件模块,基于一定的规则业务线之间协同开发,最终每个业务模块生成一个插件,由宿主加载组合即可。
##VirtualAPK
https://github.com/didi/VirtualAPK
是滴滴的开源插件话框架 请读者详细阅读一下Readme和Wiki,对VirtualAPK框架有个整体的认识
VirtualAPK是滴滴出行自研的一款优秀的插件化框架,主要有如下几个特性。
##VirtualAPK特性
###功能完备
+ 支持几乎所有的Android特性;
+ 四大组件方面
####四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期。
1. Activity:支持显示和隐式调用,支持Activity的 theme 和 LaunchMode ,支持透明主题;
2. Service:支持显示和隐式调用,支持Service的 start 、 stop 、 bind 和 unbind ,并支持跨进程bind插件中的Service;
3. Receiver:支持静态注册和动态注册的Receiver;
4. ContentProvider:支持provider的所有操作,包括 CRUD 和 call 方法等,支持跨进程访问插件中的Provider。
+ 自定义View:支持 自定义View ,支持自定义属性和 style ,支持动画;
+ PendingIntent:支持 PendingIntent 以及和其相关的 Alarm 、 Notification 和 AppWidget ;
+ 支持插件 Application 以及插件manifest中的 meta-data ;
+ 支持插件中的 so 。
####优秀的兼容性
+ 兼容市面上几乎所有的Android手机,这一点已经在滴滴出行客户端中得到验证;
+ 资源方面适配小米、Vivo、Nubia等,对未知机型采用自适应适配方案;
+ 极少的Binder Hook,目前仅仅hook了两个Binder: AMS 和 IContentProvider ,hook过程做了充分的兼容性适配;
+ 插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。
####入侵性极低
+ 插件开发等同于原生开发,四大组件无需继承特定的基类;
+ 精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖;
+ 插件的构建过程简单,通过Gradle插件来完成插件的构建,整个过程对开发者透明。
####Wiki中和已有的插件话框架的对比
特性 | DynamicLoadApk| DynamicAPK|Small|DroidPlugin|VirtualAPK
-----------|:-------------:| :---------:|:---:|:--------:|:--------:
支持四大组件 | 只支持Activity| 只支持Activity|只支持Activity|全支持| 全支持
组件无需在宿主manifest中预注册 | √| × |× | √ | √
插件可以依赖宿主 | √ | √ |√ | × |√
支持PendingIntent| × |× |× |√| √
Android特性支持 |大部分 |大部分| 大部分| 几乎全部 |几乎全部
兼容性适配| 一般 |一般 |中等 |高| 高
插件构建 |无 |部署aapt| Gradle插件 |无| Gradle插件
####VirtualAPK接入指南
>Gradle版本号目前需要为2.14.1
com.android.tools.build的版本号为2.1.3
>宿主接入:
1. 在宿主工程根目录的build.gradle添加依赖
```java
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
```
2. 在App的工程模块的build.gradle添加使用gradle插件
```java
apply plugin: 'com.didi.virtualapk.host'
```
3. 添加VirtualAPK SDK compile依赖
```java
dependencies {
compile 'com.didi.virtualapk:core:0.9.0'
}
```
4. 在App的工程模块proguard-rules.pro文件添加混淆规则(Ps:Picasso库的混淆规则没有列出来)
```java
-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }
-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }
-dontwarn com.didi.virtualapk.**
-dontwarn android.content.pm.**
-keep class android.** { *; }
```
5. MyApplication类是继承了Application,覆写attachBaseContext函数,进行插件SDK初始化工作
```java
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}
```
6. 在使用插件之前加载插件,可以根据具体业务场景选择合适时机加载,我是在MainActivity的onCreate时机加载
```java
protected void onCreate(Bundle savedInstanceState) {
// 加载plugin.apk插件包
PluginManager pluginManager = PluginManager.getInstance(this);
File apk = new File(getExternalStorageDirectory(), "plugin.apk");
if (apk.exists()) {
try {
pluginManager.loadPlugin(apk);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
经过上述6步后,VirtualAPK插件功能就集成到宿主中了,宿主打包和运行方式没有任何改变。接下来看下插件工程如何集成和构建的。
>插件工程接入
1. 插件工程根目录的build.gradle添加依赖
```java
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
```
2. 在App的工程模块的build.gradle添加使用gradle插件和插件配置信息,信息需要放在文件最下面
```java
apply plugin: 'com.didi.virtualapk.plugin'
...
...
// 插件配置信息,放在文件最下面
virtualApk {
packageId = 0x6f // 插件资源id,避免资源id冲突
targetHost='../host/app' // 宿主工程的路径
applyHostMapping = true // 插件编译时是否启用应用宿主的apply mapping
}
```
>解释一下上面3个参数的作用
+ packageId用于定义每个插件的资源id,多个插件间的资源Id前缀要不同,避免资源合并时产生冲突
+ targetHost指明宿主工程的应用模块,插件编译时需要获取宿主的一些信息,比如mapping文件、依赖的SDK版本信息、R资源文件,一定不能填错,否则在编译插件时会提示找不到宿主工程。
+ applyHostMapping表示插件是否开启apply mapping功能。当宿主开启混淆时,一般情况下插件就要开启applyHostMapping功能。因为宿主混淆后函数名可能有fun()变为a(),插件使用宿主混淆后的mapping映射来编译插件包,这样插件调用fun()时实际调用的是a(),才能找到正确的函数调用。
3. 最后一步生成插件,需要使用Gradle命令
```java
gradle clean assemblePlugin
```
或者
```java
./gradlew clean assemblePlugin
```
资料来源:
1. 接入指南
http://www.jianshu.com/p/013510c19391
2. 源码分析
http://blog.csdn.net/lmj623565791/article/details/75000580
http://blog.csdn.net/u012124438/article/details/74118905