Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 餓了麼開源項目Hermes:新穎巧妙易用的Android進程間通信IPC框架

餓了麼開源項目Hermes:新穎巧妙易用的Android進程間通信IPC框架

編輯:關於Android編程

Android進程間通信IPC是比較高級的話題,很多Android程序員碰到IPC就覺得頭疼,尤其是AIDL這類東西。

公司最近在研究DroidPlugin插件開發,DroidPlugin把每個子app都變成一個進程。這樣的話子app和主app如果需要共享數據,就需要IPC。所以我開發了Hermes框架,讓IPC變得非常簡單優雅。

項目地址:

https://github.com/Xiaofei-it/Hermes

這個框架開發難度很大,涉及到AIDL、binder、反射、注解、進程間垃圾回收、動態代理等很多技術。我以後會對源碼進行解析。

本來我寫的文檔是英文的,後來為了便於讀者查閱,特意翻譯成了中文文檔。希望大家持續關注,可以給個star。

中文文檔鏈接:

https://github.com/Xiaofei-it/Hermes/blob/master/README-ZH-CN.md

 

 

Hermes是一套新穎巧妙易用的Android進程間通信IPC框架。這個框架使得你不用了解IPC機制就可以進行進程間通信,像調用本地函數一樣調用其他進程的函數。

你們知道把英文文檔翻譯成中文有多麼蛋疼嗎???還不給我star一下 o(╥﹏╥)o

特色

  1. 使得進程間通信像調用本地函數一樣方便簡單。

  2. 輕而易舉在本地進程創建其他進程類的對象,輕而易舉在本進程獲取其他進程的單例,輕而易舉在本進程使用其他進程的工具類。

  3. 支持進程間函數回調,調用其他進程函數的時候可以傳入回調函數,讓其他進程回調本進程的方法。

  4. 自帶內存優化,並且支持跨進程垃圾回收。

基本原理

IPC的主要目的是調用其他進程的函數,Hermes讓你方便地調用其他進程函數,調用語句和本地進程函數調用一模一樣。

比如,單例模式經常在Android App中使用。假設有一個app有兩個進程,它們共享如下單例:

@ClassId(“Singleton”)
public class Singleton {

    private static Singleton sInstance = null;

    private volatile String mData;

    private Singleton() {
        mData = new String();
    }

    public static synchronized Singleton getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }

    @MethodId(“setData”)
    public void setData(String data) {
        mData = data;
    }

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

}

如果不使用Hermes,單例是無法共享的。

假設單例在進程A中,進程B想訪問這個單例。那麼你寫如下接口:

@ClassId(“Singleton”)
public interface ISingleton {

    @MethodId(“setData”)
    void setData(String data);

    @MethodId(“getData”)
    String getData();

}

進程B使用單例的時候,代碼如下:

//obtain the instance of Singleton
ISingleton singleton = Hermes.getInstance(ISingleton.class);

//Set a data
singleton.setData(“Hello, Hermes!”);

//Get the data
Log.v(TAG, singleton.getData());

是不是很神奇?

只要給Hermes.getInstance()傳入這樣的接口,Hermes.getInstance()便會返回和進程A中實例一模一樣的實例。之後你在進程B中調用這個實例的方法時,進程A的同一個實例的方法也被調用。

但是,怎麼寫這種接口呢?很簡單。比如,進程A有一個類Foo,你想在進程B中訪問使用這個類。那麼你寫如下接口IFoo,加入同樣的方法,再在類Foo和接口IFoo上加上同樣的@ClassId注解,相同的方法上加上同樣的@MethodId注解。之後你就可以在進程B使用Hermes.getInstance(IFoo.class)獲取進程A的Foo實例。

Gradle

dependencies {
    compile 'xiaofei.library:hermes:0.2'
}

Maven


  xiaofei.library
  hermes
  0.2
  pom

使用方法

接下來的部分將告訴你如何在其他進程調用主進程的函數。Hermes支持任意進程之間的函數調用,想要知道如何調用非主進程的函數,請看這裡。

AndroidManifest.xml

在AndroidManifest.xml中加入如下聲明,你可以加上其他屬性。



初始化

經常地,一個app有一個主進程。給這個主進程命名為進程A。

假設有一個進程B,想要調用進程A的函數。那麼進程B應該初始化Hermes。

你可以在進程B的Application.OnCreate()或者Activity.OnCreate()中對Hermes初始化。相應的API是Hermes.connect(Context)。

Hermes.connect(getApplicationContext());

你可以調用Hermes.isConnected()來查看通信的進程是否還活著。

設置Context

在給其他進程提供函數的進程中,可以使用Hermes.setContext(Context)來設置context。

函數調用時,如果參數有Context,這個參數便會被轉換成之前設置的Context。具體見“注意事項”的第8點。

注冊

進程A中,被進程B調用的類需要事先注冊。有兩種注冊類的API:Hermes.register(Class)和Hermes.register(Object)。Hermes.register(object)等價於Hermes.register(object.getClass())。

但是如果類上面沒有加上注解,那麼注冊就不是必須的,Hermes會通過類名進行反射查找相應的類。詳見“注意事項”的第3點。

創建實例

進程B中,創建進程A中的實例有三種方法:Hermes.newInstance()、Hermes.getInstance()和Hermes.getUtilityClass()。

Hermes.newInstance(Class, Object...)

這個函數在進程A中創建指定類的實例,並將引用返回給進程B。函數的第二個參數將傳給指定類的對應的構造器。

@ClassId(“LoadingTask”)
public class LoadingTask {

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

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

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

在進程B中,調用Hermes.newInstance(ILoadingTask.class, “files/image.png”, true)便得到了LoadingTask的實例。

Hermes.getInstance(Class, Object...)

這個函數在進程A中通過指定類的getInstance方法創建實例,並將引用返回給進程B。第二個參數將傳給對應的getInstance方法。

這個函數特別適合獲取單例,這樣進程A和進程B就使用同一個單例。

@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 class IBitmapWrapper {

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

}

進程B中,調用Hermes.getInstance(IBitmapWrapper.class, “files/image.png”)或Hermes.getInstance(IBitmapWrapper.class, 1001)將得到BitmapWrapper的實例。

Hermes.getUtilityClass(Class)

這個函數獲取進程A的工具類。

這種做法在插件開發中很有用。插件開發的時候,通常主app和插件app存在不同的進程中。為了維護方便,應該使用統一的工具類。這時插件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 class 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);

注意事項

事實上,如果兩個進程屬於兩個不同的app(分別叫App A和App B),App A想訪問App B的一個類,並且App A的接口和App B的對應類有相同的包名和類名,那麼就沒有必要在類和接口上加@ClassId注解。但是要注意使用ProGuard後類名和包名仍要保持一致。

如果接口和類裡面對應的方法的名字相同,那麼也沒有必要在方法上加上@MethodId注解,同樣注意ProGuard的使用後接口內的方法名字必須仍然和類內的對應方法名字相同。

如果進程A的一個類上面有一個@ClassId注解,這個類在進程B中對應的接口上有一個相同的@ClassId注解,那麼進程A在進程B訪問這個類之前必須注冊這個類。否則進程B使用Hermes.newInstance()、Hermes.getInstance()或Hermes.getUtilityClass()時,Hermes在進程A中找不到匹配的類。類可以在構造器或者Application.OnCreate()中注冊。

但是,如果類和對應的接口上面沒有@ClassId注解,但有相同的包名和類名,那麼就不需要注冊類。Hermes通過包名和類名匹配類和接口。

對於接口和類裡面的函數,上面的說法仍然適用。

如果你不想讓一個類或者函數被其他進程訪問,可以在上面加上@WithinProcess注解。

使用Hermes跨進程調用函數的時候,傳入參數的類型可以是原參數類型的子類,但不可以是匿名類和局部類。但是回調函數例外,關於回調函數詳見“注意事項”的第7點。

public class A {}

public class B extends A {}

進程A中有下面這個類:

@ClassId(“Foo”)
public class Foo {

   public static A f(A a) {
   }
}

進程B的對應接口如下:

@ClassId(“Foo”)
public interface IFoo {

   A f(A a);
}

進程B中可以寫如下代碼:

IFoo foo = Hermes.getUtilityClass(IFoo.class);
B b = new B();
A a = foo.f(b);

但你不能寫如下代碼:

A a = foo.f(new A(){});

如果被調用的函數的參數類型和返回值類型是int、double等基本類型或者String這樣的Java通用類型,上面的說法可以很好地解決問題。但如果類型是自定義的類,比如“注意事項”的第5點中的例子,並且兩個進程分別屬於兩個不同app,那麼你必須在兩個app中都定義這個類,且必須保證代碼混淆後,兩個類仍然有相同的包名和類名。不過你可以適用@ClassId和@MethodId注解,這樣包名和類名在混淆後不同也不要緊了。

如果被調用的函數有回調參數,那麼函數定義中這個參數必須是一個接口,不能是抽象類。請特別注意回調函數運行的線程。

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

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

默認情況下,Hermes框架持有回調函數的強引用,這個可能會導致內存洩漏。你可以在接口聲明的對應回調參數前加上@WeakRef注解,這樣Hermes持有的就是回調函數的弱引用。如果進程的回調函數被回收了,而對方進程還在調用這個函數(對方進程並不會知道回調函數被回收),這個不會有任何影響,也不會造成崩潰。如果回調函數有返回值,那麼就返回null。

如果你使用了@Background和@WeakRef注解,你必須在接口中對應的函數參數前進行添加。如果加在其他地方,並不會有任何作用。

@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);
}

調用函數的時候,任何Context在另一個進程中都會變成對方進程的application context。

數據傳輸是基於Json的。

使用Hermes框架的時候,有任何的錯誤,都會使用android.util.Log.e()打出錯誤日志。你可以通過日志定位問題。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved