基於JSON RPC的一種Android跨進程調用解決方案瞭解一下?

簡介

今天上午,看票圈有朋友分享愛奇藝的跨進程通訊框架——Andromeda,覺的仍是有點意思的。 之前項目中用到跨進程這種解決方案比較少,今天看了下Andromeda,發現調用方式很簡單。git

剛好最近一年都是在作後端工做,想到了Json RPC的方案,其實Android跨進程接也是一種rpc調用方式,那麼參考json rpc協議,經過aidl通道也能夠很簡單一種跨進程通訊方式,並且使用方式也很簡單。程序員

說幹就幹,可是做爲了高級程序員,確定要給項目起個名字高大上的名字——Bifrost(彩虹橋),參考復聯電影雷神上面的彩虹橋,寓意能夠傳送到各地,也表達Android跨進程通訊能夠暢通無阻。github

使用方式

在Android系統的跨進程調用須要用到AIDL方式,可是呢,操做起來很是麻煩,能夠傳遞基本類型,若是須要自定義類,那麼還須要實現Parcelable接口,同時也要寫很多代碼,操做起來繁瑣。json

像日常同樣,先定義一個接口和實現類就好了。後端

public interface INumberApi {

    int add(int a, int b);
}

複製代碼
public class NumberApiImpl implements INumberApi {

    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

複製代碼

註冊下接口和實現類,由於暫時沒有用到依賴注入工具,同時我也不想把功能作的很複雜,暫時手動註冊吧,作註冊前,先作好初始化工做。api

Bifrost.getInstance().init(this);
Bifrost.getInstance().register(IUserApi.class, new UserApiImpl());
Bifrost.getInstance().register(INumberApi.class, NumberApiImpl.class);

複製代碼

Bifrost暫時支持2個註冊方式,kv都是class類型,還有就是k是class,v是接口實現類的一個對象。bash

調用方式也很簡單。微信

IUserApi userApi = Bifrost.getInstance().getRemoteInstance(IUserApi.class);
User user = userApi.login("admin", "123456");

Timber.i("user = %s", user);

INumberApi numberApi = Bifrost.getInstance().getRemoteInstance(INumberApi.class);
int ret = numberApi.add(1, 2);

Toast.makeText(getApplicationContext(), "1 + 2 = " + ret, Toast.LENGTH_LONG).show();

複製代碼

實現原理

原理很簡單,見下圖所示。併發

當在原始的進程中,定義一個接口,而後獲取該對象的時候,其實返回值是一個用Java動態代理實現的一個值,當有使用方調用接口中的方法時候,會構形成一個RpcRequest對象,這個對象很簡單,就是標識這個調用的必要信息。框架

public class RpcRequest {

    @SerializedName("jsonRpc")
    public String jsonRpc = "1.0";

    @SerializedName("id")
    public String id = UUID.randomUUID().toString();

    @SerializedName("clazz")
    public String clazz;

    @SerializedName("method")
    public String method;

    @SerializedName("params")
    public String params;

    @Override
    public String toString() {
        return "RpcRequest{" +
                "jsonRpc='" + jsonRpc + '\'' + ", id='" + id + '\'' + ", clazz='" + clazz + '\'' +
                ", method='" + method + '\'' + ", params='" + params + '\'' + '}'; } } 複製代碼

好比上面的接口方法INumberApi.add,那麼生成的最終的json信息以下。

{
  "clazz": "cn.mycommons.bifrost.demo.api.INumberApi",
  "id": "0af23e0d-03ab-4cb9-8f52-2c7f7e094023",
  "jsonRpc": "1.0",
  "method": "add",
  "params": "[1,2]"
}

複製代碼

而後這個對象又會轉化成Req對象,這個對象是實現Parcelable接口的,用於2個進程之間通訊。

public class Req implements Parcelable {

    private String uuid;

    private String payload;

    public Req() {
        uuid = UUID.randomUUID().toString();
    }

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public String getPayload() {
        return payload;
    }

    public void setPayload(String payload) {
        this.payload = payload;
    }

    public static Creator<Req> getCREATOR() {
        return CREATOR;
    }

    @Override
    public String toString() {
        return "Req{" +
                "uuid='" + uuid + '\'' + ", payload='" + payload + '\'' + '}'; } protected Req(Parcel in) { uuid = in.readString(); payload = in.readString(); } public static final Creator<Req> CREATOR = new Creator<Req>() { @Override public Req createFromParcel(Parcel in) { return new Req(in); } @Override public Req[] newArray(int size) { return new Req[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(uuid); dest.writeString(payload); } } 複製代碼

上面的請求最終的信息變成了這樣,這個不是json,是Java的toString方法返回的。

Req{uuid='f6a8028a-3cba-4abf-912b-ee7979923fb5', payload='{"clazz":"cn.mycommons.bifrost.demo.api.INumberApi","id":"0af23e0d-03ab-4cb9-8f52-2c7f7e094023","jsonRpc":"1.0","method":"add","params":"[1,2]"}'}

複製代碼

當另一個進程獲取到這些數據後,那麼會作對應的反序列化,再次轉化成Req,而後又能夠獲得RpcRequest。

當取到RpcRequest時候,能夠根據裏面的信息,獲取當前調用接口的實現類,而後利用反射完成調用操做,獲得結果後再次把結果轉成json。

public class BifrostAidlImpl extends BifrostAidl.Stub {

    private Gson gson = new Gson();

    @Override
    public Resp exec(Req req) throws RemoteException {
        Timber.i("%s-->exec", this);
        Timber.i("req = %s", req);
        String data = req.getPayload();

        RpcRequest rpcRequest = gson.fromJson(data, RpcRequest.class);
        Timber.i("rpcRequest = %s", rpcRequest);

        try {
            Class<?> clazz = Class.forName(rpcRequest.clazz);
            Method method = null;
            for (Method m : clazz.getMethods()) {
                if (m.getName().equals(rpcRequest.method)) {
                    method = m;
                    break;
                }
            }
            if (method != null) {
                Class<?>[] types = method.getParameterTypes();
                List<Object> args = new ArrayList<>();
                if (!TextUtils.isEmpty(rpcRequest.params)) {
                    JSONArray array = new JSONArray(rpcRequest.params);
                    for (int i = 0; i < array.length(); i++) {
                        String o = array.getString(i);
                        args.add(gson.fromJson(o, types[i]));
                    }
                }
                Object instance = Bifrost.getInstance().getInstance(clazz);
                Timber.i("instance = %s", instance);
                Timber.i("method = %s", method);
                Timber.i("types = %s", Arrays.toString(types));
                Timber.i("params = %s", args);
                Object result = method.invoke(instance, args.toArray(new Object[0]));
                Timber.i("result = %s", result);

                return RespUtil.success(req.getUuid(), rpcRequest.id, result);
            }
            throw new RuntimeException("method " + rpcRequest.method + " cant not find");
        } catch (Exception e) {
            Timber.e(e);
            // e.printStackTrace();
            return RespUtil.fail(req.getUuid(), rpcRequest.id, e);
        }
    }
}

複製代碼

json也會轉成Resp,返回到原始的進程。而後解析數據,當作函數返回值。

總結

整體來講,這個流程仍是蠻清晰的,就是利用一個aidl通道,而後本身定義調用協議,我這邊參考了JSON RPC協議。固然了也能夠參考其餘的,這裏再也不表述。

整理下優缺點吧:

優勢

  • 使用和調用簡單,無上手壓力

  • 無需實現Parcelable接口,代碼簡潔

缺點

  • 由於涉及到json轉換,因此須要依賴gson

  • 調用過程當中含有屢次json序列化與反序列化,有反射操做,可能會有性能影響

  • 接口方法中的參數和返回值必需要是基本的類型,支持josn序列化和反序列化,但原始的AIDL方式基本上也是同樣,因此這條能夠接受

後續安排

暫時只是實現簡單的Demo,只是驗證這個思路是否可行,後續會作些優化操做,若有朋友有興趣,能夠一塊兒參與,本人聯繫方式 xiaqiulei@126.com

  • 支持異步操做,支持回調函數,可參考Retroft調用方式,可支持RxJava操做

  • 被調用進程支持線程池,增長併發量

  • 單獨的日誌操做,不依賴Timber

  • 支持同進程和誇進程調用

  • 支持事件的通知、發送,可參考BroadcastReceiver,EventBus等。

更多

2017上半年技術文章集合—184篇文章分類彙總

除了敲代碼,你還有什麼副業嗎?

若是你以爲此文對您有所幫助,歡迎隨時撩我 。微信公衆號:終端研發部

技術+職場
相關文章
相關標籤/搜索