餓了么開源項(xiàng)目Hermes:新穎巧妙易用的Android進(jìn)程間通信IPC框架

版權(quán)所有。所有權(quán)利保留。

歡迎轉(zhuǎn)載,轉(zhuǎn)載時請注明出處:

http://blog.csdn.net/xiaofei_it/article/details/51464518

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

公司最近在研究DroidPlugin插件開發(fā)绝淡,DroidPlugin把每個子app都變成一個進(jìn)程宙刘。這樣的話子app和主app如果需要共享數(shù)據(jù),就需要IPC牢酵。所以我開發(fā)了Hermes框架悬包,讓IPC變得非常簡單優(yōu)雅。

項(xiàng)目地址:

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

這個框架開發(fā)難度很大馍乙,涉及到AIDL布近、binder、反射丝格、注解撑瞧、進(jìn)程間垃圾回收、動態(tài)代理等很多技術(shù)显蝌。我以后會對源碼進(jìn)行解析预伺。

本來我寫的文檔是英文的,后來為了便于讀者查閱,特意翻譯成了中文文檔酬诀。希望大家持續(xù)關(guān)注脏嚷,可以給個star。

中文文檔鏈接:

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

Hermes是一套新穎巧妙易用的Android進(jìn)程間通信IPC框架瞒御。這個框架使得你不用了解IPC機(jī)制就可以進(jìn)行進(jìn)程間通信父叙,像調(diào)用本地函數(shù)一樣調(diào)用其他進(jìn)程的函數(shù)。

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

特色

使得進(jìn)程間通信像調(diào)用本地函數(shù)一樣方便簡單践宴。

輕而易舉在本地進(jìn)程創(chuàng)建其他進(jìn)程類的對象,輕而易舉在本進(jìn)程獲取其他進(jìn)程的單例爷怀,輕而易舉在本進(jìn)程使用其他進(jìn)程的工具類阻肩。

支持進(jìn)程間函數(shù)回調(diào),調(diào)用其他進(jìn)程函數(shù)的時候可以傳入回調(diào)函數(shù)运授,讓其他進(jìn)程回調(diào)本進(jìn)程的方法烤惊。

自帶內(nèi)存優(yōu)化,并且支持跨進(jìn)程垃圾回收吁朦。

基本原理

IPC的主要目的是調(diào)用其他進(jìn)程的函數(shù)柒室,Hermes讓你方便地調(diào)用其他進(jìn)程函數(shù),調(diào)用語句和本地進(jìn)程函數(shù)調(diào)用一模一樣逗宜。

比如雄右,單例模式經(jīng)常在Android App中使用。假設(shè)有一個app有兩個進(jìn)程纺讲,它們共享如下單例:

@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擂仍,單例是無法共享的。

假設(shè)單例在進(jìn)程A中熬甚,進(jìn)程B想訪問這個單例逢渔。那么你寫如下接口:

@ClassId(“Singleton”)

public interface ISingleton {

? ? @MethodId(“setData”)

? ? void setData(String data);

? ? @MethodId(“getData”)

? ? String getData();

}

進(jìn)程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()便會返回和進(jìn)程A中實(shí)例一模一樣的實(shí)例。之后你在進(jìn)程B中調(diào)用這個實(shí)例的方法時诲泌,進(jìn)程A的同一個實(shí)例的方法也被調(diào)用盲赊。

但是,怎么寫這種接口呢档礁?很簡單秽澳。比如,進(jìn)程A有一個類Foo僵刮,你想在進(jìn)程B中訪問使用這個類。那么你寫如下接口IFoo惨险,加入同樣的方法,再在類Foo和接口IFoo上加上同樣的@ClassId注解脊髓,相同的方法上加上同樣的@MethodId注解辫愉。之后你就可以在進(jìn)程B使用Hermes.getInstance(IFoo.class)獲取進(jìn)程A的Foo實(shí)例。

Gradle

dependencies {

? ? compile 'xiaofei.library:hermes:0.2'

}

Maven

? xiaofei.library

? hermes

? 0.2

? pom

使用方法

接下來的部分將告訴你如何在其他進(jìn)程調(diào)用主進(jìn)程的函數(shù)将硝。Hermes支持任意進(jìn)程之間的函數(shù)調(diào)用恭朗,想要知道如何調(diào)用非主進(jìn)程的函數(shù),請看這里依疼。

AndroidManifest.xml

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

初始化

經(jīng)常地律罢,一個app有一個主進(jìn)程膀值。給這個主進(jìn)程命名為進(jìn)程A。

假設(shè)有一個進(jìn)程B误辑,想要調(diào)用進(jìn)程A的函數(shù)沧踏。那么進(jìn)程B應(yīng)該初始化Hermes。

你可以在進(jìn)程B的Application.OnCreate()或者Activity.OnCreate()中對Hermes初始化巾钉。相應(yīng)的API是Hermes.connect(Context)翘狱。

Hermes.connect(getApplicationContext());

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

設(shè)置Context

在給其他進(jìn)程提供函數(shù)的進(jìn)程中砰苍,可以使用Hermes.setContext(Context)來設(shè)置context潦匈。

函數(shù)調(diào)用時,如果參數(shù)有Context师骗,這個參數(shù)便會被轉(zhuǎn)換成之前設(shè)置的Context历等。具體見“注意事項(xiàng)”的第8點(diǎn)。

注冊

進(jìn)程A中辟癌,被進(jìn)程B調(diào)用的類需要事先注冊寒屯。有兩種注冊類的API:Hermes.register(Class)和Hermes.register(Object)。Hermes.register(object)等價于Hermes.register(object.getClass())黍少。

但是如果類上面沒有加上注解寡夹,那么注冊就不是必須的,Hermes會通過類名進(jìn)行反射查找相應(yīng)的類厂置。詳見“注意事項(xiàng)”的第3點(diǎn)菩掏。

創(chuàng)建實(shí)例

進(jìn)程B中,創(chuàng)建進(jìn)程A中的實(shí)例有三種方法:Hermes.newInstance()昵济、Hermes.getInstance()和Hermes.getUtilityClass()智绸。

Hermes.newInstance(Class, Object...)

這個函數(shù)在進(jìn)程A中創(chuàng)建指定類的實(shí)例野揪,并將引用返回給進(jìn)程B。函數(shù)的第二個參數(shù)將傳給指定類的對應(yīng)的構(gòu)造器瞧栗。

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

}

在進(jìn)程B中斯稳,調(diào)用Hermes.newInstance(ILoadingTask.class, “files/image.png”, true)便得到了LoadingTask的實(shí)例。

Hermes.getInstance(Class, Object...)

這個函數(shù)在進(jìn)程A中通過指定類的getInstance方法創(chuàng)建實(shí)例迹恐,并將引用返回給進(jìn)程B挣惰。第二個參數(shù)將傳給對應(yīng)的getInstance方法。

這個函數(shù)特別適合獲取單例殴边,這樣進(jìn)程A和進(jìn)程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();

}

進(jìn)程B中,調(diào)用Hermes.getInstance(IBitmapWrapper.class,

“files/image.png”)或Hermes.getInstance(IBitmapWrapper.class,

1001)將得到BitmapWrapper的實(shí)例锤岸。

Hermes.getUtilityClass(Class)

這個函數(shù)獲取進(jìn)程A的工具類竖幔。

這種做法在插件開發(fā)中很有用。插件開發(fā)的時候是偷,通常主app和插件app存在不同的進(jìn)程中赏枚。為了維護(hù)方便,應(yīng)該使用統(tǒng)一的工具類晓猛。這時插件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);

}

進(jìn)程B中凡辱,使用下面代碼使用進(jìn)程A的工具類戒职。

IMaths maths = Hermes.getUtilityClass(IMaths.class);

int sum = maths.plus(3, 5);

int diff = maths.minus(3, 5);

注意事項(xiàng)

事實(shí)上,如果兩個進(jìn)程屬于兩個不同的app(分別叫App

A和App B)透乾,App A想訪問App B的一個類洪燥,并且App A的接口和App

B的對應(yīng)類有相同的包名和類名,那么就沒有必要在類和接口上加@ClassId注解乳乌。但是要注意使用ProGuard后類名和包名仍要保持一致捧韵。

如果接口和類里面對應(yīng)的方法的名字相同,那么也沒有必要在方法上加上@MethodId注解汉操,同樣注意ProGuard的使用后接口內(nèi)的方法名字必須仍然和類內(nèi)的對應(yīng)方法名字相同再来。

如果進(jìn)程A的一個類上面有一個@ClassId注解,這個類在進(jìn)程B中對應(yīng)的接口上有一個相同的@ClassId注解磷瘤,那么進(jìn)程A在進(jìn)程B訪問這個類之前必須注冊這個類芒篷。否則進(jìn)程B使用Hermes.newInstance()、Hermes.getInstance()或Hermes.getUtilityClass()時采缚,Hermes在進(jìn)程A中找不到匹配的類针炉。類可以在構(gòu)造器或者Application.OnCreate()中注冊。

但是扳抽,如果類和對應(yīng)的接口上面沒有@ClassId注解篡帕,但有相同的包名和類名殖侵,那么就不需要注冊類。Hermes通過包名和類名匹配類和接口镰烧。

對于接口和類里面的函數(shù)拢军,上面的說法仍然適用。

如果你不想讓一個類或者函數(shù)被其他進(jìn)程訪問拌滋,可以在上面加上@WithinProcess注解朴沿。

使用Hermes跨進(jìn)程調(diào)用函數(shù)的時候,傳入?yún)?shù)的類型可以是原參數(shù)類型的子類败砂,但不可以是匿名類和局部類赌渣。但是回調(diào)函數(shù)例外,關(guān)于回調(diào)函數(shù)詳見“注意事項(xiàng)”的第7點(diǎn)昌犹。

public class A {}

public class B extends A {}

進(jìn)程A中有下面這個類:

@ClassId(“Foo”)

public class Foo {

? public static A f(A a) {

? }

}

進(jìn)程B的對應(yīng)接口如下:

@ClassId(“Foo”)

public interface IFoo {

? A f(A a);

}

進(jìn)程B中可以寫如下代碼:

IFoo foo = Hermes.getUtilityClass(IFoo.class);

B b = new B();

A a = foo.f(b);

但你不能寫如下代碼:

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

如果被調(diào)用的函數(shù)的參數(shù)類型和返回值類型是int坚芜、double等基本類型或者String這樣的Java通用類型,上面的說法可以很好地解決問題斜姥。但如果類型是自定義的類鸿竖,比如“注意事項(xiàng)”的第5點(diǎn)中的例子,并且兩個進(jìn)程分別屬于兩個不同app铸敏,那么你必須在兩個app中都定義這個類缚忧,且必須保證代碼混淆后,兩個類仍然有相同的包名和類名杈笔。不過你可以適用@ClassId和@MethodId注解闪水,這樣包名和類名在混淆后不同也不要緊了。

如果被調(diào)用的函數(shù)有回調(diào)參數(shù)蒙具,那么函數(shù)定義中這個參數(shù)必須是一個接口球榆,不能是抽象類。請?zhí)貏e注意回調(diào)函數(shù)運(yùn)行的線程禁筏。

如果進(jìn)程A調(diào)用進(jìn)程B的函數(shù)持钉,并且傳入一個回調(diào)函數(shù)供進(jìn)程B在進(jìn)程A進(jìn)行回調(diào)操作,那么默認(rèn)這個回調(diào)函數(shù)將運(yùn)行在進(jìn)程A的主線程(UI線程)篱昔。如果你不想讓回調(diào)函數(shù)運(yùn)行在主線程每强,那么在接口聲明的函數(shù)的對應(yīng)的回調(diào)參數(shù)之前加上@Background注解。

如果回調(diào)函數(shù)有返回值州刽,那么你應(yīng)該讓它運(yùn)行在后臺線程舀射。如果運(yùn)行在主線程,那么返回值始終為null怀伦。

默認(rèn)情況下脆烟,Hermes框架持有回調(diào)函數(shù)的強(qiáng)引用,這個可能會導(dǎo)致內(nèi)存泄漏房待。你可以在接口聲明的對應(yīng)回調(diào)參數(shù)前加上@WeakRef注解邢羔,這樣Hermes持有的就是回調(diào)函數(shù)的弱引用驼抹。如果進(jìn)程的回調(diào)函數(shù)被回收了,而對方進(jìn)程還在調(diào)用這個函數(shù)(對方進(jìn)程并不會知道回調(diào)函數(shù)被回收)拜鹤,這個不會有任何影響框冀,也不會造成崩潰。如果回調(diào)函數(shù)有返回值敏簿,那么就返回null明也。

如果你使用了@Background和@WeakRef注解,你必須在接口中對應(yīng)的函數(shù)參數(shù)前進(jìn)行添加惯裕。如果加在其他地方温数,并不會有任何作用。

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

}

調(diào)用函數(shù)的時候蜻势,任何Context在另一個進(jìn)程中都會變成對方進(jìn)程的application context撑刺。

數(shù)據(jù)傳輸是基于Json的。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冕屯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拂苹,更是在濱河造成了極大的恐慌愕撰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件醋寝,死亡現(xiàn)場離奇詭異,居然都是意外死亡带迟,警方通過查閱死者的電腦和手機(jī)音羞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仓犬,“玉大人嗅绰,你說我怎么就攤上這事〔蠹蹋” “怎么了窘面?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叽躯。 經(jīng)常有香客問我财边,道長,這世上最難降的妖魔是什么点骑? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任酣难,我火速辦了婚禮谍夭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘憨募。我一直安慰自己紧索,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布菜谣。 她就那樣靜靜地躺著珠漂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尾膊。 梳的紋絲不亂的頭發(fā)上媳危,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音眯停,去河邊找鬼济舆。 笑死,一個胖子當(dāng)著我的面吹牛莺债,可吹牛的內(nèi)容都是我干的滋觉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼齐邦,長吁一口氣:“原來是場噩夢啊……” “哼椎侠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起措拇,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤我纪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后丐吓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浅悉,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年券犁,在試婚紗的時候發(fā)現(xiàn)自己被綠了术健。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡粘衬,死狀恐怖荞估,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情稚新,我是刑警寧澤勘伺,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站褂删,受9級特大地震影響飞醉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屯阀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一冒掌、第九天 我趴在偏房一處隱蔽的房頂上張望噪裕。 院中可真熱鬧,春花似錦股毫、人聲如沸膳音。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祭陷。三九已至,卻和暖如春趣席,著一層夾襖步出監(jiān)牢的瞬間兵志,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工宣肚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留想罕,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓霉涨,卻偏偏與公主長得像按价,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子笙瑟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理楼镐,服務(wù)發(fā)現(xiàn),斷路器往枷,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • 深夜框产,寺里一人一佛,佛坐人跪错洁。 女人:圣明的佛秉宿,我今年30多歲了,依然單身屯碴,感覺好孤單描睦。 佛曰:每一顆心生來都是孤...
    婉風(fēng)清逸閱讀 932評論 4 20
  • 我相信命中注定這件事 比如我會遇見你喜歡你 而你卻永遠(yuǎn)不會喜歡上我 只能說 我們都不是將就的人 我把和你的聊天記錄...
    背背背對閱讀 252評論 0 0
  • 最近兒子迷戀上了微信的一種小游戲×保“跳一跳”。中午吃完飯膝舅,兒子寫了十道計算嗡载,念了一會英語。在征求過我的同...
    逆風(fēng)飛的會更高閱讀 223評論 0 0