版權(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í)例。
dependencies {
? ? compile 'xiaofei.library:hermes:0.2'
}
? 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中加入如下聲明痰腮,你可以加上其他屬性。
經(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)程是否還活著。
在給其他進(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)菩掏。
進(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);
事實(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()打出錯誤日志。你可以通過日志定位問題挠铲。