Android 內核--Binder架構分析


一、Binder架構

  在Android中,Binder用於完成進程間通信(IPC),即把多個進程關聯在一起。比如,普通應用程序可以調用音樂播放服務提供的播放、暫停、停止等功能。
Binder工作在Linux層面,屬於一個驅動,只是這個驅動不需要硬件,或者說其操作的硬件是基於一小段內存。從線程的角度來講,Binder驅動代碼運行在內核態,客戶端程序調用Binder是通過系統調用完成的。

  Binder是一種架構,這種架構提供了服務端接口、Binder驅動、客戶端接口三個模塊。
  服務端:一個Binder服務端實際上就是一個Binder類的對象,該對象一旦創建,內部就啟動一個隱藏線程。該線程接下來會接收Binder驅動發送的消息,收到消息后,會執行到Binder對象中的onTransact()函數,並按照該函數的參數執行不同的服務代碼。因此,要實現一個Binder服務,就必須重載onTransact()方法。重載onTransact()函數的主要內容是把onTransact()函數的參數轉換為服務函數的參數,而onTransact()函數的參數來源是客戶端調用transact()函數時輸入的,因此,如果transact()有固定格式的輸入,那么onTransact()就會有固定格式的輸出。
  Binder驅動:任意一個服務端Binder對象被創建時,同時會在Binder驅動中創建一個mRemote對象,該對象的類型也是Binder類。客戶端要訪問遠程服務時,都是通過mRemote對象。
  客戶端:客戶端要想訪問遠程服務,必須獲取遠程服務在Binder對象中對應的mRemote引用,至於如何獲取,下面將會介紹。獲得該mRemote對象后,就可以調用其transact()方法,而在Binder驅動中,mRemote對象也重載了transact()方法,重載的內容主要包括以下幾項。

  •  以線程間消息通信的模式,向服務端發送客戶端傳遞過來的參數。
  •  掛起當前線程,當前線程正是客戶端線程,並等待服務端線程執行完指定服務函數后通知(notify)。
  •  接收到服務端線程的通知,然后繼續執行客戶端線程,並返回到客戶端代碼區。


從這里可以看出,對應用程序開發員來講,客戶端似乎是直接調用遠程服務對應的Binder,而實際上是通過Binder驅動進行了中轉。即存在兩個Binder對象,一個是服務端的Binder對象,另一個則是Binder驅動中的Binder對象,不同的是Binder驅動中的對象不會再額外產生一個線程。

二、Service端

設計Service端很簡單,從代碼的角度來講,只要基於Binder類新建一個Servier類即可。以下以設計一個Service類為例。

    class MyService extends Binder {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply,
                int flags) throws RemoteException { // 接收客戶端傳過來的消息參數 return super.onTransact(code, data, reply, flags);
        }
        public void start(String name) {
        }
        public void stop() {
        }
    }

當要啟動該服務時,只需要初始化一個MyService對象即可。之后可以在DDMS中看到多了一個線程

  定義了服務類后,接下來需要重載onTrasact()方法,並從data變量中讀出客戶端傳遞的參數,比如start()方法所需要的name變量。然而,服務端如何知道這個參數在data變量中的位置?因此,這就需要調用者和服務者雙方有個約定。假定客戶端在傳入的包裹data中放入的第一個數據就是filePath變量,則onTransact()的代碼可以如下所示:

            switch (code) {
            case 0x100:
                data.enforceInterface("MyService");
                String name = data.readString();
                start(name);
                break;
            }

code變量用於標識客戶端期望調用服務端的哪個函數,因此,雙方需要約定一組int值,不同的值代表不同的服務端函數,該值和客戶端的transact()函數中第一個參數code的值是一致的。這里假定0x100是雙方約定要調用start()函數的值。
enforceInterface()是為了某種校驗,它與客戶端的writeInterfaceToken()對應,具體見下一小節。
readString()用於從包裹中取出一個字符串。取出filePath變量后,就可以調用服務端的start()函數了。如果該IPC調用的客戶端期望返回一些結果,則可以在返回包裹reply中調用Parcel提供的相關函數寫入相應的結果。Parcel.writeXXX();

三、Binder客戶端設計

  要想使用服務端,首先要獲取服務端在Binder驅動中對應的mRemote變量的引用,獲取的方法后面將介紹。獲得該變量的引用后,就可以調用該變量的transact()方法。該方法的函數原型:
  public final boolean transact(int code, Parcel data, Parcel reply,int flags)
  其中data表示的是要傳遞給遠程Binder服務的包裹(Parcel),遠程服務函數所需要的參數必須放入這個包裹中。包裹中只能放入特定類型的變量,這些類型包括常用的原子類型,比如String、int、long等,要查看包裹可以放入的全部數據類型,可以參照Parcel類。除了一般的原子變量外,Parcel還提供了一個writeParcel()方法,可以在包裹中包含一個小包裹。因此,要進行Binder遠程服務調用時,服務函數的參數要么是一個原子類,要么必須繼承於Parcel類,否則,是不能傳遞的。

        IBinder mRemote = null;
        String name = "Livingstone";
        int code = 0x100;
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken("MyService");
        data.writeString(name);
        mRemote.transact(code, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();

  現在來分析以上代碼。首先,包裹不是客戶端自己創建的,而是調用Parcel.obtain()申請的,這正如生活中的郵局一樣,用戶一般只能用郵局提供的信封(尤其是EMS)。其中data和reply變量都由客戶端提供,reply變量用戶服務端把返回的結果放入其中。
  writeInterfaceToken()方法標注遠程服務名稱,理論上講,這個名稱不是必需的,因為客戶端既然已經獲取指定遠程服務的Binder引用,那么就不會調用到其他遠程服務。該名稱將作為Binder驅動確保客戶端的確想調用指定的服務端。
  writeString()方法用於向包裹中添加一個String變量。注意,包裹中添加的內容是有序的,這個順序必須是客戶端和服務端事先約定好的,在服務端的onTransact()方法中會按照約定的順序取出變量。

  接着調用transact()方法。調用該方法后,客戶端線程進入Binder驅動,Binder驅動就會掛起當前線程,並向遠程服務發送一個消息,消息中包含了客戶端傳進來的包裹。服務端拿到包裹后,會對包裹進行拆解,然后執行指定的服務函數,執行完畢后,再把執行結果放入客戶端提供的reply包裹中。然后服務端向Binder驅動發送一個notify的消息,從而使得客戶端線程從Binder驅動代碼區返回到客戶端代碼區。
  transact()的最后一個參數的含義是執行IPC調用的模式,分為兩種:一種是雙向,用常量0表示,其含義是服務端執行完指定服務后會返回一定的數據;另一種是單向,用常量1表示,其含義是不返回任何數據。
  最后,客戶端就可以從reply中解析返回的數據了,同樣,返回包裹中包含的數據也必須是有序的,而且這個順序也必須是服務端和客戶端事先約定好的。

四、使用Service類

以上手工編寫Binder服務端和客戶端的過程存在兩個重要問題。
  第一,客戶端如何獲得服務端的Binder對象引用。
  第二,客戶端和服務端必須事先約定好兩件事情。
   服務端函數的參數在包裹中的順序。
   服務端不同函數的int型標識。
關於第一個問題,為什么要用Binder。答案很簡單,即為了提供一個全局服務,所謂的“全局”,是指系統中的任何應用程序都可以訪問。很明顯,這是一個操作系統應該提供的最基本的功能之一,Android的工程師自然也是這么認為的,因此,他們提供了一個更傻瓜的解決方法,那就是Service。它是Android應用程序四個基本程序片段(Component)之一,四個基本片段包括Activity、Service、Content Provier、Receiver。
無論是否使用Service類,都必須要解決以上兩個重要問題。

1、獲取Binder對象

事實上,對於有創造力的程序員來講,可以完全不使用Service類,而僅僅基於Binder類編寫服務程序,但只是一部分。具體來講,可以僅使用Binder類擴展系統服務,而對於客戶端服務則必須基於Service類來編寫。所謂的系統服務是指可以使用getSystemService()方法獲取的服務,所謂的客戶端服務是指應用程序提供的自定義服務。
那么,Service類是如何解決本節開頭所提出的兩個重要問題的呢?
首先,AmS提供了startService()函數用於啟動客戶服務,而對於客戶端來講,可以使用以下兩個函數來和一個服務建立連接,其原型在android.app. ContextImpl類中。

public ComponentName startService(Intent intent);
該函數用於啟動intent指定的服務,而啟動后,客戶端暫時還沒有服務端的Binder引用,因此,暫時還不能調用任何服務功能。

    @Override
    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
        IServiceConnection sd;
        if (mPackageInfo != null) {
            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                    mMainThread.getHandler(), flags);
        } 
        // ......int res = ActivityManagerNative.getDefault().bindService(
                mMainThread.getApplicationThread(), getActivityToken(),
                service, service.resolveTypeIfNeeded(getContentResolver()),
                sd, flags);
    }

該函數用於綁定一個服務,這就是第一個重要問題的關鍵所在。其中第二個參數是一個interface類,該interface的定義如以下代碼所示:

public interface ServiceConnection {
  public void onServiceConnected(ComponentName name, IBinder service);
  public void onServiceDisconnected(ComponentName name);
}

  此interface中的onServiceConnected()方法的第二個變量Service。當客戶端請求AmS啟動某個Service后, 該Service如果正常啟動,那么AmS就會遠程調用ActivityThread類中的ApplicationThread對象,調用的參數中會包含Service的Binder引用,然后在ApplicationThread中會回調bindService中的conn接口。因此,在客戶端中,可以在onServiceConnected()方法中將其參數Service保存為一個全局變量,從而在客戶端的任何地方都可以隨時調用該遠程服務。這就解決了第一個重要問題,即客戶端如何獲取遠程服務的Binder引用。


下面查看源碼

        public final void scheduleBindService(IBinder token, Intent intent, boolean rebind) {
            BindServiceData s = new BindServiceData();
            s.token = token;
            s.intent = intent;
            s.rebind = rebind;
            queueOrSendMessage(H.BIND_SERVICE, s);
        }
    // if the thread hasn't started yet, we don't have the handler, so just
    // save the messages until we're ready.
    private final void queueOrSendMessage(int what, Object obj) {
        queueOrSendMessage(what, obj, 0, 0);
    }
    private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
        synchronized (this) {
            // ...
            Message msg = Message.obtain();
            msg.what = what;
            msg.obj = obj;
            msg.arg1 = arg1;
            msg.arg2 = arg2;
            mH.sendMessage(msg); 
        }
    }

2、AIDL工具對象使用

關於第二個問題,Android的SDK中提供了一個aidl工具,該工具可以把一個aidl文件轉換為一個Java類文件,在該Java類文件中,同時重載了transact和onTransact()方法,統一了存入包裹和讀取包裹參數,從而使設計者可以把注意力放到服務代碼本身上。aidl工具不是必需的,對於有經驗的程序員來講,手工編寫一個參數統一的包裹存入和包裹讀出代碼並不是一件復雜的事情。
下面是一個服務對應的AIDL文件,服務中包含兩個服務函數,分別是start()和stop()。那么,可以首先編寫一個IMyService.aidl文件。如以下代碼所示:

package com.androidstudy;
interface IMyService{
boolean start(String path);
void stop();
}

該文件的名稱必須遵循一定的規范,第一個字母“I”不是必需的,但是,為了程序風格的統一,“I”的含義是IInterface類,即這是一個可以提供訪問遠程服務的類。服務的類名可以是任意的,但是,aidl工具會以該名稱命名輸出Java類。這些規則都只是Eclipse下ADT插件的默認規則,aidl本身只是一個命令行程序,借助命令行的話,則可以靈活指定輸出文件的名稱及位置。

aidl文件的語法基本類似於Java,package指定輸出后的Java文件對應的包名。如果該文件需要引用其他Java類,則可以使用import關鍵字,但需要注意的是,包裹內只能寫入以下三個類型的內容。

  •  Java原子類型,如int、long、String等變量。
  •  Binder引用。
  •  實現了Parcelable的對象。

因此,基本上來講,import所引用的Java類也只能是以上三個類型。
interface為關鍵字,有時會在interface前面加一個oneway,代表該service提供的方法都是沒有返回值的,即都是void類型。

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

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

            /**
             * Cast an IBinder object into an com.androidstudy.IMyService
             * interface, generating a proxy if needed.
             */
            public static com.androidstudy.IMyService asInterface(
                    android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.androidstudy.IMyService))) {
                    return ((com.androidstudy.IMyService) iin);
                }
                return new com.androidstudy.IMyService.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 {
                switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_start: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    boolean _result = this.start(_arg0);
                    reply.writeNoException();
                    reply.writeInt(((_result) ? (1) : (0)));
                    return true;
                }
                case TRANSACTION_stop: {
                    data.enforceInterface(DESCRIPTOR);
                    this.stop();
                    reply.writeNoException();
                    return true;
                }
                }
                return super.onTransact(code, data, reply, flags);
            }

            private static class Proxy implements com.androidstudy.IMyService {
                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 boolean start(java.lang.String path)
                        throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    boolean _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeString(path);
                        mRemote.transact(Stub.TRANSACTION_start, _data, _reply,
                                0);
                        _reply.readException();
                        _result = (0 != _reply.readInt());
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }

                @Override
                public void stop() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_stop, _data, _reply,
                                0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
            }

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

        public boolean start(java.lang.String path)
                throws android.os.RemoteException;

        public void stop() throws android.os.RemoteException;
    }

這些代碼主要完成以下三個任務。

  •  定義一個Java interface,內部包含aidl文件所聲明的服務函數,類名稱為IMyService,並且該類基於IInterface接口,即需要提供一個asBinder()函數。
  •  定義一個Proxy類,該類將作為客戶端程序訪問服務端的代理。所謂的代理主要就是為了前面所提到的第二個重要問題——統一包裹內寫入參數的順序。
  •  定義一個Stub類,這是一個abstract類,基於Binder類,並且實現了IMyService接口,主要由服務端來使用。該類之所以要定義為一個abstract類,是因為具體的服務函數必須由程序員實現,因此,IMyService接口中定義的函數在Stub類中可以沒有具體實現。同時,在Stub類中重載了onTransact()方法,由於transact()方法內部給包裹內寫入參數的順序是由aidl工具定義的,因此,在onTransact()方法中,aidl工具自然知道應該按照何種順序從包裹中取出相應參數。

  在Stub類中還定義了一些int常量,比如TRANSACTION_start,這些常量與服務函數對應, transact()和onTransact()方法的第一個參數code的值即來源於此。
  在Stub類中,除了以上所述的任務外,Stub還提供了一個asInterface()函數。提供這個函數的作用是這樣的:首先需要明確的是,aidl所產生的代碼完全可以由應用程序員手工編寫,IMyService中的函數只是一種編碼習慣而已,asInterface即如此,提供這個函數的原因是服務端提供的服務除了其他進程可以使用外,在服務進程內部的其他類也可以使用該服務,對於后者,顯然是不需要經過IPC調用,而可以直接在進程內部調用的,而Binder內部有一個queryLocalInterface(String description)函數,該函數是根據輸入的字符串判斷該Binder對象是一個本地的Binder引用。在第一張圖中曾經指出,當創建一個Binder對象時,服務端進程內部創建一個Binder對象,Binder驅動中也會創建一個Binder對象。如果從遠程獲取服務端的Binder,則只會返回Binder驅動中的Binder對象,而如果從服務端進程內部獲取Binder對象,則會獲取服務端本身的Binder對象。

因此,asInterface()函數正是利用了queryLocalInterface()方法,提供了一個統一的接口。無論是遠程客戶端還是本地端,當獲取Binder對象后,可以把獲取的Binder對象作為asInterface()的參數,從而返回一個IMyService 接口,該接口要么使用Proxy類,要么直接使用Stub所實現的相應服務函數。

五、系統服務中的Binder對象

  在應用程序中,經常使用getSystemService(String serviceName)方法獲取一個系統服務,那么,這些系統服務的Binder引用是如何傳遞給客戶端的呢?須知系統服務並不是通過startService()啟動的。getSystemService()函數的實現是在ContextImpl類中,該函數所返回的Service比較多,具體可參照源碼。這些Service一般都由ServiceManager管理。
1、ServiceManger管理的服務
ServiceManager是一個獨立進程,其作用如名稱所示,管理各種系統服務,管理的邏輯如下

  ServiceManager本身也是一個Service,Framework提供了一個系統函數,可以獲取該Service對應的Binder引用,那就是BinderInternal.getContextObject()。該靜態函數返回ServiceManager后,就可以通過ServiceManager提供的方法獲取其他系統Service的Binder引用。這種設計模式在日常生活中到處可見,ServiceManager就像是一個公司的總機,這個總機號碼是公開的,系統中任何進程都可以使用BinderInternal.getContextObject()獲取該總機的Binder對象,而當用戶想聯系公司中的其他人(服務)時,則要經過總機再獲得分機號碼。這種設計的好處是系統中僅暴露一個全局Binder引用,那就是ServiceManager,而其他系統服務則可以隱藏起來,從而有助於系統服務的擴展,以及調用系統服務的安全檢查。其他系統服務在啟動時,首先把自己的Binder對象傳遞給ServiceManager,即所謂的注冊(addService)。
  下面查看獲取一個Service{IMPUT_METHOD_SERVICE}

        if (INPUT_METHOD_SERVICE.equals(name)) {
            return InputMethodManager.getInstance(this);
    static public InputMethodManager getInstance(Looper mainLooper) {
        synchronized (mInstanceSync) {
            if (mInstance != null) {
                return mInstance;
            }
            IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
            IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
            mInstance = new InputMethodManager(service, mainLooper);
        }
        return mInstance;
    }

  即通過ServiceManager獲取InputMethod Service對應的Binder對象b,然后再將該Binder對象作為IInputMethodManager.Stub.asInterface()的參數,返回一個IInputMethodManager的統一接口。
  ServiceManager.getService()的代碼如下

    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

即首先從sCache 緩存中查看是否有對應的Binder 對象,有則返回,沒有則調用getIServiceManager().getService(name),函數getIServiceManager()即用於返回系統中唯一的ServiceManager對應的Binder,其代碼如下:

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }
        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

  BinderInternal.getContextObject()靜態函數即用於返回ServiceManager對應的全局Binder對象,該函數不需要任何參數,因為它的作用是固定的。其他所有通過ServiceManager獲取的系統服務的過程與以上基本類似,所不同的就是傳遞給ServiceManager的服務名稱不同,因為ServiceManager正是按照服務的名稱(String類型)來保存不同的Binder對象的。
  使用addService()向ServiceManager中添加一個服務一般是在SystemService進程啟動時完成的。

2、理解Manger

  ServiceManager所管理的所有Service都是以相應的Manager返回給客戶端,因此,這里簡述一下Framework中關於Manager的語義。在我們中國的企業里,Manager一般指經理,比如項目經理、人事經理、部門經理。經理本身的含義比較模糊,其角色有些是給我們分配任務,比如項目經理;有些是給我們提供某種服務,比如人事經理;有些則是監督我們的工作等。而在Android中,Manager的含義更應該翻譯為經紀人,Manager所manage的對象是服務本身,因為每個具體的服務一般都會提供多個API接口 ,而Manager所manage的正是這些API。客戶端一般不能直接通過Binder引用去訪問具體的服務,而是要經過一個Manager,相應 的Manager類對客戶端是可見的,而遠程的服務類對客戶端則是隱藏的。而這些Manager的類內部都會有一個遠程服務Binder的變量,而且在一般情況下,這些Manager的構造函數參數中會包含這個Binder對象。簡單地講,即先通過ServiceManager獲取遠程服務的Binder引用,然后使用這個Binder引用構造一個客戶端本地可以訪問的經紀人,然后客戶端就可以通過該經紀人訪問遠程的服務。這種設計的作用是屏蔽直接訪問遠程服務,從而可以給應用程序提供靈活的、可控的API接口,比如AmS。系統不希望用戶直接去訪問AmS,而是經過ActivityManager類去訪問,而ActivityManager內部提供了一些更具可操作性的數據結構,比如RecentTaskInfo數據類封裝了最近訪問過的Task列表,MemoryInfo數據類封裝了和內存相關的信息。

通過本地Manger訪問遠程服務的模型圖如下





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM