前言:前一段時間新開源了一種全面插件化的方案-- RePlugin,之前一種都在關注 DroidPlugin 并且很早也在項目中試用了,但最終沒有投入到真正的生產環節,一方面是項目中沒有特別需要插件化的需求,另一方面也考慮到 DroidPlugin 不是特別穩定,Android系統每更新一次 DroidPlugin 可能就會出現一些 Bug,畢竟 Hook 了 Android 原生的太多東西,系統一旦更新引發 Bug 是在所難免的。當然,這些并不能否認 DroidPlugin 的優秀,它的原理和思路值得我們深入探究、學習,前一段時間更新過幾篇插件化的原理分析的文章(基于 DrodiPlugin 原理)學習過程中不得不嘆服作者的思路和技術深度!前幾篇小白也能看懂的插件化系列文章仍然會不定期更新,但目前我們可以先來學習學習 RePlugin,畢竟多學無害,也能互相參考他們的思路,比較優缺點。
1.什么是RePlugin?
在Android開發領域,有關插件化的討論一直熱度不減。目前市面上的插件化方案雖然很多,但多數只能實現某些功能的插件化,距離開發者的預期尚有相當差距。對此,在近期GMTC全球移動技術大會上,360手機衛士主程序架構負責人張炅軒宣布,360的插件化框架RePlugin已經可以實現“全面插件化”,同時具有出色的穩定性和靈活性,可適用于各種類型的應用上。
“RePlugin預計7月份開源,這將是我們獻給安卓世界最好的禮物。”360如是說。
2.RePlugin有什么用?
RePlugin是一套完整的、穩定的、適合全面使用的,占坑類插件化方案,由360手機衛士的RePlugin Team研發,也是業內首個提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。
3.RePlugin官方介紹
其主要優勢有:
一、集成主工程
1、在項目根目錄的 build.gradle 下添加 RePlugin Host Gradle 依賴:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' // 1、添加RePlugin Host Gradle依賴 classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1' }} 2、在 app/build.gradle 下添加 RePlugin Host Library 依賴(為了更清晰的表示出代碼添加的位置,將原有代碼也一并貼出):
apply plugin: 'com.android.application'
android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { applicationId "cn.codingblock.repluginstudy" minSdkVersion 21 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}apply plugin: 'replugin-host-gradle'// 集成 RePlugin 添加的配置// 集成 RePlugin 添加的配置repluginHostConfig { useAppCompat = true // 如果項目需要支持 AppComat,則需要將此配置置為 true // 如果應用需要個性化配置坑位數量,則需要添加以下代碼進行配置// countNotTranslucentStandard = 6// countNotTranslucentSingleTop = 2// countNotTranslucentSingleTask = 3// countNotTranslucentSingleInstance = 2}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:26.+' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.qihoo360.replugin:replugin-host-lib:2.2.1' // 集成 RePlugin 添加的配置 testCompile 'junit:junit:4.12'}以上代碼有三點需要注意:
countNotTranslucentStandard = 6countNotTranslucentSingleTop = 2countNotTranslucentSingleTask = 3countNotTranslucentSingleInstance = 2
3、讓工程的 Application 直接繼承自 RePluginApplication:
public class MyApplication extends RePluginApplication { } 當然,同時不要忘了在 AndroidManifest 對 MyApplication 的相關配置。
說明:有時候由于項目原有結構的需要,我們可能不能直接使用繼承 RePluginApplication 的方式,這個問題看來 RePlugin 開發者也想到了,所以還特地多了一種選擇,下面是項目的 Application 不繼承 RePluginApplication 的方式:
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(); RePlugin.App.onLowMemory(); } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); RePlugin.App.onTrimMemory(level); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); RePlugin.App.onConfigurationChanged(newConfig); }}二、集成插件
新建一個工程做為插件APP,這里為了方便起見,直接在主工程中新建了一個 Module。
1、同集成主工程類似,在根目錄的 build.gradle 添加 RePlugin Plugin Gradle 依賴(若是單獨創建插件工程,則不需要添加注釋1下面的代碼):
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' // 1、添加RePlugin Host Gradle依賴(主工程用) classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.1' // 2、添加RePlugin Plugin Gradle依賴(插件工程用) classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.1' }}2、在 app/build.gradle 中添加 replugin-plugin-gradle 插件和 replugin-plugin-lib 依賴:
apply plugin: 'com.android.application'android { ...}apply plugin: 'replugin-plugin-gradle' // 集成 RePlugin 添加的配置dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:26.+' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.1' // 集成 RePlugin 添加的配置 testCompile 'junit:junit:4.12'}三、管理插件
RePlugin 對插件定義兩種方式一種是外置插件、一種是內置插件。
(一)外置插件的安裝(升級)、啟動、卸載
安裝插件:
PluginInfo pluginInfo = RePlugin.install(Environment.getExternalStorageDirectory().getPath().toString() + "/plugin1.apk");System.out.println(pluginInfo);
同時別忘了添加文件讀寫的權限。 輸出日下:
安裝成功了! (升級插件也是用 install() 方法,不可降級,同本版可覆蓋安裝)
啟動插件
先來看一下 RePlugin.java 中啟動插件相關的源碼
/** * 創建一個用來定向到插件組件的Intent <p> * <p> * 推薦用法: <p> * <code> * Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"); * </code> <p> * 當然,也可以用標準的Android創建方法: <p> * <code> * Intent in = new Intent(); <p> * in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity")); * </code> * * @param pluginName 插件名 * @param cls 目標全名 * @return 可以被RePlugin識別的Intent * @since 1.0.0 */public static Intent createIntent(String pluginName, String cls) { Intent in = new Intent(); in.setComponent(createComponentName(pluginName, cls)); return in;}/** * 開啟一個插件的Activity <p> * 其中Intent的ComponentName的Key應為插件名(而不是包名),可使用createIntent方法來創建Intent對象 * * @param context Context對象 * @param intent 要打開Activity的Intent,其中ComponentName的Key必須為插件名 * @return 插件Activity是否被成功打開? * FIXME 是否需要Exception來做? * @see #createIntent(String, String) * @since 1.0.0 */public static boolean startActivity(Context context, Intent intent) { // TODO 先用舊的開啟Activity方案,以后再優化 ComponentName cn = intent.getComponent(); if (cn == null) { // TODO 需要支持Action方案 return false; } String plugin = cn.getPackageName(); String cls = cn.getClassName(); return Factory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO);}根據 RePlugin 的 startActivity() 和 createIntent() 方法注釋中的示例可知,啟動插件需要先用插件的名字和目標Activity的全路徑創建一個 Intent,然后調用 RePlugin.startActviity() 啟動即可:
Intent intent = RePlugin.createIntent("Plugin1", "cn.codingblock.plugin1.MainActivity");if (!RePlugin.startActivity(MainActivity.this, intent)) { Toast.makeText(mContext, "啟動失敗", Toast.LENGTH_LONG).show();} 點擊按鈕,輸出如下:
10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: start activity: intent=Intent { cmp=Plugin1/cn.codingblock.plugin1.MainActivity } plugin=Plugin1 activity=cn.codingblock.plugin1.MainActivity process=-214748364810-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: start activity: intent=Intent { cmp=Plugin1/cn.codingblock.plugin1.MainActivity } plugin=Plugin1 activity=cn.codingblock.plugin1.MainActivity process=-2147483648 download=true10-30 16:21:02.464 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: plugin=Plugin1 not found, start download ...10-30 16:21:02.469 20280-20280/cn.codingblock.repluginstudy D/RePlugin.ws001: isNeedToDownload(): V5 file not exists. Plugin = Plugin1 啟動失敗了!(插件名稱確實是:Plugin1,而不是 plugin1 )
把 ==createIntent() 方法的第一參數換成插件的包名 cn.codingblock.plugin1 ==試一試,居然可以了。
但是,注釋總不會這樣赤裸裸的坑我們吧!
卸載插件
RePlugin.uninstall("Plugin1"); 點擊卸載,輸入如下:
10-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy D/RePlugin.ws001: MP.pluginUninstall ... pluginName=Plugin110-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy D/RePlugin.ws001: Not installed. pluginName=Plugin1
沒卸載成功?哈哈,這個簡單,原套路把參數換成包名,果然可以了:
10-30 16:41:46.179 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: MP.pluginUninstall ... pluginName=cn.codingblock.plugin110-30 16:41:46.202 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: sendIntent pr=cn.codingblock.repluginstudy intent=Intent { act=ACTION_UNINSTALL_PLUGIN (has extras) }10-30 16:41:46.203 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: Clear plugin cache. pn=cn.codingblock.plugin110-30 16:41:46.204 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: removeInfo plugin table: info=PInfo { <cn.codingblock.plugin1:1(4)> [APK] processes=[] js={"pkgname":"cn.codingblock.plugin1","name":"cn.codingblock.plugin1","low":10,"high":10,"ver":1,"verv":2814792716779521,"path":"//data//user//0//cn.codingblock.repluginstudy//app_p_a//-347346251.jar","type":11,"frm_ver":4,"used":true} dex=/data/user/0/cn.codingblock.repluginstudy/app_p_od/-347346251.dex nlib=/data/user/0/cn.codingblock.repluginstudy/app_p_n/-347346251 } rc=true10-30 16:41:46.204 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: cached filename: cn.codingblock.plugin1 -> null10-30 16:41:46.275 10193-10263/cn.codingblock.repluginstudy V/RenderScript: 0xb34e8000 Launching thread(s), CPUs 4 啟動插件那里畢竟在官方教程里面找不到,但是 Plugin.uninstall() 方法傳入插件名即可這可是官方文檔說的,這次不會是官方文檔和源碼注釋合起伙來坑我們把? 經過多次試驗后,有個有趣的發現:對于啟動插件創建 Intent 的createIntent() 方法和 卸載插件的 RePlugin.uninstall() 方法,如果項目是使用繼承 RePluginApplication 方式的話,參數傳包名才生效;如果不是繼承的方式傳插件名才生效!(本人是在一款小米3手機上試驗的,由于并沒有廣泛測試,所以不保證其他手機也是這個套路)這真是奇了葩了!
卸載插件時有一點需要注意:如果插件正在運行,則不會立即卸載插件,而是將卸載訴求記錄下來。直到所有“正在使用插件”的進程結束并重啟后才會生效。(引自官方說明)
(二)內置插件
添加內置插件非常簡單,首先在主工程的 assets 目錄下創建一個 plugins 文件夾,然后將要作為插件的 apk 后綴名改成 .jar 并放入到新建的 plugins 文件夾下,剩下的事情就不用管了,交給 RePlugin 即可,也就說,框架會自動加載插件。
四、小結
初步體驗了一下發現,雖然目前有可能會有那么一點坑需要踩一踩,在使用起來也不比 DroidPlugin 方便,需要在宿主和插件兩端都要做集成工作。但總體明顯發現,這次的插件化框架明顯比以前那些的插件化框架資料更加的全面、豐富,而且從 wiki 上發現 RePlugin 團隊充滿了很大的熱情在孜孜不倦維護、更新,并且計劃明確,哪些功能在未來會添加、哪些功能在未來會被舍棄,一目了然,讓我們更加看到了 RePlugin 美好的未來,我相信在未來的插件化領域即使 RePlugin 不能一家獨大,也必然處于一個非常重要的地位!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。
新聞熱點
疑難解答