IPC詳解之Binder工作原理(中)

Binder是Android中的一個類,她實現了IBinder接口。從IPC角度來說,Binder是客戶端和服務端進行通訊的媒介!(PS:在Android系統有兩個不同的進程,請求數據的進程為客戶端,接收請求的進程為服務端)

Service與Binder之間的關系

Android開發中,Binder主要用在Service:

  1. 同一個進程bindService
  2. Messenger發送Message
  3. 通過AIDL跨進程bindService

第一種bindService在ServiceConnection中的onServiceConnected(ComponentName name, IBinder service)的service是綁定的Service自身,這就可以直接調用Service中的方法,沒有涉及Binder核心功能。第二種Messenger發送Message底層其實是封裝AIDL,所以我們用AIDL來分析Binder的工作原理。

AIDL進行IPC

  1. 創建AIDL文件
    • User.aidl
    package com.hk.b.aidl;
    parcelable User;
    
    由于User類無法在AIDL中直接使用,所以要創建User.aidl對User.java進行描述
    • IUserManager.aidl
    package com.hk.b.aidl;
    import com.hk.b.aidl.User;
    
    interface IUserManager {
        List<User> getUserList();
        void addUser(in User u);
    }
    
    AIDL不同于Java,即使兩個aidl文件處于同一個包下,也要對引用進行import

2.使用.aidl文件生成的.java文件
定義好aidl文件后,系統會自動在Android Studio的ProjectName/ModuleName/build/generated/source/com.hk.b.aidl/IUserManager.java,可以直接在Service中使用這個類,這個類的內容是Binder原理的關鍵,稍后解析。

public class UserService extends Service {

    private CopyOnWriteArrayList<User> mUserList = new CopyOnWriteArrayList<>();

    public UserService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    private IBinder mBinder = new IUserManager.Stub() {
        @Override
        public List<User> getUserList() throws RemoteException {
            Log.d("hvcker","server getUsers");
            return mUserList;
        }

        @Override
        public void addUser(User u) throws RemoteException {
            Log.d("hvcker","server add");
            mUserList.add(u);
        }
    };
}

另外這個Service在AndroidManifest.xml文件中要配置使該Service運行在新的進程中,這樣才能達到IPC

<service android:name=".s.UserService" android:process=":remote"/>

3.客戶端綁定服務端

  • 定義一個IUserManager變量

    private IUserManager mUserManager;
    
  • 定義一個ServiceConnection

    private ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mUserManager = IUserManager.Stub.asInterface(service);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mUserManager = null;
        }
    };
    
  • 綁定Service

    bindService(new Intent(this, UserService.class), mConn, BIND_AUTO_CREATE);
    

這個時候就可以用mUserManager來執行addUsergetUserList,那么問題來了,mUserManager是怎么把客戶端的數據添加到服務端,又是怎么獲取服務端的數據呢?

Binder工作原理

首先,我們來看一下根據IUserManager.aidl文件生成的Java類中有什么乾坤
1.先來大概看一下IUserManager.java

public interface IUserManager extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.hk.b.aidl.IUserManager {
    //...
    }
    public java.util.List<com.hk.b.aidl.User> getUserList() throws android.os.RemoteException;

    public void addUser(com.hk.b.aidl.User u) throws android.os.RemoteException;
}
  • IUserManager繼承IInterface這個接口,要想在Binder中用,這是必須的。
  • 內部類Stub繼承Binder,同時它又實現了IUserManager。Stub他是一個抽象類,是因為它沒有實現IUserManager的未實現方法,這兩個方法要在使用該Stub的時候實現。

2.再來看一下IUserManager.Stub

public static abstract class Stub extends android.os.Binder implements com.hk.b.aidl.IUserManager {
    private static final java.lang.String DESCRIPTOR = "com.hk.b.aidl.IUserManager";

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public static com.hk.b.aidl.IUserManager asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.hk.b.aidl.IUserManager))) {
            return ((com.hk.b.aidl.IUserManager) iin);
        }
        return new com.hk.b.aidl.IUserManager.Stub.Proxy(obj);
    }

    @Override
    public android.os.IBinder asBinder() {
        return this;
    }

    @Override
    public boolean onTransact(int code, android.os.Parcel data, 
        //...
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements com.hk.b.aidl.IUserManager {
        //...
    }
    //...
}
  • DESCRIPTOR:Binder的唯一標識,一般用當前aidl生成類的類名表示,例如:com.hk.b.aidl.IUserManager
  • Stub():構造函數,把該接口添加到本地接口中
  • asBinder():返回當前Binder對象
  • asInterface(android.os.IBinder obj):android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);客戶端查詢本地接口,如果本地接口有,說明客戶端和服務端是屬于同一個進程,則返回該進程里的IInterface,如果沒有說明客戶端和服務端不屬于同一個進程,則new com.hk.b.aidl.IUserManager.Stub.Proxy(obj),返回Binder代理類Sub.Proxy,Binder跨進程的關鍵來了

3.IUserManager.Stub.Proxy和它用到的一些Stub成員或方法

public static abstract class Stub extends android.os.Binder implements com.hk.b.aidl.IUserManager {
    
    //...
    
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getUserList: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.hk.b.aidl.User> _result = this.getUserList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addUser: {
                data.enforceInterface(DESCRIPTOR);
                com.hk.b.aidl.User _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.hk.b.aidl.User.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addUser(_arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements com.hk.b.aidl.IUserManager {
        private android.os.IBinder mRemote;

        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }

        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }

        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public java.util.List<com.hk.b.aidl.User> getUserList() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.hk.b.aidl.User> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getUserList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.hk.b.aidl.User.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public void addUser(com.hk.b.aidl.User u) 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 ((u != null)) {
                    _data.writeInt(1);
                    u.writeToParcel(_data, 0);
                } else {
                    _data.writeInt(0);
                }
                mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }

    static final int TRANSACTION_getUserList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
  • TRANSACTION_getUserListTRANSACTION_addUser接口方法id,如果有更多方法,依次遞增

  • Proxy實現了IUserManager,并實現了它的兩個方法getUserList()addUser(com.hk.b.aidl.User u),這兩個方法是在客戶端執行的,做了什么事呢?

    1. 創建android.os.Parcel類型的_data和_replay,_data用來存放方法參數,_replay用來存放RPC的返回值。
    2. _data.writeInterfaceToken(DESCRIPTOR)寫入唯一標識,如果方法有參數,寫入1,然后把參數信息寫入_data
    3. mRemote.transact(Stub.TRANSACTION_xxx, _data, _reply, 0)Binder發起RPC,傳入方法id,參數,返回值和標志位,0表示普通RPC
    4. _result = _reply.createTypedArrayList(com.hk.b.aidl.User.CREATOR)如果方法有返回值的話,_reply調用.createXxx()方法,Xxx表示返回值類型,比如TypedArrayList返回的類型是帶泛型的ArrayList
    5. 最后回收_data和_reply避免資源的浪費
  • 一旦客戶端執行transact,客戶端發起RPC的線程就會掛起,等待服務端的響應,這個時候就會調用服務端Stub的onTransact方法,這個方法又干了什么呢?

    1. 匹配方法id,不用多說
    2. 從服務端Binder線程池中用唯一標示符匹配客戶端傳過來android.os.Parcel類型的data和reply,如果data.readInt()不為0,取出參數
    3. 調用與客戶端請求對應的服務端實現IUserManager的方法
    4. 如果方法有返回值,則寫入reply
    5. 返回true,如果返回false,則服務端拒絕,可以用這個來做權限判斷。

總結

這樣我們就很清晰的知道了Binder的工作原理,我們完全可以不用aidl文件自己寫一個Binder,但是存在即是合理,還是要用aidl文件來節約開發成本,最后來畫一下Binder的流程圖吧

Client->Server: bindService
Server-->Client: 返回一個Binder
Client->Server: Binder請求數據,寫入參數,調用transact,掛起
Note right of Server:onTransact從Binder線程池中得到與客戶端對應的Binder,處理請求
Server-->Client: 寫入結果,返回給客戶端Binder,并喚醒客戶端
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容