- 2019.9.30
- VirtualAPK
- 接入指南
- 一 预注意事项
- 1. gradle-wrapper.properties
- 2. 根 build.gradle 配置
- 3. 注意混淆
- 4. 依赖库
- 二 宿主程序
- 1. 应用级 build.gradle 配置
- 2. 初始化
- 3. 假设已有插件包
- (1)安装
- (2)检查插件是否安装
- (3)启动
- (4)删除插件
- 三 插件程序
- 1. 应用级 build.gradle 配置
- 2. 生成插件包
- 3. 问题
- (1) 使用 FragmentActivity
- (2) Theme.AppCompat.Light.NoActionBar 主题无法去除 ActionBar
- (3) IncompatibleClassChangeError
- (4) 插件使用 material 控件会报错
- (5) CardView 背景会变黑
- (6) 约束布局在插件中无效
- (7) 其他
- 一 预注意事项
2019.9.30
以下内容时当前时间2019.9.30版本下,跑通了 VirtualAPK 的踩坑接入总结。
VirtualAPK
https://github.com/didi/VirtualAPK
要是在 RePlugin 和 VirtualAPK 里选对话,我更偏向 RePlugin, 因为它至少可用 AppCompatActivity, VirtualAPK 在 2019.9.30 当前版本的插件里面只能用 Activity,而且插件不支持多module, 倒是 入侵性极低 这个很不错。
但是,至少可以让项目跑起来上线,在当前已经接入百万级APP里看,还算稳定。
接入指南
官网 Wiki 也说的很详细, 建议使用 Androidx。
一 预注意事项
宿主和插件保持一致性,才会让坑更少点。
1. gradle-wrapper.properties
目前最好使用 4.6:
#Fri Mar 01 16:49:19 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
2. 根 build.gradle 配置
classpath 'com.android.tools.build:gradle:3.2.0'
classpath 'com.didi.virtualapk:gradle:0.9.8.6.2-dev'
3. 注意混淆
混淆文件尽量保持一致。如果报错提示没有找到某个文件,那么就是这个文件需要被keep。
记住 keep 你的启动页,别让它被混淆了。
virtualapk 混淆:
-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }
-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }
-dontwarn com.didi.virtualapk.**
-dontwarn android.content.pm.**
如果插件使用 Retrofit+Rxjava+OkHttp:
#retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-keep class io.reactivex.**
-keep class io.reactivex.** { *; }
#okhttp
-dontwarn okhttp3.**
-keep class okhttp3.**{*;}
-keep interface okhttp3.**{*;}
-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.** { *;}
-dontwarn okio.**
# rxjava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
4. 依赖库
在当前测试情况下,只要把插件的依赖库完整的拷贝一份到宿主里面,就可以不用针对不同宿主重新编译插件包。
插件所用依赖库的版本必须和宿主一致。
二 宿主程序
宿主的资源id最好有自己的命名前缀,如果想坑少点。
1. 应用级 build.gradle 配置
应用插件:
apply plugin: 'com.android.application'
apply plugin: 'com.didi.virtualapk.host'
可以使用 Java 8:
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
添加 virtualapk 依赖:
implementation ('com.didi.virtualapk:core:0.9.9.1-dev') {
exclude group: 'com.android.support'
}
2. 初始化
在 Application 进行初始化
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
PluginManager.getInstance(base).init();
}
3. 假设已有插件包
插件包后缀必须 .apk
(1)安装
PluginManager.getInstance(PCache.getContext()).loadPlugin(file);
需要记录插件启动页相对路径比如: com.afra55.greate.MainActivity
。
安装代码执行后,插件包不要删除,目前的情况是,每次进入插件都 安装一次才不会出问题。
(2)检查插件是否安装
自己进行安装判断。
(3)启动
Intent intent = new Intent();
intent.setClassName(packageName, "com.afra55.greate.MainActivity");
context.startActivity(intent);
(4)删除插件
无。
三 插件程序
注意,如果有多个 module,最好把多个module 的代码都放到一个module下,不能直接去依赖module 会出错。 或者把module库代码放到 maven 仓库去依赖。
插件不支持分包,代码越少越好,重的依赖库就放到宿主,通过反射调用。
插件不能配置 Java8, 谨记。
1. 应用级 build.gradle 配置
在最底下配置:
apply plugin: 'com.didi.virtualapk.plugin'
virtualApk {
packageId = 0x6f // 插件的唯一标志,不用插件要有不同的 packageId.
targetHost = '/Users/afra55/Program/Android/TestMaster/app' // TODO(根据不同的项目,修改成应用级module的全路径)
applyHostMapping = true //optional, default value: true.
forceUseHostDependences = true // 强制使用宿主工程的依赖
}
targetHost 需要根据不同的项目,修改成应用级module的全路径。
上面也提到了,拷贝一份插件的全部依赖到宿主,插件包就可以复用。
这样就配置完成了。
2. 生成插件包
使用Terminal到应用级目录下执行命令:
../gradlew clean assemblePlugin
这样就打包出来了。
可以修改打包路径 (适用 gradle-3.2.0):
release {
signingConfig signingConfigs.release
debuggable false
zipAlignEnabled true
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
applicationVariants.all { variant ->
variant.outputs.all { output ->
def outputFile = output.outputFile
if (outputFile != null &&
outputFile.name.endsWith('release.apk')) {
variant.getPackageApplication().outputDirectory = new File("${getApkSavedPath()}")
def fileName =
"afra55.apk"
output.outputFileName = fileName
}
}
}
}
3. 问题
(1) 使用 FragmentActivity
不能使用 AppCompatActivity, 会报错
(2) Theme.AppCompat.Light.NoActionBar 主题无法去除 ActionBar
在代码里实现你的去ActionBar效果,在主题里无效:
try {
requestWindowFeature(Window.FEATURE_NO_TITLE);
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.hide();
}
} catch (Exception e) {
e.printStackTrace();
}
(3) IncompatibleClassChangeError
凡是出现这个问题,就是插件和宿主的相同依赖度的版本不一致造成的。
(4) 插件使用 material 控件会报错
去除 material 控件,自己写一个吧。
(5) CardView 背景会变黑
那就别用了,自己写个。
(6) 约束布局在插件中无效
换掉。