天天看點

Binder 學習筆記前言什麼是Binder?為什麼跨程序通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題

目錄

前言

什麼是Binder?

為什麼跨程序通信要使用binder?

Binder通信模型

從AIDL的角度來了解Binder

AIDL java代碼分析

demo:手寫最簡單的aidl

問題

文章參考:

寫給 Android 應用工程師的 Binder 原理剖析

Binder學習指南

前言

本篇文章主要圍繞下面的幾個點進行Binder學習的記錄。

Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題

什麼是Binder?

一句話概括:Binder是Android中跨程序通信的一種方式。

為什麼跨程序通信要使用binder?

參考:寫給 Android 應用工程師的 Binder 原理剖析

文章主要從性能、穩定性、安全性進行對比總結binder的優勢所在。

性能方面:
  • socket作為一款通用的接口,其傳輸效率低,開銷大。主要用在跨網絡的程序間通信和本機上程序間的低速通信。
  • 消息隊列和管道采用存儲-轉發方式,即資料先從發送方緩存區拷貝到核心開辟的緩存區中,然後再從核心緩存區拷貝到接收方緩存區,至少有兩次拷貝過程。
  • 共享記憶體雖然無需拷貝,但控制複雜,難以使用
  • Binder 隻需要一次資料拷貝,性能上僅次于共享記憶體。
穩定性:
  • Binder 基于 C/S 架構,用戶端(Client)有什麼需求就丢給服務端(Server)去完成,架構清晰、職責明确又互相獨立,自然穩定性更好。
  • 共享記憶體雖然無需拷貝,但是在并發讀寫使它變得極為不可靠。

安全性

傳統的 IPC 接收方無法獲得對方可靠的程序使用者ID/程序ID(UID/PID),進而無法鑒别對方身份。Android 為每個安裝好的 APP 配置設定了自己的 UID,故而程序的 UID 是鑒别程序身份的重要标志。傳統的 IPC 隻能由使用者在資料包中填入 UID/PID,但這樣不可靠,容易被惡意程式利用。可靠的身份辨別隻有由 IPC 機制在核心中添加。其次傳統的 IPC 通路接入點是開放的,隻要知道這些接入點的程式都可以和對端建立連接配接,不管怎樣都無法阻止惡意程式通過猜測接收方位址獲得連接配接。同時 Binder 既支援實名 Binder,又支援匿名 Binder,安全性高。

Binder通信模型

摘自 Binder學習指南

對于跨程序通信的雙方,我們姑且叫做Server程序(簡稱Server),Client程序(簡稱Client);由于程序隔離的存在,它們之間沒辦法通過簡單的方式進行通信,那麼Binder機制是如何進行的呢?

回想一下日常生活中我們通信的過程:假設A和B要進行通信,通信的媒介是打電話(A是Client,B是Server);A要給B打電話,必須知道B的号碼,這個号碼怎麼擷取呢?通信錄.

這個通信錄就是一張表;内容大緻是:

1
2
           
B -> 12345676
C -> 12334354
           

先查閱通信錄,拿到B的号碼;才能進行通信;否則,怎麼知道應該撥什麼号碼?回想一下古老的電話機,如果A要給B打電話,必須先連接配接通話中心,說明給我接通B的電話;這時候通話中心幫他呼叫B;連接配接建立,就完成了通信。

另外,光有電話和通信錄是不可能完成通信的,沒有基站支援;資訊根本無法傳達。

我們看到,一次電話通信的過程除了通信的雙方還有兩個隐藏角色:通信錄和基站。Binder通信機制也是一樣:兩個運作在使用者空間的程序要完成通信,必須借助核心的幫助,這個運作在核心裡面的程式叫做Binder驅動,它的功能類似于基站;通信錄呢,就是一個叫做ServiceManager的東西(簡稱SM)

OK,Binder的通信模型就是這麼簡單,如下圖:

Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題

Binder通信原理

摘自 Binder學習指南

上文給出了Binder的通信模型,指出了通信過程的四個角色: Client, Server, SM, driver; 但是我們仍然不清楚Client到底是如何與Server完成通信的。

兩個運作在使用者空間的程序A和程序B如何完成通信呢?核心可以通路A和B的所有資料;是以,最簡單的方式是通過核心做中轉;假設程序A要給程序B發送資料,那麼就先把A的資料copy到核心空間,然後把核心空間對應的資料copy到B就完成了;使用者空間要操作核心空間,需要通過系統調用;剛好,這裡就有兩個系統調用:

copy_from_user

copy_to_user

但是,Binder機制并不是這麼幹的。講這麼一段,是說明程序間通信并不是什麼神秘的東西。那麼,Binder機制是如何實作跨程序通信的呢?

Binder驅動為我們做了一切。

假設Client程序想要調用Server程序的

object

對象的一個方法

add

;對于這個跨程序通信過程,我們來看看Binder機制是如何做的。 (通信是一個廣泛的概念,隻要一個程序能調用另外一個程序裡面某對象的方法,那麼具體要完成什麼通信内容就很容易了。)
Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題

Alt text

首先,Server程序要向SM注冊;告訴自己是誰,自己有什麼能力;在這個場景就是Server告訴SM,它叫

zhangsan

,它有一個

object

對象,可以執行

add

 操作;于是SM建立了一張表:

zhangsan

這個名字對應程序Server;

然後Client向SM查詢:我需要聯系一個名字叫做

zhangsan

的程序裡面的

object

對象;這時候關鍵來了:程序之間通信的資料都會經過運作在核心空間裡面的驅動,驅動在資料流過的時候做了一點手腳,它并不會給Client程序傳回一個真正的

object

對象,而是傳回一個看起來跟

object

一模一樣的代理對象

objectProxy

,這個

objectProxy

也有一個

add

方法,但是這個

add

方法沒有Server程序裡面

object

對象的

add

方法那個能力;

objectProxy

add

隻是一個傀儡,它唯一做的事情就是把參數包裝然後交給驅動。(這裡我們簡化了SM的流程,見下文)

但是Client程序并不知道驅動傳回給它的對象動過手腳,畢竟僞裝的太像了,如假包換。Client開開心心地拿着

objectProxy

對象然後調用

add

方法;我們說過,這個

add

什麼也不做,直接把參數做一些包裝然後直接轉發給Binder驅動。

驅動收到這個消息,發現是這個

objectProxy

;一查表就明白了:我之前用

objectProxy

替換了

object

發送給Client了,它真正應該要通路的是

object

對象的

add

方法;于是Binder驅動通知Server程序,調用你的object對象的

add

方法,然後把結果發給我,Sever程序收到這個消息,照做之後将結果傳回驅動,驅動然後把結果傳回給

Client

程序;于是整個過程就完成了。

由于驅動傳回的

objectProxy

與Server程序裡面原始的

object

是如此相似,給人感覺好像是直接把Server程序裡面的對象object傳遞到了Client程序;是以,我們可以說Binder對象是可以進行跨程序傳遞的對象

但事實上我們知道,Binder跨程序傳輸并不是真的把一個對象傳輸到了另外一個程序;傳輸過程好像是Binder跨程序穿越的時候,它在一個程序留下了一個真身,在另外一個程序幻化出一個影子(這個影子可以很多個);Client程序的操作其實是對于影子的操作,影子利用Binder驅動最終讓真身完成操作。

了解這一點非常重要;務必仔細體會。另外,Android系統實作這種機制使用的是代理模式, 對于Binder的通路,如果是在同一個程序(不需要跨程序),那麼直接傳回原始的Binder實體;如果在不同程序,那麼就給他一個代理對象(影子);我們在系統源碼以及AIDL的生成代碼裡面可以看到很多這種實作。

另外我們為了簡化整個流程,隐藏了SM這一部分驅動進行的操作;實際上,由于SM與Server通常不在一個程序,Server程序向SM注冊的過程也是跨程序通信,驅動也會對這個過程進行暗箱操作:SM中存在的Server端的對象實際上也是代理對象,後面Client向SM查詢的時候,驅動會給Client傳回另外一個代理對象。Sever程序的本地對象僅有一個,其他程序所擁有的全部都是它的代理。

一句話總結就是:Client程序隻不過是持有了Server端的代理;代理對象協助驅動完成了跨程序通信。

從AIDL的角度來了解Binder

binder學習指南中舉了定義了一個簡單的aidl接口ICompute

// ICompute.aidl
package com.example.test.app;
interface ICompute {
     int add(int a, int b);
}           

經過編譯生成的java代碼類結構如下:

Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題

Binder/IBinder: 它代表了一種跨程序傳輸的能力;隻要實作了這個接口,就能将這個對象進行跨程序傳遞。也可以将binder了解成一種協定,類似http在 用戶端 發起http請求,服務端接收到請求封包,做對應的處理将結果傳回給用戶端。在跨程序通信的過程中binder就是網絡通信中的http

IInterface: IInterface代表的就是遠端server對象具有什麼能力,具體來說,就是aidl裡面的接口。同時指導如何擷取跨程序傳輸的binder。

這樣來看的話,Stub繼承Binder實作了IInterface接口,既可以進行跨程序傳輸,同時可以直接和遠端進行互動。

Proxy:實作了IInterface接口通過它可以擷取到跨程序傳輸的binder

AIDL java代碼分析

aidl生成的java代碼如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.example.txl.tool;
// Declare any non-default types here with import statements

public interface ICompute extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.example.txl.tool.ICompute {
        private static final java.lang.String DESCRIPTOR = "com.example.txl.tool.ICompute";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface( this, DESCRIPTOR );
        }

        /**
         * Cast an IBinder object into an com.example.txl.tool.ICompute interface,
         * generating a proxy if needed.
         */
        public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
            if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
                return ((com.example.txl.tool.ICompute) iin);
            }
            return new com.example.txl.tool.ICompute.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 {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString( descriptor );
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface( descriptor );
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add( _arg0, _arg1 );
                    reply.writeNoException();
                    reply.writeInt( _result );
                    return true;
                }
                default: {
                    return super.onTransact( code, data, reply, flags );
                }
            }
        }

        private static class Proxy implements com.example.txl.tool.ICompute {
            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 int add(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken( DESCRIPTOR );
                    _data.writeInt( a );
                    _data.writeInt( b );
                    mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int add(int a, int b) throws android.os.RemoteException;
}
           

這裡的java代碼符合前面對Binder和IInterface的描述。

Server端實作的時候繼承Stub類,它能夠進行跨程序傳輸供Client端調用。

Client端調用Stub的靜态 方法asInterface擷取一個具有服務端外觀(和服務端有同樣的方法)的ICompute對象。

其實asInterface方法和Stub方法沒有什麼太大的關系。它的代碼如下:

public static com.example.txl.tool.ICompute asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
            if (((iin != null) && (iin instanceof com.example.txl.tool.ICompute))) {
                return ((com.example.txl.tool.ICompute) iin);
            }
            return new com.example.txl.tool.ICompute.Stub.Proxy( obj );
        }           

邏輯是:先從本地去檢視是否有符合需求的binder對象,如果有直接傳回本地對象。如果沒有生成一個Proxy對象傳回。

對于用戶端而言這個Proxy類與其說是一個代理對象倒不如說是它在指導Binder如何進行資料處理。我們看看它的add方法

@Override
public int add(int a, int b) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken( DESCRIPTOR );
        _data.writeInt( a );
        _data.writeInt( b );
        //除了這句代碼,其他的全部在進行資料處理
        mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}           

可以看到當調用add方法的時候除了  mRemote.transact( Stub.TRANSACTION_add, _data, _reply, 0 );其他的都在進行資料處理。而transact就像是将指定資料同步發送到服務端然後等待響應。這也是前面我為什麼将Binder類比成http。

用戶端 transact方法接收4個參數,他們分别是

  1. 要調用方法的編碼
  2. 要發送給服務端參數的容器
  3. 要傳回資料的容器
  4. 标記falg(暫時不知道這個參數的作用)

服務端onTransact同樣的接收4個參數,他們分别與用戶端的4個參數對應。

這樣一次aidl跨程序通信就分析完了。做一個簡單的總結:

服務端:實作繼承Binder并實作IIterface,重寫onTransact方法處理用戶端的回調。

用戶端:同過綁定服務時得到的Ibinder對象,通過transact進行對應的資料傳輸。

demo:手寫最簡單的aidl

服務端實作:

package com.txl.calculationserver;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Log;

public class CalculationService extends Service {
    private static final String TAG = "CalculationService";
    Binder binder = new CalculationBinder();
    public CalculationService() {
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d( TAG,"onCreate" );
    }

    private static class CalculationBinder extends Binder implements IInterface {
        private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
        static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;

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

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

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString( descriptor );
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface( descriptor );
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add( _arg0, _arg1 );
                    reply.writeNoException();
                    reply.writeInt( _result );
                    return true;
                }
                default: {
                    return super.onTransact( code, data, reply, flags );
                }
            }
        }

        public int add(int a, int b) throws RemoteException {
            Log.d( TAG,"call add method by client" );
            return a+b;
        }
    }
}
           

用戶端實作:

public class MainActivity extends AppCompatActivity {
    private final String TAG = "MainActivity";

    private ProxyCalculationServer proxyCalculationServer;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            proxyCalculationServer = ProxyCalculationServer.asInterface( iBinder );
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main );
        findViewById( R.id.tv_call_remote_add ).setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(proxyCalculationServer != null){
                    try {
                        int result = proxyCalculationServer.add( 3,6 );
                        Log.d( TAG,"result = "+result );
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }else {
                    Log.e( TAG,"proxyCalculationServer is null" );
                }
            }
        } );
        Intent intent = new Intent(  );
        intent.setAction( "com.txl.calculationserver" );
        intent.setPackage( "com.txl.calculationserver" );
        bindService( intent,connection,BIND_AUTO_CREATE );
    }

    static class ProxyCalculationServer implements IInterface {
        private static final java.lang.String DESCRIPTOR = "com.txl.calculationserver.CalculationService.CalculationBinder";
        static final int TRANSACTION_add = IBinder.FIRST_CALL_TRANSACTION ;
        private android.os.IBinder mRemote;
        public ProxyCalculationServer(IBinder obj) {
            mRemote = obj;
        }
//        @Override
//        public android.os.IBinder asBinder() {
//            return mRemote;
//        }

        public static ProxyCalculationServer asInterface(IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR );
            if (((iin != null) && (iin instanceof ProxyCalculationServer))) {
                return ((ProxyCalculationServer) iin);
            }
            return new ProxyCalculationServer( obj );
        }

        public int add(int a, int b) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            int _result;
            try {
                _data.writeInterfaceToken( DESCRIPTOR );
                _data.writeInt( a );
                _data.writeInt( b );
                mRemote.transact( TRANSACTION_add, _data, _reply, 0 );
                _reply.readException();
                _result = _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

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

這個兩個檔案分别實作在不同的項目中。對于簡單的xml沒有貼上代碼。

最後服務端和用戶端的運作結果如下

Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題
Binder 學習筆記前言什麼是Binder?為什麼跨程式通信要使用binder?Binder通信模型從AIDL的角度來了解Binderdemo:手寫最簡單的aidl問題

通過上面的案例可以看到,對于服務端而言不是一定要擷取一個Proxy對象。隻要你懂得binder傳輸過程中的資料處理,甚至完全可以直接自己調用transact。

總結:

aidl實作跨程序通信不是必須要先編寫 .aidl 檔案完全可以按照前面總結aidl需要完成的東西來進行編寫。但是aidl檔案幫助我們更加簡單的實作款程序通信的過程,也不容易出錯。建議大家在實作aidl的時候不要手寫,手寫實作過程的目的是為了更好的了解這一過程。

問題

  1.  服務端什麼時候被注冊到ServerManager?
  2. IInterface接口的asBinder方法是幹什麼的?為什麼傳回null也能夠正常調用?