360 RePlugin插件化-项目接入

RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案
GitHub 官方文档


RePlugin 接入项目分为 主程序接入 和 插件接入。

360 RePlugin插件化-项目接入_第1张图片
Replugin-dev

图片中标识的 replugin-host-* 是主程序接入需要用到的,replugin-plugin-* 是插件接入需要用的。

主程序接入

注意:Android Support库和AndroidX冲突的问题

  • AS 3.2以上、Gradle 插件版本改为 4.6及以上 、compileSdkVersion 版本升级到 28及以上和buildToolsVersion 版本改为 28.0.2及以上 会引入Android新的扩展库 AndroidX。
  • RePlugin 需依赖 android.support:appcompat-v7:28.*
  • 目前还不知道如何兼容,所以在引入时做了一些修改

想了解AndroidX相关问题点击这里

1.添加 RePlugin Host Gradle 依赖

项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-host-gradle 依赖:

buildscript {
    dependencies {
        //降低gradle插件版本
        classpath 'com.android.tools.build:gradle:3.1.0'
        classpath 'com.qihoo360.replugin:replugin-host-gradle:2.3.3'
        ...
    }
}
2.添加 RePlugin Host Library 依赖

app/build.gradle 中应用 replugin-host-gradle 插件,并添加 replugin-host-lib 依赖:

android {
    // ATTENTION!!! Must CONFIG this to accord with Gradle's standard, and avoid some error
    defaultConfig {
        applicationId "com.****.host"
        ...
    }
    ...
}
// ATTENTION!!! Must be PLACED AFTER "android{}" to read the applicationId
apply plugin: 'replugin-host-gradle'

/**
 * 配置项均为可选配置,默认无需添加
 * 更多可选配置项参见replugin-host-gradle的RepluginConfig类
 * 可更改配置项参见 自动生成RePluginHostConfig.java
 */
repluginHostConfig {
    /**
     * 是否使用 AppCompat 库
     * 不需要个性化配置时,无需添加
     */
    useAppCompat = true
    /**
     * 背景不透明的坑的数量
     * 不需要个性化配置时,无需添加
     */
    countNotTranslucentStandard = 6
    countNotTranslucentSingleTop = 2
    countNotTranslucentSingleTask = 3
    countNotTranslucentSingleInstance = 2
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //不使用 androidx库
//    implementation 'androidx.appcompat:appcompat:1.1.0'
//    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
//    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
//    testImplementation 'junit:junit:4.12'
//    androidTestImplementation 'androidx.test:runner:1.2.0'
//    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    //引入 android.support 库
    //noinspection GradleCompatible
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.qihoo360.replugin:replugin-host-lib:2.3.3'
    ...
}

注意

  • 必须将包名写在applicatonId,而非AndroidManifest.xml中
  • 请将apply plugin: 'replugin-host-gradle'放在 android{} 块之后,防止出现无法读取applicationId,导致生成的坑位出现异常
  • 如果您的应用需要支持AppComat,则除了在主程序中引入AppComat-v7包以外,还需要在宿主的build.gradle中添加下面的代码:
repluginHostConfig {
   useAppCompat = true
}
  • 如果您的应用需要个性化配置坑位数量,则需要在宿主的build.gradle中添加下面的代码:
repluginHostConfig {
    /**
    * 背景不透明的坑的数量
    */
   countNotTranslucentStandard = 6
   countNotTranslucentSingleTop = 2
   countNotTranslucentSingleTask = 3
   countNotTranslucentSingleInstance = 2
}
3.配置 Application 类

让工程的 Application 直接继承自 RePluginApplication。

public class MyApplication extends RePluginApplication {
}

既然声明了Application,自然还需要在AndroidManifest中配置这个Application。


备选:“非继承式”配置Application
若您的应用对Application类继承关系的修改有限制,或想自定义RePlugin加载过程(慎用!),则可以直接调用相关方法来使用RePlugin。

public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        RePlugin.App.attachBaseContext(this);
        ....
    }

    @Override
    public void onCreate() {
        super.onCreate();
        
        RePlugin.App.onCreate();
        ....
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();

        /* Not need to be called if your application's minSdkVersion > = 14 */
        RePlugin.App.onLowMemory();
        ....
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);

        /* Not need to be called if your application's minSdkVersion > = 14 */
        RePlugin.App.onTrimMemory(level);
        ....
    }

    @Override
    public void onConfigurationChanged(Configuration config) {
        super.onConfigurationChanged(config);

        /* Not need to be called if your application's minSdkVersion > = 14 */
        RePlugin.App.onConfigurationChanged(config);
        ....
    }
}

针对“非继承式”的注意点

  • 所有方法必须在UI线程来“同步”调用。切勿放到工作线程,或者通过post方法来执行
  • 所有方法必须一一对应,例如 RePlugin.App.attachBaseContext 方法只在Application.attachBaseContext中调用
  • 请将RePlugin.App的调用方法,放在“仅次于super.xxx()”方法的后面

插件接入

只需两步,就能让您的App变成“RePlugin插件”:

1.添加 RePlugin Plugin Gradle 依赖

在项目根目录的 build.gradle(注意:不是 app/build.gradle) 中添加 replugin-plugin-gradle 依赖:

buildscript {
    dependencies {
        //降低gradle插件版本
        classpath 'com.android.tools.build:gradle:3.1.0'
        classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.3.3'
        ...
    }
}
2.添加 RePlugin Plugin Library 依赖

在 app/build.gradle 中应用 replugin-plugin-gradle 插件,并添加 replugin-plugin-lib 依赖:

apply plugin: 'replugin-plugin-gradle'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //不使用 androidx库
//    implementation 'androidx.appcompat:appcompat:1.1.0'
//    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
//    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
//    testImplementation 'junit:junit:4.12'
//    androidTestImplementation 'androidx.test:runner:1.2.0'
//    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    //引入 android.support 库
    //noinspection GradleCompatible
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.qihoo360.replugin:replugin-plugin-lib:2.3.3'
    ...
}
3.添加插件别名、版本号

        
        
        

        
            
                

                
            
        
    

项目运行测试

1、外置插件

外置插件是指可通过“下载”、“放入SD卡”等方式来安装并运行的插件。

A.安装外置插件

要安装一个外置插件,只需使用 RePlugin.install 方法,传递一个“APK路径”即可。

RePlugin.install("/sdcard/exam.apk");
  • 无论安装还是升级,都会将“源文件”“移动”(而非复制)到插件的安装路径(如app_p_a)上,这样可大幅度节省安装和升级时间,但显然的,“源文件”也就会消失
  • 若想改变这个行为,您可以参考RePluginConfig中的 setMoveFileWhenInstalling() 方法
  • 升级插件和此等同
B.升级外置插件

升级插件的做法和“安装”是一样的,仍可以直接调用 RePlugin.install 方法。

RePlugin.install("/sdcard/exam_new.apk");
  • 如果插件正在运行,则不会立即升级,而是“缓存”起来。直到所有“正在使用插件”的进程结束并重启后才会生效
  • 升级可能会占用“内部存储空间”(因为要释放新的APK)
  • 不支持“插件降级”,但可以“同版本覆盖”(在 RePlugin 2.1.5版本中开始支持)

安装或升级失败(返回值为Null)的原因有如下几种:

  • 是否开启了“签名校验”功能且签名不在“白名单”之中?——通常在Logcat中会出现“verifySignature: invalid cert: ”。如是,则请参考“安全与签名校验”一节,了解如何将签名加白,或关闭签名校验功能(默认为关闭)
  • 是否将replugin-host-lib升级到2.1.4及以上?——在2.1.3及之前版本,若没有填写“meta-data”,则可能导致安装失败,返回值为null。我们在 2.1.4 版本中已经修复了此问题(卫士和其它App的所有插件都填写了meta-data,所以问题没出现)
  • APK安装包是否有问题?——请将“插件APK”直接安装到设备上(而非作为插件)试试。如果在设备中安装失败,则插件安装也一定是失败的。
  • 是否没有SD卡的读写权限?——如果您的插件APK放到了SD卡上,则请务必确保主程序中拥有SD卡权限(主程序Manifest要声明,且ROM允许),否则会出现权限问题,当然,放入应用的files目录则不受影响。
  • 设备内部存储空间是否不足?——通常出现此问题时其Logcat会出现“copyOrMoveApk: Copy/Move Failed”的警告。如是,则需要告知用户去清理手机。
C.卸载外置插件

要卸载插件,则需要使用 RePlugin.uninstall 方法。只需传递一个“插件名”即可。

RePlugin.uninstall("exam");
  • 如果插件正在运行,则不会立即卸载插件,而是将卸载诉求记录下来。直到所有“正在使用插件”的进程结束并重启后才会生效
  • 由于内置插件是捆在主程序包内的,故无法卸载“内置插件”

2、内置插件

内置插件是指可以“随着主程序发版”而下发的插件,通常这个插件会放到主程序的Assets目录下。

A.添加内置插件

添加一个内置插件是非常简单的,甚至可以“无需任何Java代码”。只需两步即可:

  • 将APK改名为:[插件名].jar
  • 放入主程序的assets/plugins目录这样,当编译主程序时,我们的“动态编译方案”会自动在assets目录下生成一个名叫“plugins-builtin.json”文件,记录了其内置插件的主要信息,方便运行时直接获取。

必须改成“[插件名].jar”后,才能被RePlugin-Host-Gradle识别,进而成为“内置插件”。

360 RePlugin插件化-项目接入_第2张图片
插件assets目录

B.升级内置插件

内置插件的升级分为两种情况:主程序随包升级、通过install方法升级

  • 主程序随包升级:当用户升级了带“新版本内置插件”的主程序时,则RePlugin会在使用插件前先做升级
  • 通过install方法升级:若通过 RePlugin.install 方法做的升级(大多为用户从服务器上下载并更新),则RePlugin在调用install方法时开始做升级。当然,其规则仍遵循安装插件的规则,例如“插件运行时先不覆盖”等。值得注意的是,无论采用何种方式,均“不支持降级”,但支持“同版本覆盖”升级
C.删除内置插件

删除内置插件非常简单,直接移除相应的Jar文件,其余均交给RePlugin来自动化完成。

注意:若用户已使用了内置插件,则即便用户升级主程序,其包内已不带这个内置插件,但用户仍可继续使用它。这样可防止出现“用户升级主程序后,发现内置插件突然用不了”的情况。

D.使用内置插件的时机

不同于“外置插件”需要先调用 RePlugin.install 方法后才能使用,内置插件可无需调用此方法。而一旦插件被使用,则RePlugin会在触发相应逻辑前,为您做下列操作:

  • 将内置插件释放到数据目录下(近似于调用install方法)
  • 若需要加载Dex,则还会释放“优化后的Dex”到数据目录下,这可能会需要一些时间这样做的好处是,不会占用太多的“内部存储空间”,毕竟不是所有内置插件,都一定会被用到。

3.插件预加载的用法

预加载插件就是将插件的dex“提前做释放”,并将Dex缓存到内存中,这样在下次启动插件时,可无需走dex2oat过程,速度会快很多。

  • 预加载当前安装的插件此为绝大多数用到的场景。直接预加载当前安装的插件即可,如果当前正在运行这个插件,则调用此方法则是无效的,毕竟当前插件已经早就被使用过了。
RePlugin.preload(pluginName);
  • 预加载新安装的插件此场景主要用于“后台升级某个插件”。如果此插件“正在被使用”,则必须借助 RePlugin.install 方法的返回值(新插件的信息)来做预加载。
PluginInfo pi = RePlugin.install("/sdcard/exam_new.apk");
if (pi != null) {
    RePlugin.preload(pi);
}

4.使用插件

通过插件名和类名调起

//第一个参数是插件的包名,第二个参数是插件的Activity。
Intent intent = RePlugin.createIntent(pluginName, className);
if (!RePlugin.startActivity(MainActivity.this, intent)) {
      Toast.makeText(getBaseContext(), "启动失败", Toast.LENGTH_LONG).show();
}

基本的接入流程就这些,如果有错误的地方,欢迎指正!

宿主与插件通信请看这里

你可能感兴趣的:(360 RePlugin插件化-项目接入)