Android IPC機制(三):淺談Binder的使用
一、前言
在上一篇部落格Android IPC機制(二):AIDL的基本使用方法中,筆者講述了安卓程式間通訊的一個主要方式,利用AIDL進行通訊,並介紹了AIDL的基本使用方法。其實AIDL方式利用了Binder來進行跨程式通訊,Binder是Android中的一種跨程式通訊方式,其底層實現原理比較複雜,限於筆者水平,不能展開詳談,所以這篇文章主要談談以AIDL為例,談談Binder的使用方法。
二、原理
上一篇文章中建立了一個IMyAidl.aidl檔案,即介面檔案,隨即編譯了該檔案,生成了一個.java檔案,該檔案在gen目錄下:
開啟該檔案,得到如下程式碼:
- <span style="font-size:18px;">/*
- * This file is auto-generated. DO NOT MODIFY.
- * Original file: G:\\Android\\Project\\MyAidl\\app\\src\\main\\aidl\\com\\chenyu\\service\\IMyAidl.aidl
- */
- package com.chenyu.service;
- public interface IMyAidl extends android.os.IInterface {
- /**
- * Local-side IPC implementation stub class.
- */
- public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {
- ......
- public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException;
- public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException;</span>
- }
(1)從大體上看,該java檔案是一個介面,繼承了IInterface介面,接著,宣告瞭一個靜態內部抽象類:Stub,然後是兩個方法,可以看到,這兩個方法分別是原IMyAidl.aidl檔案內宣告的兩個方法。
(2)我們看回Stub類,它繼承了Binder,同時實現了IMyAidl。這個類實現了自己的介面!那麼可想而知,該介面所宣告的addPerson,getPersonList方法,將會在Stub類得到實現,具體如何實現,我們展開Stub類:
- public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {
- private static final java.lang.String DESCRIPTOR = "com.chenyu.service.IMyAidl";
- /**
- * Construct the stub at attach it to the interface.
- */
- public Stub() {<span style="white-space:pre"> </span>//①
- this.attachInterface(this, DESCRIPTOR);
- }
- /**
- * Cast an IBinder object into an com.chenyu.service.IMyAidl interface,
- * generating a proxy if needed.
- */
- public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) { //②
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {
- return ((com.chenyu.service.IMyAidl) iin);
- }
- return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);
- }
- public android.os.IBinder asBinder() {<span style="white-space:pre"> </span>//③
- return this;
- }
- public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {<span style="white-space:pre"> </span>//④
- switch (code) {
- case INTERFACE_TRANSACTION: {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_addPerson: {
- data.enforceInterface(DESCRIPTOR);
- com.chenyu.service.Person _arg0;
- if ((0 != data.readInt())) {
- _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);
- } else {
- _arg0 = null;
- }
- this.addPerson(_arg0);
- reply.writeNoException();
- return true;
- }
- case TRANSACTION_getPersonList: {
- data.enforceInterface(DESCRIPTOR);
- java.util.List<com.chenyu.service.Person> _result = this.getPersonList();
- reply.writeNoException();
- reply.writeTypedList(_result);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
- private static class Proxy implements com.chenyu.service.IMyAidl {<span style="white-space:pre"> </span>//⑤
- ...
- }
- static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); //⑥
- static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
- }
①Stub()構造方法:此方法呼叫了父類Binder的attachInterface()方法,將當前的Interface與Binder聯絡起來,由於傳遞了DESCRIPTOR這個引數,唯一標識了當前Interface。
②asInterface(IBinder obj) :靜態方法,傳遞了一個介面物件,該物件從哪裡傳遞進來的呢?我們來看看上一章部落格的客戶端程式碼:
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d("cylog", "onServiceConnected success");
- iMyAidl=IMyAidl.Stub.asInterface(service);
- }
- public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) {
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {
- return ((com.chenyu.service.IMyAidl) iin);
- }
- return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);
- }
- /**
- * Attempt to retrieve a local implementation of an interface
- * for this Binder object. If null is returned, you will need
- * to instantiate a proxy class to marshall calls through
- * the transact() method.
- */
- public IInterface queryLocalInterface(String descriptor);
③asBinder():此方法用於返回當前物件本身。
④onTransact(int code,Parcel data,Parcel reply,int flags):該方法一般執行在服務端中的Binder執行緒池中,即遠端請求會在該方法得到處理。傳遞的code值用於判斷客戶端的請求目標,是addPerson或者是getPersonList。我們以請求目標為addPerson()為例分析一下,提取其主要函式體如下:
- case TRANSACTION_addPerson: {
- data.enforceInterface(DESCRIPTOR);
- com.chenyu.service.Person _arg0;
- if ((0 != data.readInt())) {
- _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);
- } else {
- _arg0 = null;
- }
- this.addPerson(_arg0);
- reply.writeNoException();
- return true;
- }
- private IBinder iBinder= new IMyAidl.Stub() {
- public void addPerson(Person person) throws RemoteException {
- persons.add(person);
- }
- public List<Person> getPersonList() throws RemoteException {
- return persons;
- }
- };
好了,回到當前的類,我們繼續往下看:
⑤private static class Proxy:這裡又出現了一個私有的靜態內部類,關於這個類將在接下來詳細講述。
⑥最後兩行程式碼分別是兩個常量,標誌了兩個方法,即上面提到的code值。
(4)Proxy類,也實現了IMyAidl介面,同時實現了addPerson和getPersonList的方法。而Proxy類在哪裡被例項化的呢?是上面(3)②中,當客戶端與服務端不在同一個程式的時候,就會例項化這個代理類,並返回給客戶端。什麼叫做代理類呢?所謂代理,即一箇中介,客戶端拿到的例項,能操作服務端的部分功能,讓客戶端以為自己已經拿到了服務端的例項,其實不是,只是拿到服務端的一個代理而已。接下來我們展開該類,看看內部:
- private static class Proxy implements com.chenyu.service.IMyAidl {
- private android.os.IBinder mRemote;
- Proxy(android.os.IBinder remote) {
- mRemote = remote;
- }
- public android.os.IBinder asBinder() {
- return mRemote;
- }
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- }
- public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- if ((person != null)) {
- _data.writeInt(1);
- person.writeToParcel(_data, 0);
- } else {
- _data.writeInt(0);
- }
- mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
- _reply.readException();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- java.util.List<com.chenyu.service.Person> _result;
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);<span style="white-space:pre"> </span> //①
- _reply.readException();
- _result = _reply.createTypedArrayList(com.chenyu.service.Person.CREATOR);
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- return _result;
- }
- }
至此,對於IPC的方式之一——AIDL的原理已經剖析完畢,接下來總結一下:
1、客戶端發出繫結請求,服務端和客戶端繫結在同一個Binder上。客戶端執行asInterface()方法,如果客戶端和服務端處於同一程式,則直接返回服務端的Stub物件本身,如果處於不同程式,則返回的是Stub.proxy代理類物件。
2、客戶端傳送遠端請求(addPerson或者getPersonList),此時客戶端執行緒掛起,Binder拿到資料後,對資料進行處理如在不同程式,會把資料寫入Parcel,呼叫Transact方法。
3、觸發onTransact方法,該方法執行在Binder執行緒池,方法中會呼叫到服務端實現的介面方法,當資料處理完畢後,返回reply值,經過Binder返回客戶端,此時客戶端執行緒被喚醒。
三、優化
最後說一說如何優化AIDL,上面提到,客戶端傳送請求後,會被掛起,這意味著,如果處理資料的時間過長,那麼該執行緒就一直等不到喚醒,這是很嚴重的,如果在UI執行緒傳送請求,會直接導致ANR,所以我們需要在子執行緒傳送非同步請求,這樣才能避免ANR。還有一點,Binder可能是意外死亡的,如果Binder意外死亡,那麼子執行緒可能會一直掛起,所以我們要啟用重新連線服務。有兩個方法,一個是給Binder設定DeathRecipient監聽,另一個是在onServiceDisconnected中重連服務。
參考書籍:《Android 開發藝術探索》 任玉剛著,2015年9月第一版
相關文章
- Android的IPC機制BinderAndroid
- Android的IPC機制(三)——Binder連線池Android
- Android Binder機制淺析Android
- android Binder機制深入淺出Android
- android binder ipcAndroid
- Android Binder IPC分析Android
- 理解 Android Binder 機制(三):Java層AndroidJava
- Binder通訊機制與IPC通訊.md
- Android中的IPC機制Android
- Android的IPC機制(一)——AIDL的使用AndroidAI
- Android的IPC機制(六)—— BroadcastReceiver的使用AndroidAST
- android-IPC/Binder/D-BUS(Binder/Messager/AIDL)程式間通訊(訊息機制)AndroidAI
- Android IPC 機制分析Android
- Android的IPC機制(五)—— ContentProvider的使用AndroidIDE
- 圖解Android中的binder機制圖解Android
- Android程式間通訊(IPC)機制Binder簡要介紹和學習計劃Android
- 淺談Android中的事件分發機制Android事件
- Binder池淺談分析
- Android進階(六)Binder機制Android
- Android Binder機制文章轉載Android
- Android的IPC機制(七)—— Socket的原理簡析與使用Android
- Binder機制
- 淺談Android 事件分發機制(二)Android事件
- 真香!為什麼Android要採用Binder作為IPC機制?全套教學資料Android
- 從AIDL開始談Android程式間Binder通訊機制AIAndroid
- Android的IPC機制(四)—— Messenger的使用及原始碼分析AndroidMessenger原始碼
- Java 同步機制淺談Java
- 理解 Android Binder 機制(二):C++層AndroidC++
- Android系統之Binder通訊機制Android
- 理解 Android Binder 機制(一):驅動篇Android
- Binder學習(二)Binder機制解析
- 02.Android之IPC機制問題Android
- 藉助 AIDL 理解 Android Binder 機制——Binder 來龍去脈AIAndroid
- Binder機制分析(2)——從MediaService中看Binder的實現和使用(1)
- Binder機制分析(2)——從MediaService中看Binder的實現和使用(2)
- 淺談 LiveData 的通知機制LiveData
- 3分鐘帶你看懂android的Binder機制Android
- 詳解 Android 中的 IPC 機制:基礎篇Android