美文网首页程序员
360 RePlugin插件化-项目接入

360 RePlugin插件化-项目接入

作者: 莫夜路人 | 来源:发表于2020-05-12 22:08 被阅读0次

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


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

    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
            android:name=".MyApplication"
            ... />
    

    备选:“非继承式”配置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.添加插件别名、版本号
    <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <!-- 别名信息需写在 activity 之前-->
            <meta-data
                android:name="com.qihoo360.plugin.name"
                android:value="plugin2" />
            <meta-data
                android:name="com.qihoo360.plugin.version.ver"
                android:value="100" />
    
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    

    项目运行测试

    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识别,进而成为“内置插件”。

    插件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插件化-项目接入

        本文链接:https://www.haomeiwen.com/subject/fzhjnhtx.html