Android插件化方案RePlugin
RePlugin 是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出“全面插件化”(全面特性、全面兼容、全面使用)的方案。整个方案的接入需要host端和plugin端同时接入。
1 接入
接入之前一定要注意gradle版本问题,建议将版本调整为官方demo的版本。一般使用3.5.4 + 5.4.1即可,具体改法可以参考网上的帖子。接下来我们分别从Plugin端和Host端进行接入。
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位即可。