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