Hermes——跨進程通信(IPC)框架,使用介紹

概述

Hermes的源碼地址

Hermes的Demo地址

  1. Android進程間通信IPC框架
  2. 像調用本地函數一樣調用其他進程的函數
  3. 在本地進程創建其他進程類的對象
  4. 在本進程獲取其他進程的單例
  5. 在本進程使用其他進程的工具類
  6. 支持進程間函數回調,調用其他進程函數的時候可以傳入回調函數,讓其他進程回調本進程的方法
  7. 自帶內存優化,內置兩個垃圾回收器,地進程在遠端進程創建的實例和本地進程傳給遠端進程的回調接口會被自動回收。

基本使用

compile 'xiaofei.library:hermes:0.7.0'

兩個進程共享一個對象單例

//在進程A中,類使用註解標記該類的id
@ClassId(“Singleton”)
public class Singleton {
    //屬性
    private volatile String mData;
    
	//實現單例,代碼省略
    public static Singleton getInstance();
 
	//方法使用註解標記該方法的id
    @MethodId(“setData”)
    public void setData(String data) {
        mData = data;
    }

    @MethodId(“getData”)
    public String getData() {
        return mData;
    }
}

進程B要訪問在進程A中的Singleton對象,如下:

//在進程B中,自定義接口
//該接口指定與Singleton的註解的classid一致
@ClassId(“Singleton”)
public interface ISingleton {
	//方法id與Singleton的註解的methodid一致
    @MethodId(“setData”)
    void setData(String data);

    @MethodId(“getData”)
    String getData();
}
//使用如下:
//獲得Singleton對象
ISingleton singleton = Hermes.getInstance(ISingleton.class);
//調用方法
singleton.setData(“Hello, Hermes!);
//調用方法
Log.v(TAG, singleton.getData());

在其他進程調用主進程的函數

Hermes支持任意進程之間的函數調用

AndroidManifest.xml

<service android:name="xiaofei.library.hermes.HermesService$HermesService0"/>

主進程初始化Hermes

//在給其他進程提供函數的進程中,使用Hermes.init(Context)初始化
Hermes.init(Context);

子進程連接Hermes

//子進程鏈接Hermes後纔可以使用Hermes的服務
//在Application.OnCreate()或Activity.OnCreate()
Hermes.connect(Context)

查看通信的進程是否還活着

Hermes.isConnected()

事先註冊被調用的類

//進程A中,被進程B調用的類需要事先註冊
//如果類上面沒有加上註解,那麼註冊就不是必須的,Hermes會通過類名進行反射查找相應的類
//有兩種註冊類的API:
Hermes.register(Class<?>)
Hermes.register(Object)
Hermes.register(object)等價於Hermes.register(object.getClass())

設置連接監聽

//在連接之前給Hermes設置監聽器
Hermes.setHermesListener(new HermesListener() {
    @Override
    public void onHermesConnected(Class<? extends HermesService> service) {
        //連接成功,首先獲取單例
       IUserInfo iUserinfo = Hermes.getInstance(IUserInfo.class);
        //通過單例獲取UserInfo
       String name = iUserinfo.getUserName();
    }
});
//連接Hermes服務
Hermes.connect(context);

連接Hermes服務

Hermes.connect(context);//需要連接服務才能得到其他進程的實例

斷開Hermes服務

Hermes.disconnect(context);

創建實例

//進程B中,創建進程A中的實例有三種方法:
Hermes.newInstance(Class,Object…)
Hermes.getInstance(Class,Object…)
Hermes.getUtilityClass(Class)

Hermes.newInstance(Class,Object…)

  1. 在進程A中創建指定類的實例,並將引用返回給進程B。

  2. 函數的第二個參數將傳給指定類的對應的構造器。

  3. 在進程B中,調用Hermes.newInstance(ILoadingTask.class, “xxx”, true)得到LoadingTask實例。

  4. 注意:該實例不是單例,該實例爲當前線程所擁有的實例。

@ClassId(“LoadingTask”)
public class LoadingTask {

    public LoadingTask(String path, boolean showImmediately) {
        //...
    }

    @MethodId(“start”)
    public void start() {
        //...
    }
}

@ClassId(“LoadingTask”)
public interface ILoadingTask {
    @MethodId(“start”)
    void start();
}

Hermes.getInstance(Class , Object…)

  1. 在進程A中通過指定類的getInstance()方法創建實例,並將引用返回給進程B。
  2. 第二個參數將傳給對應的getInstance()方法。
  3. 該函數適合獲取單例,這樣進程A和進程B就使用同一個單例。
  4. 進程B中,調用Hermes.getInstance(IBitmapWrapper.class, “XXX”)將得到BitmapWrapper的實例。
  5. 進程B中,調用Hermes.getInstance(IBitmapWrapper.class, 100)將得到BitmapWrapper的實例。
@ClassId(“BitmapWrapper”)
public class BitmapWrapper {

    @GetInstance
    public static BitmapWrapper getInstance(String path) {
        //這裏可以寫成單例,兩個進程可以獲得當前這個實例
    }

    @GetInstance
    public static BitmapWrapper getInstance(int label) {
        //...
    }

    @MethodId(“show”)
    public void show() {
        //...
    }

}

@ClassId(“BitmapWrapper”)
public interface IBitmapWrapper {

    @MethodId(“show”)
    void show();

}

Hermes.getUtilityClass(Class )

  1. 獲取進程A的工具類。
  2. 這種做法在插件開發中很有用。
  3. 主app和插件app存在不同的進程中,爲了維護方便,應該使用統一的工具類。
  4. 插件app可以通過這個方法獲取主app的工具類。
@ClassId(“Maths”)
public class Maths {

    @MethodId(“plus”)
    public static int plus(int a, int b) {
       //模擬加法
    }

    @MethodId(“minus”)
    public static int minus(int a, int b) {
        //模擬減法
    }

}

@ClassId(“Maths”)
public interface IMaths {

    @MethodId(“plus”)
    int plus(int a, int b);

    @MethodId(“minus”)
    int minus(int a, int b);
}

進程B中,使用下面代碼使用進程A的工具類

IMaths maths = Hermes.getUtilityClass(IMaths.class);
int sum = maths.plus(3, 5);
int diff = maths.minus(3, 5);

其他註解的使用

//@WithinProcess//不讓其他進程訪問
@ClassId("HermesOtherBean")
public class HermesOtherBean {

    //@WeakRef:持有當前回調的弱引用
    //@Background:不讓回調函數運行在主線程
    public void customBackMethod(HermesMethodBean hermesMethodBean, IHermesOtherListener iHermesOtherListener) {
        if (hermesMethodBean != null) {
            iHermesOtherListener.getVoid();
            iHermesOtherListener.setInteger(100);
            int i = iHermesOtherListener.getBackInteger();
            iHermesOtherListener.setString("customBackMethod");
            String str = iHermesOtherListener.getBackString();
            iHermesOtherListener.setHermesMethod(hermesMethodBean);
            Log.i("appjson", "getbackInteger:" + i + ";getBackString:" + str);
        }
    }
}
@ClassId("IHermesOtherListener")
public interface IHermesOtherListener {

    void getVoid();

    int getBackInteger();

    void setInteger(int i);

    String getBackString();

    void setString(String str);

    HermesMethodBean getHermesMethod();

    void setHermesMethod(HermesMethodBean hermesMethod);
}
@ClassId("HermesOtherBean")
public interface IHermesOtherBean {

    //@WeakRef:持有當前回調的弱引用
    //@Background:不讓回調函數運行在主線程
    void customBackMethod(HermesMethodBean hermesMethodBean, @WeakRef @Background IHermesOtherListener iHermesOtherListener);
}
public class HermesOtherService extends Service {
    public HermesMethodBean hermesMethodBean = new HermesMethodBean(1, "a", 1D);

    @Override
    public void onCreate() {
        super.onCreate();
        //在連接之前給Hermes設置監聽器
        Hermes.setHermesListener(new HermesListener() {
            @Override
            public void onHermesConnected(Class<? extends HermesService> service) {
                //連接成功,首先獲取單例
                try {
                    //靜態方法沒有限制,這裏不清楚原因需要先調用getUtilityClass在調用newInstance才能正常使用
                    Hermes.getUtilityClass(IHermesOtherBean.class);
                    //如果是new的有限制
                    IHermesOtherBean iHermesOtherBeanNew = Hermes.newInstance(IHermesOtherBean.class);
                    if (iHermesOtherBeanNew == null)
                        Log.i("appjson", "當前IHermesOtherBean==null");
                    else {
                        //這種匿名類是錯誤的寫法
//                        Log.i("appjson", iHermesOtherBean.customMethod(new HermesMethodBean(1, "aaaa", 1D)));
                        //這種private的對象也是錯誤的
//                        HermesMethodBean hermesMethodBean = new HermesMethodBean(1, "a", 1D);
//                        iHermesOtherBean.customMethod(hermesMethodBean);
                        //必須是非private且不是匿名類的對象
//                        hermesMethodBean = new HermesMethodBean(1, "a", 1D);
                        iHermesOtherBeanNew.customBackMethod(hermesMethodBean, new IHermesOtherListener() {
                            @Override
                            public void getVoid() {
                                Log.i("appjson", "getVoid()");
                            }

                            @Override
                            public int getBackInteger() {
                                Log.i("appjson", "getBackInteger()");
                                return 100;
                            }

                            @Override
                            public void setInteger(int i) {
                                Log.i("appjson", "setInteger(" + i + ")");

                            }

                            @Override
                            public String getBackString() {
                                Log.i("appjson", "getBackString()");
                                return "getBackString-admin";
                            }

                            @Override
                            public void setString(String str) {
                                Log.i("appjson", "setString(" + str + ")");
                            }

                            @Override
                            public HermesMethodBean getHermesMethod() {
                                Log.i("appjson", "getHermesMethod()");
                                return new HermesMethodBean(50, "test", 99D);
                            }

                            @Override
                            public void setHermesMethod(HermesMethodBean hermesMethod) {
                                Log.i("appjson", "setHermesMethod():" + hermesMethod.toString());
                            }
                        });
                    }
                } catch (Exception e) {
                    Log.i("appjson", "error:" + e.getMessage());
                }
            }
        });
        //連接Hermes服務
        Hermes.connect(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //斷開Hermes服務
        Hermes.disconnect(this);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

注意事項

  1. 如果兩個進程屬於兩個不同的app(分別叫App A和App B),App A想訪問App B的一個類,並且App A的接口和App B的對應類有相同的包名和類名,那麼就沒有必要在類和接口上加@ClassId註解。

    1. 但是要注意使用ProGuard後類名和包名仍要保持一致。
  2. 如果接口和類裏面對應的方法的名字相同,那麼也沒有必要在方法上加上@MethodId註解。

    1. 但是要注意使用ProGuard後接口內的方法名字必須仍然和類內的對應方法名字相同。
  3. 如果進程A的一個類上面有一個@ClassId註解,這個類在進程B中對應的接口上有一個相同的@ClassId註解,那麼進程A在進程B訪問這個類之前必須註冊這個類。

    1. 否則進程B使用Hermes.newInstance()、Hermes.getInstance()或Hermes.getUtilityClass()時,Hermes在進程A中找不到匹配的類。
    2. 類可以在構造器或者Application.OnCreate()中註冊。
    3. 但是如果類和對應的接口上面沒有@ClassId註解,但有相同的包名和類名,那麼就不需要註冊類。
    4. Hermes通過包名和類名匹配類和接口。
    5. 對於接口和類裏面的函數,上面的說法仍然適用。
  4. 如果不想讓一個類或者函數被其他進程訪問,可以在上面加上@WithinProcess註解。

  5. 使用Hermes跨進程調用函數的時候,傳入參數的類型可以是原參數類型的子類,但不可以是匿名類和局部類。

    1. 傳遞的參數不能是匿名類:maths.puls(new A(){});
    2. 傳遞的參數也不能是private的:private A a=new A(){};
    3. 但是回調函數例外,關於回調函數詳見第7點
  6. 如果被調用的函數的參數類型和返回值類型是int、double等基本類型或者String這樣的Java通用類型,上面的說法可以很好地解決問題。但如果類型是自定義的類,比如第5點中的自定義類A,並且兩個進程分別屬於兩個不同app,那麼你必須在兩個app中都定義這個類,且必須保證代碼混淆後,兩個類仍然有相同的包名和類名。

    1. 不過你可以適用@ClassId和@MethodId註解,這樣包名和類名在混淆後不同也不要緊了。
  7. 如果被調用的函數有回調參數,那麼函數定義中這個參數必須是一個接口,不能是抽象類。請特別注意回調函數運行的線程。

    1. 如果進程A調用進程B的函數,並且傳入一個回調函數供進程B在進程A進行回調操作,那麼默認這個回調函數將運行在進程A的主線程(UI線程)。如果你不想讓回調函數運行在主線程,那麼在接口聲明的函數的對應的回調參數之前加上@Background註解。

    2. 如果回調函數有返回值,那麼你應該讓它運行在後臺線程。如果運行在主線程,那麼返回值始終爲null。

    3. 默認情況下,Hermes持有回調函數的強引用,這個可能會導致內存泄漏。你可以在接口聲明的對應回調參數前加上@WeakRef註解,這樣Hermes持有的就是回調函數的弱引用。

    4. 如果進程的回調函數被回收了,而對方進程還在調用這個函數(對方進程並不會知道回調函數被回收),這個不會有任何影響,也不會造成崩潰。

    5. 如果回調函數有返回值,那麼就返回null。

    6. 如果你使用了@Background和@WeakRef註解,你必須在接口中對應的函數參數前進行添加。

    7. 如果加在其他地方,並不會有任何作用。

    8. @ClassId(“Foo”)
      public class Foo {
          public static void f(int i, Callback callback) {
          }
      }
      
      @ClassId(“callback”)
      public interface Callback {
          void callback();
      }
      
      @ClassId(“Foo”)
      public interface IFoo {
          void f(int i, @WeakRef @Background Callback callback);
      }
      
  8. 調用函數的時候,任何Context在另一個進程中都會變成對方進程的applicationContext。

  9. 數據傳輸是基於Json的。

  10. 使用Hermes的時候,有任何的錯誤都會使用android.util.Log.e()打出錯誤日誌。

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