Android--›360全面外掛化框架RePlugin使用概述
官網的wiki文件, 把RePlugin
的接入, 外掛的使用, 元件的呼叫介紹的很清楚.
但是關於宿主
和外掛
的互動,介紹的比較少.
本文主要介紹關於Binder
互動方式的使用.
1.1 在專案根目錄的 build.gradle(注意:不是 app/build.gradle) 中新增 replugin-host-gradle 依賴:
buildscript {
dependencies {
classpath 'com.qihoo360.replugin:replugin-host-gradle:2.2.4'
...
}
}
1.2 在 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.qihoo360.replugin.sample.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 { compile 'com.qihoo360.replugin:replugin-host-lib:2.2.4' ... }
1.3 讓工程的 Application 直接繼承自 RePluginApplication。
如果您的工程已有Application類,則可以將基類切換到RePluginApplication即可。或者您也可以用“非繼承式”接入。
public class MainApplication extends RePluginApplication {
}
既然聲明瞭Application,自然還需要在AndroidManifest中配置這個Application。
<application android:name=".MainApplication" ... />
2.1 在專案根目錄的 build.gradle(注意:不是 app/build.gradle) 中新增 replugin-plugin-gradle 依賴:
buildscript {
dependencies {
classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.4'
...
}
}
2.2 在 app/build.gradle 中應用 replugin-plugin-gradle 外掛,並新增 replugin-plugin-lib 依賴:
apply plugin: 'replugin-plugin-gradle'
dependencies {
compile 'com.qihoo360.replugin:replugin-plugin-lib:2.2.4'
...
}
新增內建外掛
- 將APK改名為:
外掛名.jar
- 放入主程式的
assets/plugins
目錄 - 如果更改了
assets/plugins
目錄下的外掛, 需要解除安裝重灌宿主才能生效
例如您要開啟一個Activity,則可以這麼玩:
Intent intent = new Intent(v.getContext(), ThemeDialogActivity.class);
context.startActivity(intent);
開啟服務呢?當然,如法炮製:
Intent intent = new Intent(v.getContext(), PluginDemoService1.class);
intent.setAction("action1");
context.startService(intent);
使用Content-Provider也是如此:
Uri uri = Uri.parse("content://com.qihoo360.replugin.sample.demo1.provider2/test");
ContentValues cv = new ContentValues();
cv.put("address", "beijing");
Uri urii = context.getContentResolver().insert(uri, cv);
當然了,還有大名鼎鼎的BroadcastReceiver:
Intent intent = new Intent();
intent.setAction("com.qihoo360.repluginapp.replugin.receiver.ACTION1");
intent.putExtra("name", "jerry");
context.sendBroadcast(intent);
和在APP中保持一致
.
// 方法1(最“單品”)
Intent intent = new Intent();
intent.setComponent(new ComponentName("demo2",
"com.qihoo360.replugin.sample.demo2.databinding.DataBindingActivity"));
context.startActivity(intent);
// 方法2(快速建立Intent)
Intent intent = RePlugin.createIntent("demo2",
"com.qihoo360.replugin.sample.demo2.databinding.DataBindingActivity");
context.startActivity(intent);
// 方法3(一行搞定)
RePlugin.startActivity(v.getContext(), new Intent(), "demo2",
"com.qihoo360.replugin.sample.demo2.databinding.DataBindingActivity");
// 方法4 (action)
Intent intent = new Intent(
"com.qihoo360.replugin.sample.demo2.action.theme_fullscreen_2");
RePlugin.startActivity(v.getContext(), intent, "demo2", null);
要獲取主程式的Context,需要呼叫 RePlugin.getHostContext() 方法即可。例如:
Context hostContext = RePlugin.getHostContext();
...
當然,獲取其它內容(如ClassLoader等)也如法炮製,可直接呼叫RePlugin類中的相應方法即可。
開啟外掛的Activity
要開啟一個外掛的Activity,您需要呼叫 RePlugin.startActivity() 方法。例如:
RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("demo1",
"com.qihoo360.replugin.sample.demo1.MainActivity"));
獲取外掛的Context
要獲取外掛的Context,可以呼叫 RePlugin.fetchContext() 方法。例如:
Context examContext = RePlugin.fetchContext("exam");
...
當然,獲取其它內容(如ClassLoader等)也如法炮製,可直接呼叫RePlugin類中的相應方法即可。
啟動、繫結外掛的Service
可以使用我們的 PluginServiceClient 類中的相應方法來操作。例如,若您想“繫結”一個服務,則可以:
PluginServiceClient.bindService(RePlugin.createIntent(
"exam", "AbcService"), mServiceConn);
其它方法都在 PluginServiceClient 裡,和系統引數完全一致,這裡不贅述。
請參見 JavaDoc 文件瞭解更多。
使用外掛的Content-Provider
同樣的,使用 PluginProviderClient 類中的方法即可操作Provider,具體做法如下:
PluginProviderClient.query(xxx);
plugin-demo2
工程
宣告aidl com.qihoo360.replugin.sample.demo2.IDemo2.aidl
package com.qihoo360.replugin.sample.demo2;
interface IDemo2 {
void hello(String str);
}
實現 com.qihoo360.replugin.sample.demo2.Demo2Impl
public class Demo2Impl extends IDemo2.Stub {
@Override
public void hello(String str) throws RemoteException {
Toast.makeText(RePlugin.getPluginContext(), str, Toast.LENGTH_SHORT).show();
}
}
註冊Binder
RePlugin.registerPluginBinder("demo2test", new Demo2Impl());
需要在工程中,宣告相同的aidl檔案 com.qihoo360.replugin.sample.demo2.IDemo2.aidl
package com.qihoo360.replugin.sample.demo2;
interface IDemo2 {
void hello(String str);
}
然後,在需要使用的外掛中呼叫:
IBinder b = RePlugin.fetchBinder("demo2", "demo2test");
if (b == null) {
return;
}
IDemo2 demo2 = IDemo2.Stub.asInterface(b);
try {
demo2.hello("helloooooooooooo");
} catch (RemoteException e) {
e.printStackTrace();
}
宿主中宣告aidl,並實現
//宣告 IMyAidlInterface.aidl
package com.qihoo360.replugin.sample.host;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
String test(String str);
}
//實現
public class IMyAidlInterfaceImpl extends IMyAidlInterface.Stub {
@Override
public String test(String str) throws RemoteException {
Toast.makeText(RePluginInternal.getAppContext(), str, Toast.LENGTH_SHORT).show();
return str;
}
}
註冊Binder
在宿主中註冊
Binder
需要使用registerHostBinder
.
在外掛中註冊
Binder
需要使用registerPluginBinder
.
RePlugin.registerHostBinder(new IHostBinderFetcher() {
@Override
public IBinder query(String module) {
if (TextUtils.equals(module, "IMyAidlInterfaceImpl")) {
return new IMyAidlInterfaceImpl();
}
return null;
}
});
外掛中獲取宿主的Binder, 並使用
IBinder binder = RePlugin.fetchBinder("main", "IMyAidlInterfaceImpl");
if (binder != null) {
IMyAidlInterface aidlInterface = IMyAidlInterface.Stub.asInterface(binder);
try {
appendText("aidl返回:" + aidlInterface.test("by plugin"));
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
appendText("binder is null");
}
需要注意的就是, fetchBinder
方法的第一個引數必須是main
,其他和外掛中使用Binder
一致.
宿主和外掛, 方法通用.
aidl
的宣告和實現, 與前2種方法一致.
區別在於註冊/獲取 Binder
的方式
註冊Binder
RePlugin.registerGlobalBinder("global", new GlobalInterfaceImpl());
獲取Binder
IBinder global = RePlugin.getGlobalBinder("global");
if (global != null) {
GlobalInterface globalInterface = GlobalInterface.Stub.asInterface(global);
try {
appendText("global aidl返回:" + globalInterface.testGlobal("by plugin"));
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
appendText("global binder is null");
}
5.1 宿主使用外掛中的類
com.qihoo360.replugin.sample.host.PluginFragmentActivity
/**
* 注意:
*
* 如果一個外掛是內建外掛,那麼這個外掛的名字就是檔案的字首,比如:demo1.jar外掛的名字就是demo1(host-gradle外掛自動生成),可以執行諸如RePlugin.fetchClassLoader("demo1")的操作;
* 如果一個外掛是外接外掛,通過RePlugin.install("/sdcard/demo1.apk")安裝的,則必須動態獲取這個外掛的名字來使用:
* PluginInfo pluginInfo = RePlugin.install("/sdcard/demo1.apk");
* RePlugin.preload(pluginInfo);//耗時
* String name = pluginInfo != null ? pluginInfo.getName() : null;
* ClassLoader classLoader = RePlugin.fetchClassLoader(name);
*/
boolean isBuiltIn = true;
String pluginName = isBuiltIn ? "demo1" : "com.qihoo360.replugin.sample.demo1";
//註冊相關Fragment的類
//註冊一個全域性Hook用於攔截系統對XX類的尋找定向到Demo1中的XX類主要是用於在xml中可以直接使用外掛中的類
RePlugin.registerHookingClass("com.qihoo360.replugin.sample.demo1.fragment.DemoFragment",
RePlugin.createComponentName(pluginName, "com.qihoo360.replugin.sample.demo1.fragment.DemoFragment"), null);
setContentView(R.layout.activity_plugin_fragment);
//程式碼使用外掛Fragment
ClassLoader d1ClassLoader = RePlugin.fetchClassLoader(pluginName);//獲取外掛的ClassLoader
try {
Fragment fragment = d1ClassLoader.loadClass("com.qihoo360.replugin.sample.demo1.fragment.DemoCodeFragment").asSubclass(Fragment.class).newInstance();//使用外掛的Classloader獲取指定Fragment例項
getSupportFragmentManager().beginTransaction().add(R.id.container2, fragment).commit();//新增Fragment到UI
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
需要新增依賴
provided files('libs/fragment.jar')//這個jar就是從Support-fragment中提取出來的並非特製包目的是為了騙過編譯期
com.qihoo360.replugin.sample.demo1.MainActivity
直接呼叫程式碼
// 此為RePlugin的另一種做法,可直接呼叫宿主的Utils
// 雖然不是很推薦(版本控制問題,見FAQ),但畢竟需求較大,且我們是“相對安全的共享程式碼”方案,故可以使用
final String curTime = TimeUtils.getNowString();
if (!TextUtils.isEmpty(curTime)) {
Toast.makeText(v.getContext(), "current time: " + TimeUtils.getNowString(), Toast.LENGTH_SHORT).show();
// 列印其ClassLoader
Log.d("MainActivity", "Use Host Method: cl=" + TimeUtils.class.getClassLoader());
} else {
Toast.makeText(v.getContext(), "Failed to obtain current time(from host)", Toast.LENGTH_SHORT).show();
}
需要新增依賴:
provided files('libs/common-utils-lib-1.0.0.jar')//這個jar就是從Host的utils中編譯生成的,其目的是為了騙過編譯期
通過反射呼叫 推薦方式
// 這是RePlugin的推薦玩法:反射呼叫Demo2,這樣"天然的"做好了"版本控制"
// 避免出現我們當年2013年的各種問題
ClassLoader cl = RePlugin.fetchClassLoader("demo2");
if (cl == null) {
Toast.makeText(v.getContext(), "Not install Demo2", Toast.LENGTH_SHORT).show();
return;
}
try {
Class clz = cl.loadClass("com.qihoo360.replugin.sample.demo2.MainApp");
Method m = clz.getDeclaredMethod("helloFromDemo1", Context.class, String.class);
m.invoke(null, v.getContext(), "Demo1");
} catch (Exception e) {
// 有可能Demo2根本沒有這個類,也有可能沒有相應方法(通常出現在"外掛版本升級"的情況)
Toast.makeText(v.getContext(), "", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
外掛資訊
<meta-data
android:name="com.qihoo360.plugin.name"
android:value="[你的外掛別名]" />
<meta-data
android:name="com.qihoo360.plugin.version.low"
android:value="[你的外掛協議版本號]" />
<meta-data
android:name="com.qihoo360.plugin.version.high"
android:value="[你的外掛協議版本號]" />
<meta-data
android:name="com.qihoo360.framework.ver"
android:value="[你的框架版本號]" />
外掛資訊的獲取
外掛資訊的類是 PluginInfo,無論是宿主還是外掛均可使用。而要獲取外掛資訊還是非常簡單的:
- 呼叫
RePlugin.getPlugin()
方法,可獲取任意外掛的資訊,也可以藉此判斷外掛是否安裝(若為null則表示沒有安裝) - 呼叫
RePlugin.getPluginInfoList()
方法可獲取所有已安裝(包括內建外掛)的資訊 - 呼叫
RePlugin.install()
方法,其返回值是“安裝成功後”的PluginInfo。若返回Null則表示“安裝失敗”
也許你還想學習更多, 來我的群吧, 我寫程式碼的能力, 遠大於寫文章的能力:
聯絡作者
請使用QQ掃碼加群, 小夥伴們都在等著你哦!
關注我的公眾號, 每天都能一起玩耍哦!