android IPC通訊(下)-AIDL

  android IPC通訊(上)-sharedUserId&&Messenger
  android IPC通訊(中)-ContentProvider&&Socket
  這篇咱們將會着重介紹AIDL的使用方式和原理,要介紹AIDL先要簡單介紹一下Binder,並且Messenger,ContentProvider和AIDL的最底層都是使用的Binder。javascript

Binder


  直觀來講,Binder是Android中的一個類,它實現了IBinder接口。從IPC角度來講,Binder是Android中的一種跨進程通訊方式,Binder還能夠理解爲一種虛擬的物理設備,它的設備驅動是 /dev/binder,該通訊方式在Linux中沒有;從Android Framework 角度來講,Binder 是 ServiceManager 鏈接各類Manager (ActivityManager,WindowManager,等等)和相應 ManagerService 的橋樑;從Android 應用層來講,Binder 是客戶端和服務端進行通訊的媒介,當 bindService 的時候,服務端會返回一個包含了服務端業務調用的 Binder 對象,經過這個Binder對象,客戶端就能夠獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務。
  還有兩點須要提到,第一點就是當客戶端發起遠程請求時,因爲當前線程會被掛起直至服務端進程返回數據,因此若是一個遠程方法是耗時的,那麼不能在UI線程中發起此遠程請求;其次,因爲服務端的Binder方法運行在Binder的線程池中,因此Binder方法無論是否耗時都應該採用同步的方法去實現,由於他已經運行在一個線程中了。下圖爲Binder的工做機制圖:
  這裏寫圖片描述

  能夠看到Client客戶端會block直到方法返回,從圖中咱們能夠看到 Binder 的調用中有四個角色:Client, Proxy, Binder Driver 和 Server ,他們的關係以下圖所示:
  這裏寫圖片描述

這四者的關係相似於網絡訪問,html

  • Binder Client 只須要知道本身要使用的 Binder 的名字及其在 ServerManager 中的引用便可獲取該 Binder 的引用,獲得引用後就能夠像普通方法調用同樣調用 Binder 實體的方法;
  • Binder Server 在生成一個 Binder 實體時會爲其綁定一個名稱並傳遞給 Binder Driver,Binder Driver 會在內核空間中建立相應的 Binder 實體節點和節點引用,並將引用傳遞給 ServerManager。ServerManager 會將該 Binder 的名字和引用插入一張數據表中,這樣 Binder Client 就可以獲取該 Binder實體 的引用,並調用上面的方法;
  • ServerManager 至關於 DNS服務器,負責映射 Binder 名稱及其引用,其本質一樣是一個標準的 Binder Server;
  • Binder Driver 則至關於一個路由器。
其中 Binder Driver 實如今內核空間中,而其他的 3 者 Binder Client, Binder Server, Server Manager 實如今用戶空間中。Binder Driver 在內核空間中,其以字符設備中的 misc 類型註冊,用戶能夠從 /dev/binder 設備文件節點上,經過 open 和 ioctl 文件操做函數與 Binder Driver 進行通訊,其主要負責 Binder 通訊的創建,以及其在進程間的傳遞和 Binder 引用計數管理/數據包的傳輸等。而 Binder Client 與 Binder Server 之間的跨進程通訊則統一經過 Binder Driver 處理轉發,對於 Binder Client 來講,其只須要知道本身要使用的 Binder 的名字以及該 Binder 實體在 ServerManager 中的 0 號引用便可,訪問的原理也很簡單,Binder Client 先是經過 0 號引用去訪問 ServerManager 獲取該 Binder Server 的引用,獲得引用後就能夠像普通方法調用那樣調用 Binder 實體的方法。最後咱們的 ServerManager 則用來管理 Binder Server,Binder Client 能夠經過它來查詢 Binder Server 接口,剛纔咱們說到 Binder Client 能夠經過 ServerManager 來獲取 Binder Server 的引用,這個 Binder Server 的引用就是由 ServerManager 來轉換的,其實不如說映射更直接,Binder Server 在生成一個 Binder 實體的同時會爲其綁定一個名字並將這個名字封裝成一個數據包傳遞給 Binder Driver,Binder Driver 接收到這個數據包後,若是發現這個 Binder 是新傳遞來的,那麼就會爲其在內核空間中建立對應的 Binder 實體節點和一個對該實體節點的引用,這個實體節點在相應的源碼中叫作 Binder_node 而其引用則叫作 Binder_ref,建立完畢後,Binder Driver 就會將該引用傳遞給 ServerManager ,ServerManager 收到後就會從中取出該 Binder 的名字和引用插入一張數據表中,這跟 DNS 中存儲的域名到 IP 地址的映射原理相似,而對於網絡訪問來講,DNS 服務器也並不必定對每個 IP 地址都有域名映射的記錄,咱們經常也會碰到直接經過 IP 地址訪問服務器的狀況,而 Binder 也同樣並不是必定要在 ServerManager 中有記錄,不少時候 Binder Server 會將一個 Binder 實體封裝進數據包傳遞給 Binder Client,而此時 Binder Server 會在該數據包中標註 Binder 實體的位置,Binder Driver 則會爲該匿名的 Binder 生成實體節點和實體引用,並將該引用傳遞給 Binder Client。從大的角度來講, ServerManager 其實也是一個標準的 Binder Server,而且在 Android 中約定其在 Binder 通訊的過程當中惟一標識永遠是 0 ,這個標識就是進程號,只不過它在 Binder Driver 中是最早被註冊的。
  ServerManager 既然是一個標準的 Binder Server,那麼它應該對外公佈其可用的接口方法,這裏你能夠將它看做一個聯想服務器,既然能讓客戶端訪問,總得給客戶端可訪問的接口和數據吧,以博客 java/android 設計模式學習筆記(9)—代理模式 中的 ActivityManagerNative 爲例來分析,在 ActivityManagerNative 的 gDefault 對象中,有這樣一行代碼:
IBinder b = ServiceManager.getService("activity");複製代碼
經過 ServiceManager 去獲取 AMS 的 Binder 對象,ServiceManager 類的代碼很簡單:
public final class ServiceManager {
private static final String TAG = "ServiceManager";

private static IServiceManager sServiceManager;
private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}

// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}

/
Returns a reference to a service with the given name.
@param name the name of the service to get @return a reference to the service, or <code>null</code> if the service doesn't exist
*/

public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}

public static void addService(String name, IBinder service) {
...
}
....
}複製代碼
  • 每次經過 ServiceManager 的 getService() 方法取得一個 SystemService 的引用,實際上只是經過 getIServiceManager() 取回一個 Proxy 對象,而後再調用這個 Proxy對象的 getService() 方法;
  • getIServiceManager(),實際上則是經過IServiceManager接口,訪問到一個ServiceManagerNative對象。
IServiceManager 爲一個接口,它所承擔的就是 「DNS 服務器」 的角色,即 ServerManager 角色:


public interface IServiceManager extends IInterface
{

/
Retrieve an existing service called @a name from the service manager. Blocks for a few seconds waiting for it to be
published if it does not already exist. /

public IBinder getService(String name) throws RemoteException;

/
Retrieve an existing service called @a name from the service manager. Non-blocking.
*/

public IBinder checkService(String name) throws RemoteException;

/
Place a new @a service called @a name into the service manager.
/

public void addService(String name, IBinder service, boolean allowIsolated)
throws RemoteException;

/** Return a list of all currently running services.
/

public String[] listServices() throws RemoteException;

/** Assign a permission controller to the service manager. After set, this
interface is checked before any services are added. /

public void setPermissionController(IPermissionController controller)
throws RemoteException;

static final String descriptor = "android.os.IServiceManager";

int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
}複製代碼


它的具體實現類是 ServiceManagerNative 和 ServiceManagerProxy 類(和 ActivityManagerNative, ActivityManagerProxy與 IActivityManager 的關係同樣: java/android 設計模式學習筆記(9)—代理模式),ServiceManagerNative 和 ServiceManagerProxy 類都屬於 Binder Client 端, ServiceManager 和 Binder Driver 在 Android 平臺中已經實現。這裏要提到的一點是 ServiceManager 在 Java 和 Native 環境裏各有其實現,但在 Java 端實際上只有 Proxy端,而 Native 環境裏實現的 Servicemanager 才具備完整的 Proxy 與 Stub 實現,這也就是爲何 ServiceManagerNative 和 ServiceManagerProxy 相對於 ServiceManager 這個 BinderServer 角色來講都是屬於 BinderClient 了。Binder的介紹就到此爲止了,畢竟太複雜,須要詳細瞭解 Binder 的能夠看看老羅的文章: blog.csdn.net/luoshengyan…
或者這篇博客也講的很清楚: blog.csdn.net/21cnbao/art…

AIDL


 AIDL的全稱是Android Interface definition language,一看就明白,它是一種android內部進程通訊接口的描述語言,經過它咱們能夠定義進程間的通訊接口,用處固然就是用來進程間的通訊和方法調用了(我在 IPC通訊上篇中介紹過也可使用Messenger加上反射機制來進行跨應用的方法調用,可是前提是讓兩個應用在一個進程中,侷限性比AIDL大)。先介紹一下AIDL進程間通訊的流程:
  1. AIDL接口的建立
  2. AIDL文件中,並非全部的數據類型都是可使用的,它支持的數據類型有:
    • 基本數據類型(int,long,char,boolean,double等)
    • String和CharSequence
    • List:只支持ArrayList,並且list中的元素也必須是AIDL支持的類型
    • Map:只支持HashMap,裏面的key和value也必須是AIDL支持的類型
    • Parceable:全部實現了Parceable接口的對象
    • AIDL:全部的AIDL接口自己也能夠在AIDL文件中使用,因此IBinder類型也是支持的。
  3. 服務端
  4. 服務端首先要建立一個Service用來監聽客戶端的請求,而後將在對應AIDL文件中聲明的接口實現,而且經過onbind函數返回相應IBinder對象便可。
  5. 客戶端
  6. 客戶端所要作的事情就稍微簡單一些,首先須要綁定服務端的Service,綁定成功後,將服務端返回的Binder對象轉成AIDL接口所屬的類型,接着就能夠調用AIDL中的方法了。
  更多內容也能夠去看 google文檔,介紹完流程以後,緊接着來介紹一下demo中的相關代碼。

aidl文件

  第一步建立幾個相關的AIDL文件,特別須要注意的是在AS中,先要在app_name/src/main/文件夾下建立一個aidl文件夾,接下來在該文件夾下去建立相關的package用來放置這些AIDL文件,基本結構以下圖所示:
   這裏寫圖片描述
不這麼作是沒法使用的。接着咱們就來仔細分析這幾個AIDL文件:



// IWeatherManager.aidl
package com.android.aidl;
import com.android.aidl.Weather;
import com.android.aidl.listener.IWeatherChangeListener;

interface IWeatherManager {
List<Weather> getWeather();
void addWeather(in Weather weather);
void addListener(in IWeatherChangeListener listener);
void removeListener(in IWeatherChangeListener listener);
}複製代碼

  這個IWeatherManager.aidl文件是鏈接客戶端和服務端的核心文件,咱們能夠看到這個aidl文件中須要引用兩個類:Weather和IWeatherChangeListener,看看這兩個aidl文件的代碼:
//Weather.aidl
package com.android.aidl;
parcelable Weather;複製代碼

// IWeatherChangeListener.aidl
package com.android.aidl.listener;
import com.android.aidl.Weather;

interface IWeatherChangeListener {
void onWeatherChange(in Weather newWeather);
}複製代碼

  詳細介紹一下這幾個文件的要點:第一點是若是AIDL文件中用到了自定義的Parcelable對象,那麼必須新建一個和它同名的AIDL文件,並在其中聲明它爲Parcelable類型。在IWeatherManager.aidl文件中用到了Weather這個Parcelable類,因此咱們必需要建立Weather.aidl文件,要否則只有一個Weather.java文件是沒法識別的,而且很是重要的是Weather.aidl和Weather.java兩個文件的包名必需要一致,好比demo中的都爲com.android.aidl,不一致也會致使Weather類沒法識別;第二點是AIDL中除了基本數據類型,其餘類型的參數必須標上方向:in,out或者inout, in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出型參數。咱們要根據實際須要去指定參數類型,不能一律使用out或者inout,由於這在底層實現是有開銷的;第三點是AIDL接口中只支持方法,不支持聲明靜態常量,這一點區別於傳統的接口。
  在這個demo中,咱們仍然是在一個應用中建立兩個進程進行通訊,和在兩個應用中的兩個進程之間進行通訊是很相似的,差別方面就以這個demo來講第一個須要在兩個應用中的app_name/src/main/文件夾下都建立一個aidl文件夾,而後將三個aidl文件總體拷貝進來,固然要保證兩個應用的package名字com.android.aidl同樣;第二個還有Weather.java文件也必須在兩個應用中的com.android.aidl(就是要和Weather.aidl的package名字一致)包下面,作到這兩點就能夠了。一個工程和兩個工程的多進程本質是同樣的,有興趣的能夠本身試試。

java文件


Parcelable實體類


  咱們來看看demo中Weather.java類的代碼:
public class Weather implements Parcelable{
public String cityName;
public double temperature;
public double humidity;
public AllWeather weather;

protected Weather(Parcel in) {
temperature = in.readDouble();
humidity = in.readDouble();
//使用該方式來寫入枚舉
weather = AllWeather.values()[in.readInt()];
cityName = in.readString();
}

public Weather() {

}

public static final Creator<Weather> CREATOR = new Creator<Weather>() {
@Override
public Weather createFromParcel(Parcel in) {
return new Weather(in);
}

@Override
public Weather[] newArray(int size) {
return new Weather[size];
}
};

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeDouble(temperature);
dest.writeDouble(humidity);
dest.writeInt(weather.ordinal());
dest.writeString(cityName);
}

public enum AllWeather{
sunny,cloudy,rain,snowy
}
}複製代碼

  代碼很簡單,就是實現Parcelable接口便可,惟一的難點就是enum枚舉在多客戶端之間的處理了,處理方法就是使用ordinal()函數將枚舉轉換成int類型,通着這個int值,就能夠找到枚舉變量在枚舉類中的位置,也就能夠知道原始值了。

服務端


  接着就是服務端的代碼實現了:
public class WeatherManagerService extends Service{

//支持併發讀寫的list
public CopyOnWriteArrayList<Weather> weathers = new CopyOnWriteArrayList<>();
public RemoteCallbackList<IWeatherChangeListener> listeners = new RemoteCallbackList<>();

@Override
public void onCreate() {
super.onCreate();
Weather nanshan = new Weather();
nanshan.cityName = "南山";
nanshan.temperature = 20.5;
nanshan.humidity = 45;
nanshan.weather = Weather.AllWeather.cloudy;

Weather futian = new Weather();
futian.cityName = "福田";
futian.temperature = 21.5;
futian.humidity = 48;
futian.weather = Weather.AllWeather.rain;

weathers.add(nanshan);
weathers.add(futian);
}

private Binder mBinder = new IWeatherManager.Stub() {
@Override
public List<Weather> getWeather() throws RemoteException {
L.i("server returns all of the weathers");
return weathers;
}

@Override
public void addWeather(Weather weather) throws RemoteException {
weathers.add(weather);
L.i("server add new Weather:" + weather.cityName);

int N = listeners.beginBroadcast();
for (int i=0; i<N; i++){
IWeatherChangeListener listener = listeners.getBroadcastItem(i);
listener.onWeatherChange(weather);
}
L.i("server notify the listener that weathers have been changed");
listeners.finishBroadcast();
}

@Override
public void addListener(IWeatherChangeListener listener) throws RemoteException {
L.i("server adding listener");
listeners.register(listener);
}

@Override
public void removeListener(IWeatherChangeListener listener) throws RemoteException {
L.i("server removing listener");
listeners.unregister(listener);
}

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
//檢測客戶端是否聲明權限
if (permission == PackageManager.PERMISSION_DENIED){
L.e("permission denied");
return false;
}
L.i("permission granted");

String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0){
String packageName = packages[0];
if (!packageName.startsWith("com.android")){
L.e("package name not accept");
return false;
}
L.i("package name accept");
}
return super.onTransact(code, data, reply, flags);
}
};

@Override
public IBinder onBind(Intent intent) {
// int permission = checkCallingPermission("com.android.permission.WRITEWEATHERPERMISSION");
// //檢測客戶端是否聲明權限
// if (permission == PackageManager.PERMISSION_DENIED){
// L.e("permission denied");
// return null;
// }
return mBinder;
}
}複製代碼

  服務端的實現比較複雜,咱們一步步來分析:
  1. 服務端固然是個Service,在該Service中咱們須要新建一個binder對象,這個binder對象是一個由IWeatherManager.aidl生成的IWeatherManager接口中的內部Stub類的對象,該對象須要實現4個接口中的方法。而後在onBind函數返回這個binder對象便可。
  2. 爲了支持多進程的併發讀寫,咱們須要使用CopyOnWriteArrayList而不是普通list,相似的還有ConcurrentHashMap。
  3. 若是須要爲這個Service增長訪問的權限,有三個方法來實現:
    • 先使用permission標籤訂義一個permission(詳情看博客 android permission權限與安全機制解析(上)),而後在manifest文件中的服務端service標籤中添加android:permission=」yourPermissionName」便可。
    • 一樣的先聲明一個permission,接着在Service的onBind函數中,經過checkCallingPermission函數檢測調用者是否使用了該聲明的權限,若是沒有就直接返回null。
    • 在onTransact函數中進行檢測,和onBind中的檢測同樣,不經過返回false,並且在該函數中還能夠檢測調用者的package name,在demo中若是調用者應用的包名不是以com.android開頭,就會拒絕訪問。簡單介紹一下onTransact函數,該函數運行在服務端中的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會經過系統底層封裝後交由此方法來處理,若是此方法返回false,那麼客戶端的請求會失敗,因此在這個方法中檢測權限也是能夠的。
  4. 如何爲服務端添加監聽器?咱們知道客戶端的listener對象通過parcelable以後到服務端的對象並非同一個對象,因此若是客戶端想要解註冊一個listener,調用服務端removeListener函數並傳入一個listener參數,可是這個listener對象通過parcelable以後並非原來的那個對象,服務端沒法處理,因此爲了應對這種狀況,系統專門提供了用於跨進程刪除listener的接口RemoteCallbackList。RemoteCallbackList是一個泛型,由於繼承自IInterface接口,因此支持管理任意的AIDL接口。爲何RemoteCallbackList類就能夠識別parcelable以後的對象呢?先來看看RemoteCallbackList的實現,在它的內部有一個Map結構專門用來保存全部的AIDL回調,這個Map的key是IBinder類型,value是Callback類型:
    ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>()
    其中Callback中封裝了真正的遠程listener。當客戶端註冊listener的時候,他會把這個listener的信息存入mCallbacks中,其中的key和value分別經過下面的方式得到:
    IBinder binder = callback.asBinder();
    Callback cb = new Callback(callback, cookie);
    雖說屢次跨進程傳輸客戶端的同一個對象在服務端生成不一樣的對象,可是這些新生成的對象有一個共同點,那就是它們底層的Binder對象是同一個,因此使用RemoteCallbackList就可以成功的刪除指定listener。

客戶端


  看看客戶端的代碼:
public class ClientActivity extends BaseActivity implements View.OnClickListener{

private ServiceConnection serviceConnection = null;
private IBinder.DeathRecipient deathRecipient = null;
private IWeatherChangeListener listener = null;
private IWeatherManager weatherManager;
private TextView tv_content;
private TextView tv_add;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
findViewById(R.id.btn_add).setOnClickListener(this);
findViewById(R.id.btn_query).setOnClickListener(this);
findViewById(R.id.btn_remove_listener).setOnClickListener(this);
tv_content = (TextView) findViewById(R.id.tv_content);
tv_add = (TextView) findViewById(R.id.tv_add);
listener = new IWeatherChangeListener.Stub(){

@Override
public void onWeatherChange(Weather newWeather) throws RemoteException {
L.i("client has been notified that "+newWeather.cityName+" has been added");
tv_add.setText(newWeather.cityName + "has been added");
}
};
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
weatherManager = IWeatherManager.Stub.asInterface(service);
try {
weatherManager.asBinder().linkToDeath(deathRecipient, 0);
weatherManager.addListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
weatherManager = null;
}
};

deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
//移出以前的死亡容器
weatherManager.asBinder().unlinkToDeath(deathRecipient, 0);
weatherManager = null;

//從新鏈接
bindServer();
}
};
bindServer();
}

private void bindServer(){
Intent intent = new Intent(this, WeatherManagerService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}

@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_query){
try {
//調用遠程服務端接口時,客戶端進程會掛起,勿在主線程中調用耗時遠程操做
L.i("client is getting weather");
List<Weather> weathers = weatherManager.getWeather();
L.i("client has gotten weather");
StringBuilder sb = new StringBuilder();
for (Weather weather : weathers){
sb.append(weather.cityName).append("\n");
sb.append("humidity:").append(weather.humidity)
.append("temperature").append(weather.temperature)
.append("weather").append(weather.weather).append("\n");
}
tv_content.setText(sb);
} catch (RemoteException e) {
e.printStackTrace();
}
}else if (v.getId() == R.id.btn_add){
Weather weather = new Weather();
weather.weather = Weather.AllWeather.cloudy;
weather.humidity = 25.5;
weather.temperature = 19.5;
weather.cityName = "羅湖";
try {
//調用遠程服務端接口時,客戶端進程會掛起,勿在主線程中調用耗時遠程操做
L.i("client is adding weather " + weather.cityName);
weatherManager.addWeather(weather);
L.i("client has added weather " + weather.cityName);
} catch (RemoteException e) {
e.printStackTrace();
}
}else if (v.getId() == R.id.btn_remove_listener){
try {
weatherManager.removeListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
try {
weatherManager.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}複製代碼


  客戶端邏輯很簡單,bindService綁定服務端以後,將服務端傳過來的IBinder對象經過asInterface方法轉換成AIDL接口,而後就能經過這個接口去調用服務端的遠程方法了。並且在客戶端還可以註冊死亡代理,新建一個DeathRecipient對象,而且使用Binder的linkToDeath註冊該對象,當Binder死亡時,咱們就會收到通知,unlinkToDeath函數能夠解註冊該死亡代理。
  還有很是重要的幾點須要說明:客戶端調用服務端方法,被調用的方法運行在服務端的Binder線程池中,同時客戶端線程會被掛起,這個時候若是服務端方法執行比較耗時,就會致使客戶端線程長時間阻塞,而若是這個客戶端線程是UI線程的話,就會致使客戶端ANR,因此若是知道服務端的一個方法是耗時的,就要避免在客戶端的UI線程中去調用該遠程方法。因爲onServiceConnected和onServiceDisconnected方法都運行在UI線程中,因此也不能夠在這兩個函數中調用耗時方法。另外,因爲服務端的方法自己就運行在服務端的Binder線程池中,因此服務端方法能夠執行大量的耗時操做,這個時候切記不要在服務端方法中開線程去進行異步任務,除非你明確知道本身在幹什麼,不然不建議這麼作。 可是有一種方法能夠發起非阻塞式的遠程調用,Messager 就是採用的非阻塞式的方式通信,其關鍵就在於 IMessager.aidl 的實現:
這裏寫圖片描述
相比日常自定義的 aidl,多了 oneway 的關鍵字,聲明和不聲明 oneway 關鍵字的在於生成 Java 類中一個參數:
這裏寫圖片描述
這裏寫圖片描述
不聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 0;聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 android.os.IBinder.FLAG_ONEWAY 。
這裏寫圖片描述
這裏寫圖片描述
查看 API 文檔便可以看到 FLAG_ONEWAY 的做用就是讓客戶端可以非阻塞的調用遠程方法,至此真相大白,若是咱們自定義的 aidl 也想實現非阻塞的調用,只需聲明 oneway 關鍵字便可。
  關於服務端和客戶端的方法分別執行在那個進程和線程中以及它們執行的前後順序,咱們先來看看log日誌的輸出:
I/PID:28533: [TID:7035] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7035] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7035] 1.addListener(line:72): server adding listener

//client add click
I/PID:28502: [TID:1] ClientActivity.onClick(line:115): client is adding weather 羅湖
I/PID:28533: [TID:7036] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7036] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7036] 1.addWeather(line:59): server add new Weather:羅湖
I/PID:28502: [TID:1] 1.onWeatherChange(line:47): client has been notified that 羅湖 has been added
I/PID:28533: [TID:7036] 1.addWeather(line:66): server has notified the listener that weathers have been changed
I/PID:28502: [TID:1] ClientActivity.onClick(line:117): client has added weather 羅湖

//client remove listener click
I/PID:28533: [TID:7035] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7035] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7035] 1.removeListener(line:78): server removing listener

//*client get click
I/PID:28502: [TID:1] ClientActivity.onClick(line:93): client is getting weather
I/PID:28533: [TID:7036] 1.onTransact(line:90): permission granted
I/PID:28533: [TID:7036] 1.onTransact(line:99): package name accept
I/PID:28533: [TID:7036] 1.getWeather(line:52): server returns all of the weathers
I/PID:28502: [TID:1] ClientActivity.onClick(line:95): client has gotten weather複製代碼

  PID:28502爲客戶端進程,PID:28533爲服務端進程,TID:1爲UI主線程,TID:7036爲Binder線程。看看log打印的順序基本就可以明白方法的執行進程,線程和客戶端的阻塞狀況了。
  源碼下載: github.com/zhaozepeng/…

BinderPool

  上面差很少就把AIDL的用法詳細介紹完了,可是有的時候咱們可能須要不止一個業務模塊,也就是不僅僅須要一個天氣模塊,咱們還須要一個計算溫度平均值的模塊(雖然能夠寫在一個模塊中,可是咱們仍是假設要用兩個模塊吧~),是否是須要爲每一個模塊都單獨創建一個Service呢?固然不是,會很耗資源的好嗎,解決方法就是先爲每個模塊創建一個單獨的aidl文件,最後再創建一個總體的aidl文件用來管理這些單獨的aidl。
  看看這三個文件,IWeatherManager.aidl,IComputerManager.aidl和IBinderPoolManager.aidl:java

// IWeatherManager.aidl
package com.android.binderpool;
import com.android.binderpool.Weather;

interface IWeatherManager {
    List<Weather> getWeather();
    void addWeather(in Weather weather);
}複製代碼
// IComputerManager.aidl
package com.android.binderpool;
import com.android.binderpool.Weather;

interface IComputerManager {
    double computeAverageTemperature(in List<Weather> weathers);
}複製代碼

// IBinderPoolManager.aidl
package com.android.binderpool;複製代碼

interface IBinderPoolManager {
IBinder queryCode(int code);
}IBinderPoolManager.aidl文件用來統一管理全部的AIDL接口,queryCode函數經過code值來肯定須要返回給客戶端的IBinder對象。
  來看看服務端的代碼的變更:node

public class BinderPoolService extends Service{
public static final int CODE_WEATHER = 1;
public static final int CODE_COMPUTER = 2;複製代碼

<span class="hljs-keyword">private</span> IBinderPoolManager iBinderPoolManager;

<span class="hljs-comment">//支持併發讀寫的list</span>
<span class="hljs-keyword">public</span> CopyOnWriteArrayList&lt;Weather&gt; weathers = <span class="hljs-keyword">new</span> CopyOnWriteArrayList&lt;&gt;();

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>() {
    <span class="hljs-keyword">super</span>.onCreate();
    Weather nanshan = <span class="hljs-keyword">new</span> Weather();
    nanshan.cityName = <span class="hljs-string">"南山"</span>;
    nanshan.temperature = <span class="hljs-number">20.5</span>;
    nanshan.humidity = <span class="hljs-number">45</span>;
    nanshan.weather = Weather.AllWeather.cloudy;

    Weather futian = <span class="hljs-keyword">new</span> Weather();
    futian.cityName = <span class="hljs-string">"福田"</span>;
    futian.temperature = <span class="hljs-number">21.5</span>;
    futian.humidity = <span class="hljs-number">48</span>;
    futian.weather = Weather.AllWeather.rain;

    weathers.add(nanshan);
    weathers.add(futian);
    iBinderPoolManager = <span class="hljs-keyword">new</span> IBinderPoolManager.Stub(){
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">public</span> IBinder <span class="hljs-title">queryCode</span>(<span class="hljs-keyword">int</span> code) <span class="hljs-keyword">throws</span> RemoteException {
            <span class="hljs-keyword">switch</span> (code){
                <span class="hljs-keyword">case</span> CODE_WEATHER:
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IWeatherManager.Stub(){

                        <span class="hljs-annotation">@Override</span>
                        <span class="hljs-keyword">public</span> List&lt;Weather&gt; <span class="hljs-title">getWeather</span>() <span class="hljs-keyword">throws</span> RemoteException {
                            <span class="hljs-keyword">return</span> weathers;
                        }

                        <span class="hljs-annotation">@Override</span>
                        <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addWeather</span>(Weather weather) <span class="hljs-keyword">throws</span> RemoteException {
                            weathers.add(weather);
                        }
                    };
                <span class="hljs-keyword">case</span> CODE_COMPUTER:
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> IComputerManager.Stub() {
                        <span class="hljs-annotation">@Override</span>
                        <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">computeAverageTemperature</span>(List&lt;Weather&gt; weathers) <span class="hljs-keyword">throws</span> RemoteException {
                            <span class="hljs-keyword">double</span> sum = <span class="hljs-number">0</span>;
                            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>; i&lt;weathers.size(); i++){
                                sum += weathers.get(i).temperature;
                            }
                            <span class="hljs-keyword">return</span> sum/weathers.size();
                        }
                    };
                <span class="hljs-keyword">default</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
            }
        }
    };
}

<span class="hljs-annotation">@Override</span>
<span class="hljs-keyword">public</span> IBinder <span class="hljs-title">onBind</span>(Intent intent) {
    <span class="hljs-keyword">return</span> iBinderPoolManager.asBinder();
}複製代碼

}  根據code的不一樣返回不一樣的IBinder對象,這樣在客戶端中就可以獲取對應AIDL接口的IBinder對象,最終就能在客戶端調用不一樣AIDL模塊中的方法。客戶端代碼很簡單,在這裏就不介紹了,感興趣的能夠去看看源碼:
  github.com/zhaozepeng/…
  關於IPC相關知識的介紹就到這了,若是有什麼疑問,你們能夠多多交流啊,謝謝~android

相關文章
相關標籤/搜索