Android插件化-RePlugin項(xiàng)目集成與使用詳解
前言:前一段時(shí)間新開源了一種全面插件化的方案-- RePlugin,之前一種都在關(guān)注 DroidPlugin 并且很早也在項(xiàng)目中試用了,但最終沒有投入到真正的生產(chǎn)環(huán)節(jié),一方面是項(xiàng)目中沒有特別需要插件化的需求,另一方面也考慮到 DroidPlugin 不是特別穩(wěn)定,Android系統(tǒng)每更新一次 DroidPlugin 可能就會(huì)出現(xiàn)一些 Bug,畢竟 Hook 了 Android 原生的太多東西,系統(tǒng)一旦更新引發(fā) Bug 是在所難免的。當(dāng)然,這些并不能否認(rèn) DroidPlugin 的優(yōu)秀,它的原理和思路值得我們深入探究、學(xué)習(xí),前一段時(shí)間更新過幾篇插件化的原理分析的文章(基于 DrodiPlugin 原理)學(xué)習(xí)過程中不得不嘆服作者的思路和技術(shù)深度!前幾篇小白也能看懂的插件化系列文章仍然會(huì)不定期更新,但目前我們可以先來學(xué)習(xí)學(xué)習(xí) RePlugin,畢竟多學(xué)無害,也能互相參考他們的思路,比較優(yōu)缺點(diǎn)。
1.什么是RePlugin?
在Android開發(fā)領(lǐng)域,有關(guān)插件化的討論一直熱度不減。目前市面上的插件化方案雖然很多,但多數(shù)只能實(shí)現(xiàn)某些功能的插件化,距離開發(fā)者的預(yù)期尚有相當(dāng)差距。對此,在近期GMTC全球移動(dòng)技術(shù)大會(huì)上,360手機(jī)衛(wèi)士主程序架構(gòu)負(fù)責(zé)人張炅軒宣布,360的插件化框架RePlugin已經(jīng)可以實(shí)現(xiàn)“全面插件化”,同時(shí)具有出色的穩(wěn)定性和靈活性,可適用于各種類型的應(yīng)用上。
“RePlugin預(yù)計(jì)7月份開源,這將是我們獻(xiàn)給安卓世界最好的禮物。”360如是說。
2.RePlugin有什么用?
RePlugin是一套完整的、穩(wěn)定的、適合全面使用的,占坑類插件化方案,由360手機(jī)衛(wèi)士的RePlugin Team研發(fā),也是業(yè)內(nèi)首個(gè)提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。
3.RePlugin官方介紹
其主要優(yōu)勢有:
- 極其靈活:主程序無需升級(無需在Manifest中預(yù)埋組件),即可支持新增的四大組件,甚至全新的插件
- 非常穩(wěn)定:Hook點(diǎn)僅有一處(ClassLoader),無任何Binder Hook!如此可做到其崩潰率僅為“萬分之一”,并完美兼容市面上近乎所有的Android ROM
- 特性豐富:支持近乎所有在“單品”開發(fā)時(shí)的特性。包括靜態(tài)Receiver、Task-Affinity坑位、自定義Theme、進(jìn)程坑位、AppCompat、DataBinding等
- 易于集成:無論插件還是主程序,只需“數(shù)行”就能完成接入
- 管理成熟:擁有成熟穩(wěn)定的“插件管理方案”,支持插件安裝、升級、卸載、版本管理,甚至包括進(jìn)程通訊、協(xié)議版本、安全校驗(yàn)等
- 數(shù)億支撐:有360手機(jī)衛(wèi)士龐大的數(shù)億用戶做支撐,三年多的殘酷驗(yàn)證,確保App用到的方案是最穩(wěn)定、最適合使用的
一、集成主工程
1、在項(xiàng)目根目錄的 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 // 如果項(xiàng)目需要支持 AppComat,則需要將此配置置為 true
// 如果應(yīng)用需要個(gè)性化配置坑位數(shù)量,則需要添加以下代碼進(jìn)行配置
// 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'
}
以上代碼有三點(diǎn)需要注意:
- 需要將 apply plugin: 'replugin-host-gradle' 放在 android {...} 之后。
- 如果項(xiàng)目需要支持 AppComat,則需要將 repluginHostConfig 的 userAppCompat 置為 true。
- 如果應(yīng)用需要個(gè)性化配置坑位數(shù)量,則需要在 repluginHostConfig 中添加以下代碼進(jìn)行配置:
countNotTranslucentStandard = 6 countNotTranslucentSingleTop = 2 countNotTranslucentSingleTask = 3 countNotTranslucentSingleInstance = 2
3、讓工程的 Application 直接繼承自 RePluginApplication:
public class MyApplication extends RePluginApplication { }
當(dāng)然,同時(shí)不要忘了在 AndroidManifest 對 MyApplication 的相關(guān)配置。
說明:有時(shí)候由于項(xiàng)目原有結(jié)構(gòu)的需要,我們可能不能直接使用繼承 RePluginApplication 的方式,這個(gè)問題看來 RePlugin 開發(fā)者也想到了,所以還特地多了一種選擇,下面是項(xiàng)目的 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);
}
}
二、集成插件
新建一個(gè)工程做為插件APP,這里為了方便起見,直接在主工程中新建了一個(gè) Module。
1、同集成主工程類似,在根目錄的 build.gradle 添加 RePlugin Plugin Gradle 依賴(若是單獨(dú)創(chuàng)建插件工程,則不需要添加注釋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 對插件定義兩種方式一種是外置插件、一種是內(nèi)置插件。
- 外置插件:即從網(wǎng)絡(luò)下載或者從SD卡中獲得的,以 .apk 結(jié)尾。
- 內(nèi)置插件:內(nèi)置于 APP 之中,并隨 APP 一并發(fā)版,需要將插件 apk 改成 .jar 結(jié)尾放入主程序的assets/plugins目錄。
(一)外置插件的安裝(升級)、啟動(dòng)、卸載
安裝插件:
PluginInfo pluginInfo = RePlugin.install(Environment.getExternalStorageDirectory().getPath().toString() + "/plugin1.apk"); System.out.println(pluginInfo);
同時(shí)別忘了添加文件讀寫的權(quán)限。 輸出日下:
10-30 16:10:23.769 20280-20280/cn.codingblock.repluginstudy I/System.out: PInfo { <cn.codingblock.plugin1:1(4)> [APK] [DEX_EXTRACTED] 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} dex=/data/data/cn.codingblock.repluginstudy/app_p_od/-347346251.dex nlib=/data/data/cn.codingblock.repluginstudy/app_p_n/-347346251 }
安裝成功了! (升級插件也是用 install() 方法,不可降級,同本版可覆蓋安裝)
啟動(dòng)插件
先來看一下 RePlugin.java 中啟動(dòng)插件相關(guān)的源碼
/**
* 創(chuàng)建一個(gè)用來定向到插件組件的Intent <p>
* <p>
* 推薦用法: <p>
* <code>
* Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity");
* </code> <p>
* 當(dāng)然,也可以用標(biāo)準(zhǔn)的Android創(chuàng)建方法: <p>
* <code>
* Intent in = new Intent(); <p>
* in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"));
* </code>
*
* @param pluginName 插件名
* @param cls 目標(biāo)全名
* @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;
}
/**
* 開啟一個(gè)插件的Activity <p>
* 其中Intent的ComponentName的Key應(yīng)為插件名(而不是包名),可使用createIntent方法來創(chuàng)建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方案,以后再優(yōu)化
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);
}
根據(jù) RePlugin 的 startActivity() 和 createIntent() 方法注釋中的示例可知,啟動(dòng)插件需要先用插件的名字和目標(biāo)Activity的全路徑創(chuàng)建一個(gè) Intent,然后調(diào)用 RePlugin.startActviity() 啟動(dòng)即可:
Intent intent = RePlugin.createIntent("Plugin1", "cn.codingblock.plugin1.MainActivity");
if (!RePlugin.startActivity(MainActivity.this, intent)) {
Toast.makeText(mContext, "啟動(dòng)失敗", Toast.LENGTH_LONG).show();
}
點(diǎn)擊按鈕,輸出如下:
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=-2147483648
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=-2147483648 download=true
10-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
啟動(dòng)失敗了!(插件名稱確實(shí)是:Plugin1,而不是 plugin1 )
把 ==createIntent() 方法的第一參數(shù)換成插件的包名 cn.codingblock.plugin1 ==試一試,居然可以了。
但是,注釋總不會(huì)這樣赤裸裸的坑我們吧!
卸載插件
RePlugin.uninstall("Plugin1");
點(diǎn)擊卸載,輸入如下:
10-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy D/RePlugin.ws001: MP.pluginUninstall ... pluginName=Plugin1 10-30 16:31:21.988 5006-5006/cn.codingblock.repluginstudy D/RePlugin.ws001: Not installed. pluginName=Plugin1
沒卸載成功?哈哈,這個(gè)簡單,原套路把參數(shù)換成包名,果然可以了:
10-30 16:41:46.179 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: MP.pluginUninstall ... pluginName=cn.codingblock.plugin1
10-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.plugin1
10-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=true
10-30 16:41:46.204 10193-10193/cn.codingblock.repluginstudy D/RePlugin.ws001: cached filename: cn.codingblock.plugin1 -> null
10-30 16:41:46.275 10193-10263/cn.codingblock.repluginstudy V/RenderScript: 0xb34e8000 Launching thread(s), CPUs 4
啟動(dòng)插件那里畢竟在官方教程里面找不到,但是 Plugin.uninstall() 方法傳入插件名即可這可是官方文檔說的,這次不會(huì)是官方文檔和源碼注釋合起伙來坑我們把? 經(jīng)過多次試驗(yàn)后,有個(gè)有趣的發(fā)現(xiàn):對于啟動(dòng)插件創(chuàng)建 Intent 的createIntent() 方法和 卸載插件的 RePlugin.uninstall() 方法,如果項(xiàng)目是使用繼承 RePluginApplication 方式的話,參數(shù)傳包名才生效;如果不是繼承的方式傳插件名才生效?。ū救耸窃谝豢钚∶?手機(jī)上試驗(yàn)的,由于并沒有廣泛測試,所以不保證其他手機(jī)也是這個(gè)套路)這真是奇了葩了!
卸載插件時(shí)有一點(diǎn)需要注意:如果插件正在運(yùn)行,則不會(huì)立即卸載插件,而是將卸載訴求記錄下來。直到所有“正在使用插件”的進(jìn)程結(jié)束并重啟后才會(huì)生效。(引自官方說明)
(二)內(nèi)置插件
添加內(nèi)置插件非常簡單,首先在主工程的 assets 目錄下創(chuàng)建一個(gè) plugins 文件夾,然后將要作為插件的 apk 后綴名改成 .jar 并放入到新建的 plugins 文件夾下,剩下的事情就不用管了,交給 RePlugin 即可,也就說,框架會(huì)自動(dòng)加載插件。
- 內(nèi)置插件無需開發(fā)者安裝,啟動(dòng)方式和外置插件一致,但不可刪除。
- 內(nèi)置插件可通過 RePlugin.install() 升級(需要先將升級包下載好),升級后等同于外置插件。
四、小結(jié)
初步體驗(yàn)了一下發(fā)現(xiàn),雖然目前有可能會(huì)有那么一點(diǎn)坑需要踩一踩,在使用起來也不比 DroidPlugin 方便,需要在宿主和插件兩端都要做集成工作。但總體明顯發(fā)現(xiàn),這次的插件化框架明顯比以前那些的插件化框架資料更加的全面、豐富,而且從 wiki 上發(fā)現(xiàn) RePlugin 團(tuán)隊(duì)充滿了很大的熱情在孜孜不倦維護(hù)、更新,并且計(jì)劃明確,哪些功能在未來會(huì)添加、哪些功能在未來會(huì)被舍棄,一目了然,讓我們更加看到了 RePlugin 美好的未來,我相信在未來的插件化領(lǐng)域即使 RePlugin 不能一家獨(dú)大,也必然處于一個(gè)非常重要的地位!
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android Studio 下自動(dòng)注釋(自定義作者,類作用等)圖文詳解
android studio 下自動(dòng)注釋功能居然被隱藏了,很多功能都不見了,下面小編通過本文給大家分享Android Studio 下自動(dòng)注釋(自定義作者,類作用等)圖文詳解,需要的朋友參考下吧2017-11-11
android7.0實(shí)現(xiàn)分享圖片到朋友圈功能
這篇文章主要為大家詳細(xì)介紹了android7.0實(shí)現(xiàn)分享圖片到朋友圈功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Android 簡單的實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼功能
這篇文章主要介紹了Android 簡單的實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼功能,幫助大家更好的理解和學(xué)習(xí)使用Android開發(fā),感興趣的朋友可以了解下2021-03-03
深入解析Android系統(tǒng)中應(yīng)用程序前后臺切換的實(shí)現(xiàn)要點(diǎn)
這篇文章主要介紹了Android系統(tǒng)中應(yīng)用程序前后臺切換的實(shí)現(xiàn)要點(diǎn),除了切換操作的效果之外還重點(diǎn)講解了判斷程序運(yùn)行于前臺還是后臺的方法,需要的朋友可以參考下2016-04-04
Android消息通知Notification常用方法(發(fā)送消息和接收消息)
最近在做消息通知類Notification的相關(guān)業(yè)務(wù),利用閑暇時(shí)間總結(jié)一下,主要分為兩部分來記錄:發(fā)送消息和接收消息,對Android消息通知相關(guān)知識感興趣的朋友一起看看吧2024-02-02
android教程之把自己的應(yīng)用加入到系統(tǒng)分享中
在Android系統(tǒng)中打開相冊中的某張圖片, 點(diǎn)擊右上角的分享按鈕會(huì)彈出分享列表, 把自己的應(yīng)用加入到里面來,下面是設(shè)置方法2014-02-02

