Android 進程通訊Binder機制

前言

想寫篇關於Binder的文章,可對其一無所知,無從下手。在閱讀了大量的優秀文章後,心驚膽戰的提筆,不怕文章被貽笑大方,怕的是誤人子弟!望各位大佬抽空閱讀本文的同時,可以對文章的知識點持懷疑態度,共同探討,共同進步!java

1、序列化

平常開發中,經過Intent攜帶數據跳轉Activity時,數據一般要經過實現Serializable或Parcelable接口,才能在被Intent所攜帶,而Serializable接口和Parcelabel接口主要是完成對象的序列化過程。將對象持久化到設備上或者網絡傳輸一樣也須要序列化。android

1.Serializable 接口

Serializable接口是Java所提供的,爲對象提供標準的序列化和反序列化操做。一般一個對象實現Serializable接口,該對象就具備被序列化和反序列化的能力,並且幾乎全部工做有系統自動完成。Serializable接口內serialVersionID可指定也能夠不指定,其做用是用來判斷序列化前和反序列化的類版本是否發生變化。該變量若是值不一致,表示類中某些屬性或者方法發生了更改,反序列化則出問題。(靜態成員變量和transient關鍵字標記的成員不參與序列化過程)git

2.Parcelable 接口

Parcelable 接口是Android所提供的,其實現相對來講比價複雜。實現該接口的類的對象就能夠在Intent和Binder進行傳遞。github

3.二者的區別

Serializable是Java提供的接口,使用簡單,但序列化與反序列化須要大量的IO操做,因此開銷比較大。Parcelable是Android提供的序列化方法,使用麻煩當效率高。在Android開發中,將對象序列化到設備或者序列化後經過網絡傳輸建議使用Serializable接口,其餘狀況建議是用Parcelable接口,尤爲在內存的序列化上。例如Intent和Binder傳輸數據。編程

2、AIDL

在Java層,想利用Binder進行誇進程的通訊,那就得經過AIDL(Android 接口定義語言)了,AIDL是客戶端與服務使用進程間通訊 (IPC) 進行相互通訊時都承認的編程接口,只有容許不一樣應用的客戶端用 IPC 方式訪問服務,而且想要在服務中處理多線程時,纔有必要使用 AIDL,若是是在單應用(單進程),建議使用Messager。安全

一、AIDL支持的數據類型

  • Java 編程語言中的全部原語類型(如 int、long、char、boolean 等等)
  • String 和 CharSequence
  • 全部實現了Parcelable接口的對象
  • AIDL接口
  • List,目前List只支持ArrayList類型,持有元素必須是以上講到類型。
  • Map,目前只支持HashMap類型,持有元素必須是以上講到類型。

自定義的Parcelable對象和AIDL接口必須顯示導入到AIDL文件中。bash

數據的走向markdown

Parcelable對象和AIDL接口在使用前必須標明數據的走向:網絡

  • in 客戶端流向服務端
  • out 服務端流向客戶端
  • inout 服務端與客戶端可進行交互

示例:多線程

void addUser(inout User user);
複製代碼

二、服務端的實現

2.一、定義數據對象

定義一個實現了Parcelable 接口,做爲客戶端和服務端傳輸的數據對象。

public class User implements Parcelable {

    private String username;

    private String address;

    public User() {
    }
    
    public User(String username, String address) {
        this.username = username;
        this.address = address;
    }

    User(Parcel in) {
       readFromParcel(in);
    }
    //系統默認生成,反序列化過程,咱們只須要要構造方法讀取相關值就能夠
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
     //系統默認生成,內容描述功能,幾乎全部狀況下都返回0,
     //僅僅當前存在文件描述符,才返回1
    @Override
    public int describeContents() {
        return 0;
    }
    //序列化過程,經過一系列的write將值寫到Parcel 對象
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(username);
        dest.writeString(address);
    }
    
    @Override
    public String toString() {
        return username+":"+address;
    }

    public void readFromParcel(Parcel in){
        username=in.readString();
        address=in.readString();
    }
}
複製代碼

2.二、抽象服務端服務

經過下面方法,創建一個UserManger.aidl文件,表示服務端能爲客戶端提供什麼樣的服務。

下面代碼經過創建UserManager.aidl文件,爲客戶端提供 addUsergetUser的能力。UserManager能夠理解爲,服務端和客戶端的共同約定,二者能進行怎麼樣的交互。

package com.gitcode.server;

// 在這裏要導入傳遞對象的類型,例如User
import com.gitcode.server.User;

interface UserManager {

    void addUser(inout User user);

    User getUser(int index);
}
複製代碼

定義UserManager.aidl文件後,系統默認會生成UserManager.java文件。


UserManager.java的代碼以下,爲了減小篇幅,去掉了一些實現。

public interface UserManager extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.gitcode.server.UserManager {
        private static final String DESCRIPTOR = "com.gitcode.server.UserManager";
        
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        
        public static com.gitcode.server.UserManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.gitcode.server.UserManager))) {
                return ((com.gitcode.server.UserManager) iin);
            }
            return new Stub.Proxy(obj);
        }

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

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
           ......
        }

        private static class Proxy implements com.gitcode.server.UserManager {
            private android.os.IBinder mRemote;

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

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

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public void addUser(com.gitcode.server.User user) throws android.os.RemoteException {
                 ......
            }

            @Override
            public com.gitcode.server.User getUser(int index) throws android.os.RemoteException {
                .....
            }
        }

        static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public void addUser(com.gitcode.server.User user) throws android.os.RemoteException;

    public com.gitcode.server.User getUser(int index) throws android.os.RemoteException;
}

複製代碼

從上文可知,UserManager自己是一個接口,並繼承IInterface接口。UserManager.java聲明瞭addUsergetUser,和在UserManager.aidl的聲明是一致的。同時聲明兩個整型TRANSACTION_addUserTRANSACTION_getUser,用於在transact()方法中標識調用服務端哪一個方法。若是服務端和客戶端在不一樣進程,方法調用會走transact()方法,邏輯由Stub 和Proxy 內部類完成。

內部類Stub的一些概念和方法含義:

DESCRIPTOR

Binder的惟一標識,通常用當前的類名全名標識。

asInterface(IBinder obj)

將服務端的Binder對象轉換成客戶端的AIDL接口類型的對象,若是客戶端和服務端同一進程,直接返回Stub對象自己,不在同一進程,則返回由系統封裝的Stub.proxy對象。

asBinder

返回當前Binder對象

onTransact(int code, Parcel data, Parcel reply, int flags)

運行在服務端Binder線程池,當客戶端跨進程發起請求後,系統封裝後交由此方法來處理。code表示調用服務端什麼方法,上文聲明的整型。data表示客戶端傳遞過來的數據,reply爲服務端對客戶端的回覆。

內部代理類 Poxy,表示客戶端遠程能對服務端進行的操做。

addUser運行在客戶端,當客戶端遠程調用時,


在相同目錄下建立User.aidl,能夠直接複製UserManager.aidl,內容修改以下。

package com.gitcode.server;

parcelable User;
複製代碼

在服務端中,服務通常以Service體現,定義UserServcie,繼承Service。

public class UserService extends Service {
    private static final String TAG = "Server";
    private List<User> list = new ArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        list.add(new User("GitCode", "深圳"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG,"on Bind");
        return stub;
    }


    private UserManager.Stub stub = new UserManager.Stub() {
        @Override
        public void addUser(User user) throws RemoteException {
            list.add(user);
            Log.i(TAG,"add user:"+user);
        }

        @Override
        public User getUser(int index) throws RemoteException {
            Log.i(TAG,"get user,index:"+index);
            return list.size() > index && index >= 0 ? list.get(index) : null;
        }
    };
}
複製代碼

在AndroidManifest.xml文件聲明Service,以兩個組件造成單獨的app來體現兩個進程,經過AIDL進行數據交互。在客戶端經過bindService()來啓動該服務。

<service android:name="com.gitcode.server.UserService"
    android:enabled="true"
    android:exported="true">
        <intent-filter>
            <action android:name="com.gitcode.server.userservice"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
</service>
複製代碼

三、客戶端的實現

客戶端主要是經過共同的約定(UserManger.aidl)向服務端進行請求,服務端響應客戶端的請求。爲了提升效率和減小出錯,經過拷貝來實現客戶端的AIDL文件。將服務端的aidl整個文件拷貝到客戶端的main目錄下,不作任何修改

在客戶端創建與服務端User類同包的目錄,並將User類拷貝過來,不作任何修改
在Activity中綁定服務端的Service,綁定成功後進行數據交互。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Client";
    private UserManager mUserManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        toBindService();
    }

    private void toBindService() {
        Intent intent = new Intent("com.gitcode.server.userservice");
        intent.setPackage("com.gitcode.server");
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mUserManager = UserManager.Stub.asInterface(service);

            try {
                User user = mUserManager.getUser(0);
                Log.e(TAG, user.toString());

                mUserManager.addUser(new User("張三","北京"));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
}
複製代碼

運行效果:

客戶端:

服務端:

四、小結

客戶端調用服務的方法,被調用的方法運行在服務端的的Binder線程池,同時客戶端會被掛起,若是服務端方法執行耗時操做,就會致使客戶端ANR,因此不要在客戶端主線程訪問遠程服務方法。同時服務端不該該本身新建新建線程運行服務方法,由於方法會交由線程池處理,同時對數據也要作好併發訪問處理。

AIDL能夠說爲應用層開發提供了封裝,不用過多的瞭解Binder的機制,經過生成的UserManager.java,初步能夠了解Binder的IPC機制。使用AIDL在進程之間進行數據通訊,更注重的是細節和業務的實現。

上文demo地址

3、Binder

Binder進制是Android系統提供的一種IPC機制。因爲Android是基於Linux內核,所以,除了Binder之外,還有其餘的IPC機制,例如Socket,共享內存,管道和消息隊列等。之全部不使用原有的 IPC機制,是由於使用Binder機制,能從性能、穩定性、安全性帶來更好的效果。例如,Socket是一套通用的接口,傳輸速率低下,適合網絡傳輸這種狀況,而管道和消息隊列須要數據的兩次拷貝,共享內容難以管控等。而Binder對數據只須要一次拷貝,使用C/S架構,職責明確,容易維護和使用。

經過下圖能夠了解到,Binder機制經過內存映射實現跨進程通訊,Binder在IPC機制只是做爲一個數據的載體,當進程A向虛擬內存空間中寫入數據,數據會被實時反饋到進程B的虛擬內存空間。整個發送數據的過程,只從用戶空間拷貝一次到虛擬內存空間。

在Binder機制中,主要涉及到Client、Server、ServiceManger三個端,三者經過Binder進行跨進程通訊,支持着Android這個大網絡。它們的關係以下圖。

Server

Server進程須要註冊一些Service到ServiceManger中,以對外告知其可提供的服務。例如上文AIDL中,會註冊UserService,併爲Client提供添加User和獲取User的操做。註冊的過程,Server進程就是客戶端,而ServiceManger就是服務端。

Client

對Sever進程進行業務邏輯操做。經過Service的名稱在ServiceManger查找對應的Service。

ServiceManager

ServiceManger集中管理系統內的全部Service,服務經過註冊Service到ServiceManger的查找表中,當Client根據Service名稱請求ServiceManger在查找表中查詢對應的Service。

圖表示三者的C/S架構,例如Client查詢向ServiceManger查詢Service時,Client就是客戶端,而ServiceManger就是服務端。而虛線則表示二者之間經過Binder進行進程間的通訊,所以經過了解一條虛線的流程,就能夠知道Binder的機制。

一、Service的註冊過程

咱們常經過getSystemService()函數來獲取系統的相關服務,例如ActivityManagerService,那麼這些Service從哪裏來呢?就是有些服務進程向ServiceManager註冊本身的一些Service,表示本身能對client提供什麼樣的服務。經過了解Service的註冊過程,來學習Binder機制的工做原理。

BpServcieManger和BnServcieManger是客戶端與服務端進程業務層輯實現的封裝,而BpBinder和BBinder是IPC機制的方式。此時Server進程是客戶端,ServiceManger是服務端。

1.1 ProcessState

每一個進程經過單例模式建立了惟一的ProcessState對象,在其構造器中,經過open_driver()方法打開了/dev/binder設備,至關於Server進程打開了與內核的Binder驅動交互的通道,並設置最大支持線程數爲15。binder設備是Android在內核中爲完成進程間通訊而專門設置的一個虛擬設備。

1.2 BpBinder和BBinder

BpBinder與BBinder都是Android與Binder通訊相關的表明,二者一一對應,都從IBinder派生而來。若是說BpBinder表明客戶端,那麼BBinder就表明服務端,一個BpBinder經過handler標識與對應的BBinder進行交互。在Binder系統,handler標識爲0表明着ServiceManger所對應的BBinder。BpBinder與BBinder並無直接操做ProcessState打開的binder設備。

1.3 BpServiceManger和BnserviceManger

二者繼承至IServiceManger,與業務邏輯相關,能夠說將業務層的邏輯架構到Binder機制上。BnserviceManger從IServiceManger BBinder派生而來,可直接參與Binder的通訊,而BpServiceManger經過mRemote指向BpBinder。

1.4 註冊相關Service

經過上文三小節,BpServiceManger對象實現對IServiceManger的業務函數,又有BpBinder做爲通訊表明,下面分析一下注冊的過程。

將字符串名字和Service對象做爲參數傳到BpServiceManger對象的addService()函數,該方法將參數數據打包後傳遞給BpBidner的transact()函數。業務層的邏輯到此就結束,主要做用是將請求信息打包交給通訊層去處理。

在BpBinder的transact()函數調用了IPCThreadState對象的transact()函數,因此說BpBinder自己沒有參與Binder設備的交互。每一個線程都有一個IPCThreadState對象,其擁有一個mOut、mIn的緩衝區,mOut用來存儲轉發Binder設備的數據,而mIn用來接收Binder設備的數據。經過ioctl方式與Binder設備進行交互。

1.5 小結

經過上文Service的註冊過程,分析了Binder的機制。Binder只是通訊機制,業務能夠基於Binder機制,也能夠基於其餘IPC方式的機制,也就是上文爲啥有BpServiceManger和BpBinder。Binder之因此複雜,是Android經過層層的封裝,巧妙的將業務與通訊融合在一塊兒。主要仍是設計理想很牛逼。

二、ServiceManger

經過1小節的分析,是否應該也有一個類繼承自BnServiceManger來處理遠方請求呢?

很惋惜的是在服務端並無BnServiceManger子類來響應遠程客戶端的請求,而是交給了ServiceManger來處理。

2.1 成爲Service管理中心

ServiceManger經過binder_open函數打開binder設備,並映射內存。經過handler等於0標識本身,讓本身成爲管理中心,全部service向ServiceManger註冊時,都是經過handle標識爲的0的BpBinder找到ServiceManger對應的BBinder,ServiceManager會保存要註冊的Service的相關信息,方便Client查找。並非全部的Service均可以在ServiceManger註冊,若是Server進程的權限不夠root或system,那麼須要在allowed添加相應的項。

2.2 ServiceManger集中管理帶來的好處

  • 統一管理,施加管控權
  • 通知字符串名稱查找Service
  • Server進程生命無常,經過ServiceManger,Client能夠實時知道Server進程的最行動態。

三、Client

Client想要使用Server進程提供的Service,又該進行哪些步驟呢?

3.1 查詢ServiceManger

Client想要獲得某個Service的信息,就得與ServiceManager打交道,經過調用getService()方法來獲取對應Service信息。Client經過服務名稱向ServiceManger查詢對應的Service。若是Service未註冊,則循環等待直到該Service註冊;若是已註冊,則會對應封裝了一個能與遠程Service通訊的BpBinder的BpXXXService,經過該Service,Client客戶調用相關業務邏輯函數。

3.2 請求信息的處理

Client調用的業務函數,莫非就是將請求參數打包發送給Binder驅動,BpBinder經過handler的值找到對應端的Service來處理。

在1.4小節中,說到IPCThreadState對象,在其executeCommand函數中,經過調用實現了BnServiceXXX的對象onTransact函數,直接定位到業務層。這就是在AIDL中,爲何在onTransact()函數中處理響應數據。

4、總結

經過對Binder機制的學習,瞭解Android是如何經過層層封裝將Binder機制集成要應用程序,對Binder機制有一個較深刻的理解。能夠經過第Java層AIDL的使用,加深對Binder機制的理解。

我的水平有限,有誤請幫忙勘正,謝謝大佬。喜歡就幫忙點個讚唄。

逛逛GitHub

參考資料:

深刻理解Android 卷一

相關文章
相關標籤/搜索