重學Android——Binder運行機制

Binder

昨天從源碼角度看了Android9.0下Activity的啓動流程,其中關於跨進程的調用,都使用到了Binder進行跨進程通訊,那麼今天來閱讀下Android的FrameWork層怎麼實現Binder的吧。java

IPC

IPC是Inter Process Communication的縮寫,意思是進程間通訊。在Android系統中,每一個應用都運行在一條獨立的進程上,具備本身的DVM實例,並且進程之間是相互隔離的,也就是說各進程之間是互相獨立,不影響的,就算哪個進程崩潰也,也不會影響別的進程。有利於提升系統的穩定性。android

爲何要使用Binder

在Linux內核中,提供了幾種跨進程IPC方式,管道pipe(在前面講Android消息機制的時候,Looper與MessageQueue的底層就是用c++使用了管道機制)、消息隊列、共享內存、信號量、信號、socket。c++

那麼爲何Android要從新開發一個Binder,而不採用現有的幾種方式來進行進程間IPC呢?緩存

首先看一下這幾種IPC各自的特色安全

通訊方式 特色
管道pipe 在建立時分配一個page大小內存,緩存區大小受限
消息隊列 信息複製兩次,額外的CPU消耗,不合適頻繁或信息量大的通訊
共享內存 無須複製,共享緩衝區直接附加到進程虛擬地址空間,速度快,但進程的同步問題,操做系統沒法實現,必須各進程間使用同步工具來解決
信號量 常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。主要做爲進程間以及同一進程內不一樣線程之間的同步手段
信號 不適用於信息交換,更適用於進程中斷控制,好比非法內存訪問,殺死進程等
套接字Socket 做爲更通用接口,傳輸效率低,主要用於不一樣機器或跨網絡的通訊

那麼使用Binder的優勢在哪?服務器

  1. 性能:Binder傳輸只須要一次copy,管道、消息隊列、Socket都須要2次,但共享內存方式一次內存拷貝都不須要;從性能角度看,Binder性能僅次於共享內存,效率成倍增加。
  2. 安全性:Binder機制對於通訊雙方的身份是內核進行機校檢支持,而socket只須要知道地址均可以鏈接,安全機制須要上層協議來處理。
  3. 易用性:共享內存不須要copy,性能高,但是使用太複雜,管道與消息隊列還要進行包裝,另外,Binder使用面向對象的設計,基於C/S架構,進行一次遠程調用和直接調用本地調用同樣,使用方便
  4. 須要管理跨進程傳遞的代理對象的生命週期,別的機制都沒法完成,Binder驅動經過引用計數技術解決了此問題。

IPC原理圖

每一個Android的進程,只能運行在本身進程所擁有的虛擬地址空間,對應一個4GB的虛擬空間,其中3GB是用戶空間,1GB是內核空間,固然是能夠經過參數來調整的。對於用戶空間,不一樣進程之間是隔離的,不能共享,而內核空間是能夠共享的。當Client進程身Server進程通訊時,能夠利用進程間共享內核空間來完成底層通訊工做。網絡

Binder在Android中的運用

Binder在Android中使用很是廣,能夠說沒有Binder,Android系統都將不存在,不管是四大組件的生命週期,仍是view的工做機制,都使用了Binder。架構

那麼,咱們先嚐試在程序開發中,怎麼使用Binder來進行進程間通訊。app

在Android中使用Binder,主要是AIDL,那麼先說下AIDL傳遞的參數類型socket

  1. 基本數據類型(除short類型外)

  2. String、charSequence

  3. List,map(List與Map承載的數據必須是AIDL支持的類型,或其餘聲明的AIDL對象)

  4. 實現了Parcelable接口的數據類型

使用示例

// Book.aidl
package com.apkcore.studdy;
parcelable Book;

//IBookController.aidl
package com.apkcore.studdy;
import com.apkcore.studdy.Book;

interface IBookController{
    List<Book> getBookList();
    void addBook(inout Book book);
    void addBookIn(in Book book);
    void addBookOut(out Book book);
}
複製代碼

在代碼中的目錄結構是上面這樣的。

而後先clean一次項目,在如圖的目錄下,能夠看到AS自動生成了IBookController類,這個類的結構過會再看。

而後新建一個同一個包名下在實體類Book

package com.apkcore.studdy;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    private String name;

    public Book(String name) {
        this.name = name;
    }
    
    //1 爲aidl使用Out類型時,會使得客戶端傳送一個不包含任何數據的對象給服務端,但該對象不是直接爲null,因此仍是須要實例化Book,就須要一個無參構造函數
    public Book() {
    }

    public String getName() {
        return name == null ? "" : name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public String toString() {
        return "book name:" + name;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
    }

	//2 使用插件自動生成Parcelable只包含了writeToParcel,當使用InOut時會出錯,須要指定readFromParcel方法
    public void readFromParcel(Parcel dest) {
        name = dest.readString();
    }

    protected Book(Parcel in) {
        this.name = in.readString();
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

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

複製代碼

寫實體類須要注意的地方有兩個

  1. 爲aidl使用Out類型時,會使得客戶端傳送一個不包含任何數據的對象給服務端,但該對象不是直接爲null,因此仍是須要實例化Book,就須要一個無參構造函數
  2. 默認生成的模板類的對象只支持爲 in 的定向 tag 。爲何呢?由於默認生成的類裏面只有 writeToParcel() 方法,而若是要支持爲 out 或者 inout 的定向 tag 的話,還須要實現 readFromParcel() 方法——而這個方法其實並無在 Parcelable 接口裏面,因此須要咱們從頭寫,指定readFromParcel方法。具體爲何你們能夠去看看:你真的理解AIDL中的in,out,inout麼?

AIDLService的源碼以下:

public class AIDLService extends Service {
    private static final String TAG = "AIDLService";

    private List<Book> mBookList;

    public AIDLService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList = new ArrayList<>();
        initData();
    }

    private void initData() {
        Book book1 = new Book("aaa");
        Book book2 = new Book("bbb");
        Book book3 = new Book("cc");
        Book book4 = new Book("ddddddd");
        Book book5 = new Book("eee");
        mBookList.add(book1);
        mBookList.add(book2);
        mBookList.add(book3);
        mBookList.add(book4);
        mBookList.add(book5);
    }

    private  final IBookController.Stub mStub = new IBookController.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            if (book!=null){
                book.setName("服務器更改了書的名字 Inout");
                mBookList.add(book);
            }else {
                Log.d(TAG, "addBook 接收到了一個空對象 Inout");
            }
        }

        @Override
        public void addBookIn(Book book) throws RemoteException {
            if (book!=null){
                book.setName("服務器更改了書的名字 In");
                mBookList.add(book);
            }else {
                Log.d(TAG, "addBook 接收到了一個空對象 In");
            }
        }

        @Override
        public void addBookOut(Book book) throws RemoteException {
            if (book!=null){
                book.setName("服務器更改了書的名字 Out");
                mBookList.add(book);
            }else {
                Log.d(TAG, "addBook 接收到了一個空對象 Out");
            }
        }
    };
    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }
}
複製代碼

給Service添加權限

<service
            android:name=".AIDLService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.apkcore.aidl.action"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
複製代碼

再新建一個客戶端應用,把aidl文件複製過去,一樣仍是Book,必須保證他們的包名是同樣的,目錄結構以下

在MainActivity

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "Client";

    private IBookController mIBookController;
    private boolean connected;
    private List<Book> mBookList;

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIBookController = IBookController.Stub.asInterface(service);
            connected = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            connected = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setPackage("com.apkcore.studdy");
        intent.setAction("com.apkcore.aidl.action");
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }

    public void bt1(View view) {

        if (connected) {
            try {
                mBookList = mIBookController.getBookList();
                if (mBookList != null && mBookList.size() > 0) {
                    for (Book book : mBookList) {
                        Log.d(TAG, "如今服務端有的書: " + book);
                    }
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void bt2(View view) {
        if (connected) {
            Book book = new Book("newInOut");
            try {
                mIBookController.addBook(book);
                Log.d(TAG, "bt2 向service發送了一本新書:" + book.getName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void bt3(View view) {
        if (connected) {
            Book book = new Book("newIn");
            try {
                mIBookController.addBookIn(book);
                Log.d(TAG, "bt3 向service發送了一本新書:" + book.getName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void bt4(View view) {
        if (connected) {
            Book book = new Book("newOut");
            try {
                mIBookController.addBookOut(book);
                Log.d(TAG, "bt4 向service發送了一本新書:" + book.getName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
...
}
複製代碼

分別點擊調用inout,in,out按鈕,能夠得出結論:

AIDL中的定向 tag 表示了在跨進程通訊中數據的流向,其中 in 表示數據只能由客戶端流向服務端, out 表示數據只能由服務端流向客戶端,而 inout 則表示數據可在服務端與客戶端之間雙向流通。其中,數據流向是針對在客戶端中的那個傳入方法的對象而言的。in 爲定向 tag 的話表現爲服務端將會接收到一個那個對象的完整數據,可是客戶端的那個對象不會由於服務端對傳參的修改而發生變更;out 的話表現爲服務端將會接收到那個對象的參數爲空的對象,可是在服務端對接收到的空對象有任何修改以後客戶端將會同步變更;inout 爲定向 tag 的狀況下,服務端將會接收到客戶端傳來對象的完整信息,而且客戶端將會同步服務端對該對象的任何變更。

Binder運行機制分析

咱們接下來看as給咱們自動生成的IBookController類

/* * This file is auto-generated. DO NOT MODIFY. * Original file: G:\\GitHub\\Studdy\\app\\src\\main\\aidl\\com\\apkcore\\studdy\\IBookController.aidl */
package com.apkcore.studdy;
// Declare any non-default types here with import statements
//import com.apkcore.studdy.Book;

public interface IBookController extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    //內部類Stub,繼承自Binder對象,這個內部類是須要在服務端手動實現的,並會經過onBind方法返回客戶端
    public static abstract class Stub extends android.os.Binder implements IBookController {
    	//Binder的惟一標識,通常用當前Binder的類名錶示
        private static final String DESCRIPTOR = "com.apkcore.studdy.IBookController";
        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        /** * Cast an IBinder object into an com.apkcore.studdy.IBookController interface, * generating a proxy if needed. * 將服務端的Binder對象轉換爲客戶端的所需的AIDL接口類型的對象,客戶端拿到這個對象就能夠遠程訪問服務端的方法 * 若是客戶端和服務端位於同一進程,那麼返回的就是服務端的Stub對象自己,不然就是系統封閉後的Stub.proxy 對象 */
        public static IBookController asInterface(android.os.IBinder obj) {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof IBookController))) {
                return ((IBookController)iin);
            }
            return new Proxy(obj);
        }
        
        //用於返回當前Binder對象
        @Override public android.os.IBinder asBinder() {
            return this;
        }

        /** * 運行在服務端進程的Binder線程池中,當客戶端進程發起遠程請求時,遠程請求會要求系統底層執行回調方法 * @param code 客戶端進程請求訪求標識符,服務端進程會根據該標識肯定所請求的目標方法 * @param data 目標就去的參數,它是客戶端進程傳進來的,當咱們調用addBook(Book book)時,這個Book就是 * @param reply 目標方法執行後的結果,將返回給客戶端 */
        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            String descriptor = DESCRIPTOR;
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList:
                {
                    data.enforceInterface(descriptor);
                    java.util.List<Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook:
                {
                    data.enforceInterface(descriptor);
                    Book _arg0;
                    //結合了下面in和out兩個的細節
                    if ((0!=data.readInt())) {
                        _arg0 = Book.CREATOR.createFromParcel(data);
                    }
                    else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    if ((_arg0!=null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    }
                    else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                case TRANSACTION_addBookIn:
                {
                    data.enforceInterface(descriptor);
                    Book _arg0;
                    //從輸入的data流中讀取book數據,並賦值給_arg0
                    if ((0!=data.readInt())) {
                        _arg0 = Book.CREATOR.createFromParcel(data);
                    }
                    else {
                        _arg0 = null;
                    }
                    //在這裏纔是真正的開始執行實際的邏輯,調用服務端寫好的實現
                    this.addBookIn(_arg0);
                    //執行完後就結束了,沒有reply流的操做
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_addBookOut:
                {
                    data.enforceInterface(descriptor);
                    Book _arg0;
                    //能夠看到,out做爲tag時,根本不從data讀取數據,而是直接new
                    //這也是咱們爲何在book裏必定要寫一個無參構造的緣由
                    //這樣,服務端固然也就不可能收到客戶羰的數據了
                    _arg0 = new Book();
                    this.addBookOut(_arg0);
                    reply.writeNoException();
                    //在這裏,_arg0是方法的傳入參數,故服務端的實現裏對傳參作出了任何修改
                    //都會在_arg0體現出來,將它寫入reply流,這樣客戶端就能收到了
                    if ((_arg0!=null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    }
                    else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                default:
                {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        /** * 內部代理類,實現了IBookController接口, * 這個代理類就是服務端返回給客戶端的AIDL接口對象,客戶端能夠經過這個代理類訪問服務端的方法 */
        private static class Proxy implements IBookController {
            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 java.util.List<Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(Book.CREATOR);
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            //這是inout方法
            @Override public void addBook(Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //定向tag爲InOut組合,結合了in和out的兩個方法的操做
                    if ((book!=null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    }
                    else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                    if ((0!=_reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
            @Override public void addBookIn(Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //若是book不爲空,data寫入int值1,將book寫入_data中
                    //若是爲空,寫入int值0
                    if ((book!=null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    }
                    else {
                        _data.writeInt(0);
                    }
                    //以後調用transact方法,將方法編碼
                    //_data(包含從客戶端向服務端的book流)
                    //_reply(包含從服務端流向客戶端的數據流)
                    mRemote.transact(Stub.TRANSACTION_addBookIn, _data, _reply, 0);
                    _reply.readException();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
            @Override public void addBookOut(Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //tag爲out時,並無將book對象寫入_data流
                    mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0);
                    _reply.readException();
                    //與tag爲in的方法裏不一樣的是,在執行transact方法後,還有針對reply的操做,把book賦值給reply流中的數據
                    if ((0!=_reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_addBookIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_addBookOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
    }
    public java.util.List<Book> getBookList() throws android.os.RemoteException;
    public void addBook(Book book) throws android.os.RemoteException;
    public void addBookIn(Book book) throws android.os.RemoteException;
    public void addBookOut(Book book) throws android.os.RemoteException;
}
複製代碼

註釋裏很明白的分析了AIDL三個不一樣的tag時,爲何會有不一樣的結果。

上面的例子中,

  • AIDLService:服務提供者
  • Activity:服務調用者
  • IBookController:負責管理服務,內部經過map集合來存儲service與binder的映射關係。
  • Binder驅動:這是IBookController鏈接各類Service的橋樑,同時也是客戶端與服務端交流的橋樑

總結一下就是應用程序(Activity)首先向IBookController發送AIDLService的請求,IBookController查看已經註冊在裏面的服務的列表,找到相應的服務後,經過Binder驅動將其中的Binder對象返回給客戶端,從而完成對服務的請求

IBookController是編譯器經過咱們本身寫的AIDL文件,自動生成的,固然咱們也能夠手寫,那樣就能夠不用寫AIDL文件了,有興趣的話能夠手寫一波。能夠參考Android多進程之手動編寫Binder類或者《Android開發藝術探索》

Binder鏈接池

若是項目變得很大了,如今擁有10個不一樣的業務模塊須要使用AIDL,若是按照上面的作法,那麼須要寫10個Service,若是更多呢?Service是一種資源,咱們須要將其進一步的優化。

在《Android開發藝術探索》中有提出一個很好的解決多AIDL方法,就是使用Binder鏈接池,受篇幅影響,這裏只說下使用的流程:

  1. 爲每一個業務模塊建立AIDL接口,以及實現其接口的方法
  2. 建立IBinderPool.aidl文件,定義queryBinder(in BinderCode)方法,客戶端經過調用這個方法來返回特定的Binder對象
  3. 建立BinderPoolService服務端,在onBind方法返回實例化的BinderPool.IBinderPoolImpl對象
  4. 建立BinderPool類,在該類實現客戶端與服務端的鏈接,解決線程同步問題,設置Binder的死亡代理等。在onServiceConnected()方法內,獲取到IBinderPool的代理對象。此外,IBinderPool的實現類:IBinderPoolImpl是BinderPool的內部類,實現了IBinderPool.aidl的方法:queryBinder()。

具體實現能夠參考:實現AIDL接口的Binder鏈接池


參考

爲何 Android 要採用 Binder 做爲 IPC 機制?

Binder源碼詳解

《Android開發藝術探索》


個人CSDN

下面是個人公衆號,歡迎你們關注我

相關文章
相關標籤/搜索