公司要实现一个这样的效果,类似于qq游戏大厅,我们可以下载斗地主,可以下载保皇,下载完成直接就可以玩,不需要安装,也就是说我们的这一款软件里面可以装载多款软件.
公司项目是一套系统管理软件,它里面包括了五款软件,用户可以选择付费选择购买这其中的任一款软件,需求是说为了用户体验好,不让用户购买一款软件就要在桌面上安装一个软件(一共五款,想想用户界面会不会特别乱).
想了想总结一下需要解决以下几个问题:
1.应用占用内存大小,不能让应用占手机内存特别大(五款系统,资源文件不能都放在一个应用里面,那样下载大小太大了~)
2.手机桌面看不到下载的五款应用图标,就像QQ游戏一样,只能看到一个主应用的图标
3.应用之间数据互通(五个应用之间不用使用一个软件就去重新登录,重新获取数据,一方面浪费流量,一方面用户体验也不好)
于是乎开始花了一上午的时间在网上查了一下,得到了三种解决办法 分别如下:
1.插件化,把一款应用分成多个插件, 就是说做一个宿主的主应用,然后里面的五款软件分别拆分成五个插件,当用户购买应用下载时候,其实是把插件下载下来了,直接调用进入就行,
2.设置AndroidManifest.xml里面的主activity
3.热更新热修复,各大互联网公司基本都有自己的技术,可以很快集成到自己的项目,但是呢~~必须把自己的代码挂到人家那上面 发布版本也是要把代码往人家上面放(..想想...自己代码都给人家了..自己还玩个P啊对吧...)
所以只剩下一个解决办法了 就是使用插件化 恩...下面开始介绍本人插件化的坎坷之路
首先我选用的是滴滴开源的VirtualAPK框架,下面是关于他跟其他插件化框架的不同之处
特性 | DynamicLoadApk | DynamicAPK | Small | DroidPlugin | VirtualAPK |
---|---|---|---|---|---|
支持四大组件 | 只支持Activity | 只支持Activity | 只支持Activity | 全支持 | 全支持 |
组件无需在宿主manifest中预注册 | √ | × | √ | √ | √ |
插件可以依赖宿主 | √ | √ | √ | × | √ |
支持PendingIntent | × | × | × | √ | √ |
Android特性支持 | 大部分 | 大部分 | 大部分 | 几乎全部 | 几乎全部 |
兼容性适配 | 一般 | 一般 | 中等 | 高 | 高 |
插件构建 | 无 | 部署aapt | Gradle插件 | 无 | Gradle插件 |
可以看到真的很牛逼的样子
接下来是,集成到项目,开始写代码
一定注意文中所提到的版本 以及gradle版本号 严格规定,只能用这个版本!
gradle -v
查看环境中配置的Gradle版本号。也可以使用工程中gradlew
来编译,可以在gradle/wrapper/gradle-wrapper.properties
中更改版本号distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
Host宿主工程接入需要以下6步
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
apply plugin: 'com.didi.virtualapk.host'
dependencies {
compile 'com.didi.virtualapk:core:0.9.0'
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}
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插件功能就集成到宿主中了,宿主打包和运行方式没有任何改变。接下来看下插件工程如何集成和构建的。
ImageBrowser插件工程接入分为3步:
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
在App的工程模块的build.gradle添加使用gradle插件和插件配置信息,信息需要放在文件最下面
apply plugin: 'com.didi.virtualapk.plugin'
...
...
// 插件配置信息,放在文件最下面
virtualApk {
packageId = 0x6f // 插件资源id,避免资源id冲突
targetHost='../host/app' // 宿主工程的路径
applyHostMapping = true // 插件编译时是否启用应用宿主的apply mapping
}
解释一下上面3个参数的作用
最后一步生成插件,需要使用Gradle命令
gradle clean assemblePlugin
或者
./gradlew clean assemblePlugin
强调一下如果构建时确保Gradle版本需要为2.14.1,否则构建可能发生错误。构建成功后在build/outputs/apk 或者plugin目录中查看插件,plugin目录和apk目录中插件的区别在于plugin将插件以packageName_timestamp格式重命名,DEMO中的插件构建成功后才3KB。
官方WIKI中还说明了:
前面说到VirtualAPK是对耦合型业务有很好的支持,对于我们的DEMO来说,宿主和插件都用到了Picasso库,我们反编译插件包后看一下里面包含的内容如下图所示。
可以看到,插件包中没有Picasso库的相关源码,构建时VirtualAPK已经帮我们移除了。需要注意,宿主和插件包中依赖的SDK版本需要完全一致时才会被移除。
因为宿主中代码写的是从SD卡根目录加载plugin.apk插件,所以我们需要将生成的插件重命名后放到指定位置。
adb push 插件路径 /sdcard/plugin.apk
然后启动宿主程序后点击"查看更多美图",此时加载的Activity来自于插件中,启动代码如下所示
@Override
public void onClick(View v) {
if (PluginManager.getInstance(this).getLoadedPlugin(PLUGIN_PKG_NAME) == null) {
Toast.makeText(getApplicationContext(),
"插件未加载,请尝试重启APP", Toast.LENGTH_SHORT).show();
return;
}
Intent intent = new Intent();
intent.setClassName("com.virtualapk.imageplugin", "com.virtualapk.imageplugin.ImageBrowserActivity");
startActivity(intent);
}
OK,插件就么运行起来了,可以看到在开发宿主和插件时,几乎没有侵入,在生成插件时需要一些命令操作,可以通过脚本实现编译插件+Push到SD卡中,提高开发效率。
DEMO比较简单只包含了Activity,后面会扩展到其他组件的使用,大家可以仔细看下官方WIKI-插件开发指南,里面介绍了插件中四大组件的使用、so库的加载、已知的约束和插件加载时机的选取问题。
资源互相调用也是可以的只要做一套公共的model,然后他们都公用同一个model里面的资源文件,资源ID冲突的问题暂时还没发现,他的生成规则是从头开始按照顺序来的,DEMO比较简单只包含了Activity,但是已经可以满足使用,大家可以仔细看下官方WIKI-插件开发指南,