Android插件技术:VirtualAPK
作者:陆金龙
发表时间:2018-03-22 22:02
详细步骤:
一、 宿主工程
1.在工程根目录下build.gradle中添加
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
2.在App的build.gradle中顶部添加
apply plugin: 'com.didi.virtualapk.host'
3.在App的build.gradle中 compile 添加
dependencies {
compile 'com.didi.virtualapk:core:0.9.0'
}
4.编写MyApp继承Application重写attachBaseContext方法中初始化插件引擎(别忘了在AndroidManifest.xml配置Application)
@Override
protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
PluginManager.getInstance(context).init();
}
5.调用之前加载apk
PluginManager pluginManager = PluginManager.getInstance(this);
//此处是当查看插件apk是否存在,如果存在就去加载(比如修改线上的bug,把插件apk下载到sdcard的根目录下取名为Demo.apk)
File apk = new File(Environment.getExternalStorageDirectory(), "Demo.apk");
if (apk.exists()) {
try {
pluginManager.loadPlugin(apk);
} catch (Exception e) {
e.printStackTrace();
}
}
6.启动插件的界面 Intent intent = new Intent();
intent.setClassName("com.youcompany.pluginapk", "com.youcompany.pluginapk.Main2Activity");
startActivity(intent);
宿主工程需要的权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
二、 插件工程
1.在工程根目录下build.gradle中添加
dependencies {
classpath 'com.didi.virtualapk:gradle:0.9.0'
}
2.在App的build.gradle中顶部添加依赖以及插件配置信息
apply plugin: 'com.didi.virtualapk.plugin'//注意这个是plugin结尾,宿主是以host结尾的
3.//App的build.gradle文件最下面 插件配置信息
virtualApk {
// 插件资源表中的packageId,需要确保不同插件有不同的packageId.
packageId = 0x6f
// 宿主工程application模块的路径,插件的构建需要依赖这个路径,我这个宿主工程和插件工程在同一级目录下,所以下面这样写
targetHost = '../VirtualAPKHostDemo/app'
//默认为true,如果插件有引用宿主的类,那么这个选项可以使得插件和宿主保持混淆一致
applyHostMapping = true
}
4.插件工程使用命令gradlew clean assemblePlugin生成apk,拷贝到手机根目录下
运行宿主工程进行测试。
三、使用VirtualAPK常见问题:
1.使用AS2.2 (AS3.0 目前没有测试成功,如果要使用AS3.0,Project的build.gradle中设置 classpath 'com.android.tools.build:gradle:2.3.3' 而不是gradle:3.0.0)
// 注:gradle:3.0.0' 要求buildToolsVersion '26.0.2',此时报以下错误:
Error:Execution failed for task ':app:processxxxDebugResources'.
> Could not get unknown property 'textSymbolOutputDir' for task ':app:processxxxxDebugResources' of type com.android.build.gradle.tasks.ProcessAndroidResources.
//注:如果com.android.tools.build:gradle:2.3.3和gradle-3.3-all.zip 插件工程生成失败,改为以下版本(具体见第7条):
classpath 'com.android.tools.build:gradle:2.1.3'
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
2.对pluginDemo项目 使用命令 gradlew clean assemblePlugin 生成插件(不要直接run生成,也不要Build APK生成)
3.生成插件com.didi.virtualapk.demo_20180321164052.apk的位置:F:\workspace\PluginDemo\app\build\outputs\plugin\beijingRelease
gradlew clean assemblePlugin 或者 gradle clean assemblePlugin
4.将com.didi.virtualapk.demo_20180321164052.apk 改名为Test.apk,拷贝到手机存储根目录下,进行测试。
5.运行VirtualAPK 测试OK。
6.使用为Activity使用AppTheme主题
java.lang.IncompatibleClassChangeError: com.didi.virtualapk.demo.aidl.BookManagerActivity
java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity
要使用与其配合的AppCompat的theme才行。
<activity
android:name=".MainActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:label="@string/app_name" >
7.插件工程执行gradlew clean assemblePlugin失败:Failed to notify project evaluation listener
A problem occurred configuring project ':app'.
> Failed to notify project evaluation listener.
> com/android/builder/dependency/ManifestDependency
> Configuring > 14/14 projects > Resolving dependencies ':app:_debugApk'
(1)Porject的build.gradle修改Gradle和build tools的版本:
dependencies {
classpath 'com.android.tools.build:gradle:2.1.3'
}
(2)gradle-wrapper.poperties 配置: distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
8.Execution failed for task ':app:transformClassesAndResourcesWithProguardForHqnoteRelease'.
> java.io.IOException: Please correct the above warnings first.
proguard-rules.pro 配置完整,并加上 -ignorewarnings (一定加上-ignorewarnings)
9. 宿主和插件资源名不能相同,否则会调用不到插件的资源:
FATAL EXCEPTION: main Process: com.adehehe.xxxx.client, PID: 31865
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.adehehe.xxxx.client/com.didi.virtualapk.core.A$1}: kotlin.TypeCastException: null cannot be cast to non-null type android.widget.LinearLayout
Caused by: kotlin.TypeCastException: null cannot be cast to non-null type android.widget.LinearLayout
10.有的类或方法找不到 混淆原因
插件混淆 找不到库中的方法
需要排除对库的混淆?
解决:插件工程不要使用混淆。
11.找不到文件app\build\VAHost\versions.txt
Can't find ..\..\..\..\户户端端\AndroidClient\xxxxClient\app\build\VAHost\versions
.txt, please check up your host application
need apply com.didi.virtualapk.host in build.gradle of host application
解决:versions.txt文件不存在,重新生成一次宿主工程。
12. 混淆打包的宿主程序 运行出错 调用方法传了空值
实体类 加入到混淆的 忽略清单中。
13.E/CrashHandler: android.view.InflateException: Binary XML file line #14: Resource ID #0x0
E/CrashHandler: Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x0
插件工程本身编译或运行有问题 编译和运行成功后再打包插件。
14.宿主 发布release版本测试 ok
release混淆版本测试 需要添加的混淆配置
# xutils
-keep class * extends java.lang.annotation.Annotation { *; }
-keep class org.xutils.**{*;}
#end xutils
# virtualapk
-keep class com.didi.virtualapk.internal.VAInstrumentation { *; }
-keep class com.didi.virtualapk.internal.PluginContentResolver { *; }
-keep class com.didi.virtualapk.** { *; }
-dontwarn com.didi.virtualapk.**
-dontwarn android.content.pm.**
-keep class android.** { *; }
# end virtualapk
vendor/lib64 armeabi等错误
android {
defaultConfig {
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
}
}
15. 混淆导致的kotlin报错
classnotfoundException kotlin/jvm/internal/Intrisics
# kotlin
-dontwarn kotlin.**
-dontwarn org.w3c.dom.events.*
-dontwarn org.jetbrains.kotlin.di.InjectorForRuntimeDescriptorLoader
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-keepclassmembers class **$WhenMappings {
<fields>;
}
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}
#end kotlin