Android插件化方案RePlugin

RePlugin 是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出“全面插件化”(全面特性、全面兼容、全面使用)的方案。整个方案的接入需要host端和plugin端同时接入。

1 接入

接入之前一定要注意gradle版本问题,建议将版本调整为官方demo的版本。一般使用3.5.4 + 5.4.1即可,具体改法可以参考网上的帖子。接下来我们分别从Plugin端和Host端进行接入。

gradle.png

1.1 Plugin端

Plugin端接入的目的就是将普通的Android开发工程变为即可独立运行也可以作为Plugin插件的apk。Plugin端的接入最为简单,只需要改配置文件引入Plugin gradle插件和Plugin库即可。首先修改根目录下的build.gradle文件,需要注意的是replugin 3.0需要使用360自己的maven库。

/*
 * Copyright (C) 2005-2017 Qihoo 360 Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
buildscript {
    repositories {
        mavenCentral()
        maven { url "https://maven.google.com" }
        maven {url "http://maven.geelib.360.cn/nexus/repository/replugin/"}
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.4'
        classpath "com.qihoo360.replugin:replugin-plugin-gradle:3.0.0"
    }
}

allprojects {
    repositories {
        mavenCentral()
        maven { url "https://maven.google.com" }
        maven {url "http://maven.geelib.360.cn/nexus/repository/replugin/"}
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

接下来只需要在app的build.gradle中添加添加一句话即可。

// 这个plugin需要放在android配置之后,因为需要读取android中的配置项
apply plugin: 'replugin-plugin-gradle'

接入完成之后,在编译成apk的时候,gradle插件脚本会自动将工程编译为插件。具体的原理主要在插件应用的编译期,基于Transform api注入到编译流程中, 再通过Java字节码类库对编译中间环节的Java字节码文件(.class)进行修改,以便实现编译期动态修改插件应用的目的。

1.2 Host端

Host端的接入过程,相较于Plugin端更为复杂一点。除开配置Host gradle插件和Host库之外,还需要继承RePluginApplication类,进行自定义配置。配置完之后,App即可作为宿主程序运行。

1.2.1 gradle配置

gradle配置和Plugin端差不多,只是引入的脚本和库不一样,引入Host端对应的脚本和库即可。

1.2.2 继承RePluginApplication

继承RePluginApplication主要是为了生成RepluginConfig,可以定义相关行为比如插件中class not found时可以使用数组的classloader、签名校验等,并且通过回调对安装失败等情况进行处理。

public class MyApp extends RePluginApplication {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        // FIXME 允许接收rpRunPlugin等Gradle Task,发布时请务必关掉,以免出现问题
        RePlugin.enableDebugger(base, BuildConfig.DEBUG);
    }

    // ----------
    // 自定义行为
    // ----------

    /**
     * RePlugin允许提供各种“自定义”的行为,让您“无需修改源代码”,即可实现相应的功能
     */
    @Override
    protected RePluginConfig createConfig() {
        RePluginConfig c = new RePluginConfig();

        // 允许“插件使用宿主类”。默认为“关闭”
        c.setUseHostClassIfNotFound(true);

        // FIXME RePlugin默认会对安装的外置插件进行签名校验,这里先关掉,避免调试时出现签名错误
        c.setVerifySign(!BuildConfig.DEBUG);

        // 针对“安装失败”等情况来做进一步的事件处理
        c.setEventCallbacks(new HostEventCallbacks(this));

        // FIXME 若宿主为Release,则此处应加上您认为"合法"的插件的签名,例如,可以写上"宿主"自己的。
        // RePlugin.addCertSignature("AAAAAAAAA");

        // 在Art上,优化第一次loadDex的速度
        // c.setOptimizeArtLoadDex(true);
        return c;
    }

    @Override
    protected RePluginCallbacks createCallbacks() {
        return new HostCallbacks(this);
    }

    /**
     * 宿主针对RePlugin的自定义行为
     */
    private class HostCallbacks extends RePluginCallbacks {

        private static final String TAG = "HostCallbacks";

        private HostCallbacks(Context context) {
            super(context);
        }

        @Override
        public boolean onPluginNotExistsForActivity(Context context, String plugin, Intent intent, int process) {
            // FIXME 当插件"没有安装"时触发此逻辑,可打开您的"下载对话框"并开始下载。
            // FIXME 其中"intent"需传递到"对话框"内,这样可在下载完成后,打开这个插件的Activity
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "onPluginNotExistsForActivity: Start download... p=" + plugin + "; i=" + intent);
            }
            return super.onPluginNotExistsForActivity(context, plugin, intent, process);
        }

    }

    private class HostEventCallbacks extends RePluginEventCallbacks {

        private static final String TAG = "HostEventCallbacks";

        public HostEventCallbacks(Context context) {
            super(context);
        }

        @Override
        public void onInstallPluginFailed(String path, InstallResult code) {
            // FIXME 当插件安装失败时触发此逻辑。您可以在此处做“打点统计”,也可以针对安装失败情况做“特殊处理”
            // 大部分可以通过RePlugin.install的返回值来判断是否成功
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "onInstallPluginFailed: Failed! path=" + path + "; r=" + code);
            }
            super.onInstallPluginFailed(path, code);
        }

        @Override
        public void onStartActivityCompleted(String plugin, String activity, boolean result) {
            // FIXME 当打开Activity成功时触发此逻辑,可在这里做一些APM、打点统计等相关工作
            super.onStartActivityCompleted(plugin, activity, result);
        }
    }
}

2 整合

接入之后,就需要把Plugin整合到Host中。

2.0.1 引入插件

插件的引入分为两种,作为内置插件和外置插件引入。Demo代码里面使用内置插件的方式引入。

  • 内置插件 内置插件只需要把编译好的插件apk尾名改为jar之后,放到Host端的Assets/plugins目录中即可,剩下的Host端的gradle会自动帮你搞剩下的工作(比如生成plugins-builtin.json)。Host启动的时候会自动完成内置插件的解析和安装工作。
  • 外置插件 外置插件是指可通过“网络下载”、“放入SD卡”等方式来安装并运行的插件,不同于内置插件,外置插件需要调用RePlugin.install进行主动安装。

2.0.2 运行插件

插件运行的场景有很多,包括:打开插件的四大组件、获取插件的PackageInfo/Context/ClassLoader等、预加载(preload)、使用插件Binder、如果想判断插件是否在运行,可使用 RePlugin.isPluginRunning方法。Demo代码处使用打开Activity的方式来运行插件。

RePlugin.startActivity(MainActivity.this,
                       RePlugin.createIntent("cocos",
                                             "com.linknext.cocos2dx.CocosActivity"));

3 特殊情况

3.1 so库的处理

有关动态链接库需要进行特殊处理,具体处理逻辑可以参考官方文档。为了减少提及,采用最直接的方案,在Host端引入插件需要的so库,然后配置仅支持32位即可。