项目VirtualApkProgram中采用要实行插件化首先考虑的是采用哪种方式,怎么实现等等,经过一番调研发现目前比较火的插件化的优缺点大概有如下几种,如图:
考虑到自己项目中可能需要进行宿主与插件之间进行通信,宿主与插件之间有些功能无法解藕,所以最终选择使用virtualAPK来实现插件化功能需求(如果加载独立插件,宿主和插件没有频繁业务逻辑推荐采用DroidPlugin),virtualapk对编译环境有很大的限制,我宿主项目和插件项目中使用的编译环境是:'com.android.tools.build:gradle:2.3.3'(刚更新了,支持3.1.0)。
在项目开始时,在主项目中进行了一些基本组件的编写依赖,插件中开发功能需求时同时添加所需的基本组件,然后都独立开发自己的功能模块需求,在项目中添加了NativePlugin和RemotePlugin的插件工程,其中NativePlugin存放在本地assets文件中,RemotePlugin存放在服务器中,这两个插件分别在宿主工程中进行从本地拷贝到指定文件加载和从服务器下载到指定文件加载。
关于通信功能需求:
本项目VirtualApkProgram中单个进程中(单个app内部)用的是EventBus消息传递机制,跨进程消息传递(多个app通信,eg:登录插件NativePlugin中登录后通知宿主和RemotePlugin插件修改登录状态) 采用的是广播机制进行的消息传递,跨进程存取操作数据采用ContentProvider方式 。
跨进程通信 可以采取Messenger,AIDL,ContentProvider,Socket的方式
didi/VirtualAPK Demo中的宿主向插件中取数据采用ContentProvider方式
一、插件集成
1、项目的build.gradle添加依赖
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.didi.virtualapk:gradle:0.9.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
2、宿主app中build.gradle添加依赖
头部添加:
apply plugin: 'com.didi.virtualapk.host'
在dependencies添加:
compile 'com.didi.virtualapk:core:0.9.5'
3、在在App的工程模块proguard-rules.pro文件添加混淆规则:
-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }
-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }
-dontwarn com.didi.virtualapk.**
-dontwarn android.**
-keep class android.** { *; }
4、初始化
在合适的地方添加: PluginManager.getInstance(context).init();
5、加载模块
//初始化插件管理器
PluginManager pluginManager = PluginManager.getInstance(context.getApplicationContext());
//获取插件APK的文件对象
File apk = getPluginFile(context, pluginId);
if (apk.exists()){ try { //加载插件 pluginManager.loadPlugin(apk); return true; } catch (Exception e) { e.printStackTrace(); } }
检测模块是否已经加载:
PluginManager pluginManager = PluginManager.getInstance(context.getApplicationContext());
//获取指定包名的插件对象
if (pluginManager.getLoadedPlugin(PackageName) != null){
return true; //已经加载
}
启动模块界面: if(isPluginLoaded(activity,pluginId)){ //模块是否加载,pluginId为model中设置的插件id
//代表模块加载成功 页面可赢正常跳转功能
try {
Intent intent = new Intent();
intent.setClassName(packageName, className);
activity.startActivity(intent);
}catch (Exception e){
e.printStackTrace();
Log.e("sun", "startActivity: " +"启动插件失败");
}
return true;
}
二、插件工程接入
头部添加: apply plugin: 'com.didi.virtualapk.plugin' 添加依赖: //引入virtualAPK依赖 compile 'com.didi.virtualapk:core:0.9.5'
插件信息配置:
virtualApk {
//Attention:::packageId 范围 0x02 - 0x7E
packageId = 0x61 // 插件资源id,避免资源id冲突
targetHost ='app' // 宿主工程的路径
applyHostMapping = true // 插件编译时是否启用应用宿主的apply mapping
}
生成插件:
最后一步生成插件,需要使用Gradle命令(在Terminal中执行,或者在醒目目录中执行)
gradle clean assemblePlugin
三、运行插件
1、demo中的plugin是push到模拟器或者手机的指定目录中。(真实项目应该是从服务器下载)
2、运行宿主app,在指定位置启动model就可以了(参考:一、插件集成中的5步骤)
注意: 模块的文件名称和路径要和代码中保持一致
/**
通过插件id获取插件文件对象
@param context
@param pluginId
@return, 返回插件文件对象 */
public static File getPluginFile(Context context, int pluginId){
//定义插件apk的名称
String pluginApkName = getPluginName(pluginId); //demo中做了简单封装,根据pluginId获model的包名
//定义插件文件对象
File file = new File(getExternalStorageDirectory(), pluginApkName);
Log.e("sun", "getPluginFile: " + file.getAbsolutePath());
return file;
}