Android TV開發-桌面 跨程序通訊(IPC) 詳解
@[TOC]
1. 寫在前面
Android 程序間通訊 的 幾種方式:
- 四大元件間傳遞Bundle
- 使用檔案共享方式,多程序讀寫一個相同的檔案,獲取檔案內容進行互動;
- 使用Messenger,一種輕量級的跨程序通訊方案,底層使用AIDL實現(實現比較簡單,博主開始本文前也想了一下是否要說一下這個東西,最後還是覺得沒有這個必要,Google一下就能解決的問題,就不囉嗦了);
- 使用AIDL(Android Interface Definition Language),Android介面定義語言,用於定義跨程序通訊的介面;
- 使用ContentProvider,常用於多程序共享資料,比如系統的相簿,音樂等,我們也可以通過ContentProvider訪問到;
- Socket(Domain Socket),Linux 桌面的DBus就是使用此方式實現的.
- ... ...
各個優缺點對比:
多個app通過統一的介面操作,不建議起執行緒的耗時任務,支援 RPC(遠端過程呼叫,程序A呼叫程序B提供的 函式/方法)
比如 登陸介面,支付,定位服務 等等
這裡唯一比較好的應該屬於 AIDL 的方式了,不僅有回撥,還可以傳引數,返回引數。
但是 通常要寫很多程式碼,操作繁雜,麻煩;不同業務的跨程序呼叫,不易複用。
我們在下面的文章將講解用另一種簡潔的方式實現。
2. 跨程序通訊的實現
我編寫 了一個 XBus(跨程序通訊)的庫 ,歡迎下載體驗 耗費了幾天時間,完成了這個 跨程序通訊 的程式碼,後續不斷完善吧,下面我講解下我寫這個程式碼的思路。
涉及的知識點:
- 動態代理 Proxy.newProxyInstance,InvocationHandler
- JSON 或者 Bundle(文章使用此方法)
- AIDL
- 反射(joor Refalect)
- 序列化/反序列化
流程圖: 大概思路如下:
第1步:程序A 繫結 程序B 的AIDL 服務 並 返回 一個 XBusAidl.Stub 的AIDL;程序B註冊好需要被呼叫的函式.
// 繫結服務 Intent intent = new Intent(context, XBusService.class); context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); // 繫結回撥(成功,失敗的處理) ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mXBusAidl = XBusAidl.Stub.asInterface(service); } }; // AIDL 介面,主要用於傳輸相應的呼叫引數以及返回. 關於 Response,Request 可以檢視相應的程式碼. interface XBusAidl { Response run(in Request request); }
// 程序B 註冊需要被呼叫的相關函式
XBus.getInstance().register(ITestData.class, TestData.class);
// 之所以要註冊,是因為介面的呼叫與實現的類不能一一對應起來,所以需要提前註冊.
public void register(Class<?> face, Class<?> impl) {
mRegisterClassMap.put(face, impl);
}
第2步:函式呼叫,觸發 動態代理,將 類名,函式,引數,呼叫 繫結服務返回的 XBusAidl.Stub 的AIDL 的 run函式傳過去
// 準備被呼叫的介面
// @ClassId("TestData")
public interface ITestData {
public String testtesttest(int i, String s);
}
// 動過動態代理,然後呼叫,觸發 invoke,然後將 類名,函式,引數傳過去,接受返回值,搞定.
ITestData testData = XBus.getInstance().getCreateCall(ITestData.class);
String result = testData.testtesttest(10, "測試函式");
public <T> T getCreateCall(Class<T> tClass) {
Class<?>[] interfaces = new Class[]{tClass};
XBusHandler handler = new XBusHandler(tClass, mXBusAidl);
Object proxy = Proxy.newProxyInstance(tClass.getClassLoader(), interfaces, handler);
return (T) proxy;
}
class XBusHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
String clazzName = clazz.getName();
// 1. 建立 Request.
Request request = new Request();
// 這裡使用了 JSON 與 Bundle的傳引數與接收返回值 的方式,還需要繼續測試,才知道那種方式更優.
// 主要是思路,無論是 JSON 還是 Bundle,鬥不過是將資料傳輸過去而已,本文章使用Bundle講解.
// 2. bundle 傳遞 args引數. 方法,類名
Bundle bundle = new Bundle();
bundle.putSerializable(Request.ARGS_KEY, args);
bundle.putString(Request.METHOD_KEY, methodName);
bundle.putString(Request.CLAZZ_KEY, clazzName);
request.setBundle(bundle);
// 3. AIDL 遠端呼叫函式run (函式執行過程)
Response response = xBusAidl.run(request);
// 4. 處理 返回值.
Bundle bundle1 = response.getBResult();
return bundle1.getSerializable(Response.RESULT_KEY);
}
}
第3步:處理 函式執行,返回值.
// 繫結服務返回的 XBusAidl.Stub 的AIDL 的 run 函式 執行.
public Response run(Request request) throws RemoteException {
return runBundle(request);
}
private Response runBundle(Request request) {
Bundle bundle = request.getBundle();
Object[] bargs = (Object[]) bundle.getSerializable(Request.ARGS_KEY);
String clazzName = bundle.getString(Request.CLAZZ_KEY);
String method = bundle.getString(Request.METHOD_KEY);
// 1. 建立Response.
Response response = new Response();
// 2. 函式呼叫
Class<?> iclazz = Reflect.on(clazzName).get();
Object createObject = XBus.getInstance().getCreateObject(iclazz); // 獲取已經註冊的相關函式
Object result = Reflect.on(createObject).call(method, bargs).get();
// 3. 處理返回值
bundle.putSerializable(Response.RESULT_KEY, (Serializable) result);
response.setBResult(bundle);
// 4. 返回response,response 帶有 code, message,主要是標誌函式是否執行成功等資訊.
return response;
}
// 這裡是程序B已經註冊的函式
public <T> T getCreateObject(Class<?> face) {
if (mRegisterClassMap.containsKey(face)) {
Class<?> aClass = mRegisterClassMap.get(face); // 根據對應的介面找到對應的類
return Reflect.on(aClass).create().get(); // 建立對應的類
}
return null;
}
原理已經分析完了,大概步驟就是,註冊AIDL服務,使用動態代理呼叫函式,處理函式執行,返回處理,沒了,就是這麼簡單.
3. 擴充套件思考
-
是否考慮過一種方式,只提供中轉分發(服務一直存在),不作為提供服務。
-
AIDL 是否可以使用 Socket 來替代,是可以的喔(DBus就是例子),不過需要使用JSON或者其它方式來序列化,反序列化.
-
註冊相應的介面與類,感覺還是比較麻煩,後續準備加入註解的方式支援.
@ClassId("TestData")
public interface ITestData {
public String testtesttest(int i, String s);
}
- 現在只是類的函式的呼叫,還需要加入對回撥的支援.
- Bitmap,View,Drawable 的傳輸是否有問題,測試一下. 如果使用JSON又這麼辦?
// 看到下面的程式碼,我感覺JSON的使用,心都涼了一半.
// 來自網上的一段程式碼,關於 Bitmap 轉換成JSON的,感覺效果應該不是很好吧(如果圖片幾MB).
/*
* This functions converts Bitmap picture to a string which can be
* JSONified.
* */
private String getStringFromBitmap(Bitmap bitmapPicture) {
final int COMPRESSION_QUALITY = 100;
String encodedImage;
ByteArrayOutputStream byteArrayBitmapStream = new ByteArrayOutputStream();
bitmapPicture.compress(Bitmap.CompressFormat.PNG, COMPRESSION_QUALITY,
byteArrayBitmapStream);
byte[] b = byteArrayBitmapStream.toByteArray();
encodedImage = Base64.encodeToString(b, Base64.DEFAULT);
return encodedImage;
}
/*
* This Function converts the String back to Bitmap
* */
private Bitmap getBitmapFromString(String stringPicture) {
byte[] decodedString = Base64.decode(stringPicture, Base64.DEFAULT);
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
return decodedByte;
}
4. 參考資料
https://github.com/LiushuiXiaoxia/Bifrost
跨程序通訊框架. https://github.com/Xiaofei-it/Hermes
愛奇藝的跨程序通訊框架 https://github.com/iqiyi/Andromeda