Android - 跨進程通信(IPC) 另一種便捷實現 詳解

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(跨進程通信)的庫 ,歡迎下載體驗
在這裏插入圖片描述
耗費了幾天時間,完成了這個 跨進程通信 的代碼,後續不斷完善吧,下面我講解下我寫這個代碼的思路。

解決的問題:
爲什麼要這麼弄,其實很多人會這麼問,用AIDL不就好了。

  • 只需要 正常的 接口(更易用,簡單) 與 原本的實現類 就可以使用.
  • 不用去寫繁瑣的AIDL了,都是接口.
  • 封裝性更強,就像調用本地的函數一樣.

涉及的知識點

流程圖
在這裏插入圖片描述
大概思路如下

第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);
	// loder,選用的類加載器。因爲代理的是zack,所以一般都會用加載zack的類加載器。
	// interfaces,被代理的類所實現的接口,這個接口可以是多個。
	// handler,綁定代理類的一個方法。
    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);
}
@ClassId("TestData")
public class TestData {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassId {
    String value();
}

ClassId classId = clazz.getAnnotation(ClassId.class);  獲取註解

  • 現在只是類的函數的調用,還需要加入對回調的支持.
// 回調是可以支持的. 需要使用 AIDL callback 的回調.
// 客戶單A -> run -> 服務端B 調用接收到,處理參數以及回調.
// 服務器B->回調相關的參數也需要 動態代理進行處理
return Proxy.newProxyInstance(
                clazz.getClassLoader(),
                new Class<?>[]{clazz},
                new XBusCallbackInvocationHandler(mCallback));
                
// 服務器B->動態代理的 invoke 就可以處理 回調的返回.                
public Object invoke(Object proxy, Method method, Object[] objects) {
	// 客戶端A的 callback 處理返回的回調參數,然後返回值給服務器B.
}

// 客戶端A 的 callback 再次處理.
Result callback(CallbackRequest request) {
	// 根據 erquest 返回的數據參數,傳入執行.
	value = method.invoke(callback, parameters);
	// 最後封裝的返回值,返回給 value.
}
  • 對於單例的函數,該如何處理?? getInstance

  • 能否使用自定義的類

// 答案是可以的,但是必須 implements Serializable 或者 Parcelable,不然要崩潰.
public class OtherMode implements Serializable {
}
  • Bitmap,View,Drawable 的傳輸是否有問題,測試一下. 如果使用JSON又這麼辦?

final Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
final Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);

// 1. Bundle 傳輸 Bitmap 毫無問題
// 適中、較小圖片,如果圖片過大如MB量級的,就不能正常工作了

// 2. Bundle 傳輸 View,會報錯,implements Serializable 也沒用
// java.lang.RuntimeException: Parcelable encountered IOException reading a Serializable object (name = com.demo.xbus.TestView)
public class TestView extends Button implements Serializable {
}

// 3. Bundle 傳輸 Drawable 報錯
// java.lang.RuntimeException: Parcel: unable to marshal value android.graphics.drawable.AdaptiveIconDrawable@ddbaeb1
// 但是自定義的 Drawable ,implements Serializable 就沒有問題.
public class TestDrawable extends GradientDrawable implements Serializable {
... ...
}

// 看到下面的代碼,我感覺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

https://github.com/kuangfrank/PieBridge

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章