編輯:關於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
使得進程間通信像調用本地函數一樣方便簡單。
輕而易舉在本地進程創建其他進程類的對象,輕而易舉在本進程獲取其他進程的單例,輕而易舉在本進程使用其他進程的工具類。
支持進程間函數回調,調用其他進程函數的時候可以傳入回調函數,讓其他進程回調本進程的方法。
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實例。
dependencies {
compile 'xiaofei.library:hermes:0.2'
}
xiaofei.library
hermes
0.2
pom
接下來的部分將告訴你如何在其他進程調用主進程的函數。Hermes支持任意進程之間的函數調用,想要知道如何調用非主進程的函數,請看這裡。
在AndroidManifest.xml中加入如下聲明,你可以加上其他屬性。
經常地,一個app有一個主進程。給這個主進程命名為進程A。
假設有一個進程B,想要調用進程A的函數。那麼進程B應該初始化Hermes。
你可以在進程B的Application.OnCreate()或者Activity.OnCreate()中對Hermes初始化。相應的API是Hermes.connect(Context)。
Hermes.connect(getApplicationContext());
你可以調用Hermes.isConnected()來查看通信的進程是否還活著。
在給其他進程提供函數的進程中,可以使用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()打出錯誤日志。你可以通過日志定位問題。
華為榮耀8流量監控怎麼打開呢?華為在EMUI 3.0的華為手機管家新功能添加期待已久的了流量管理功能,方便大家監控自己的手機流量,避免超出。不過,
前言:在上篇中,分析了MediaPlayer的從創建到setDataSource過程,盡管看了代碼,但是沒有從MediaPlayer生態上認識各類庫之間依賴調用關系,在本
1. Java知識儲備本知識點不做重點講解:對於有基礎的同學推薦看《Java編程思想》,鞏固基礎,查漏補全,了解並熟悉更多細節知識點。對於沒有基礎的同學推薦看一本Java
這篇繼續解決上一篇遺留下來的問題:點擊條目顯示具體的知乎日報信息怎麼實現?很簡單,讓ContentActivity的recylerView響應點擊事件便可。先來看看我的代