歡迎光臨
每天分享高質量文章

關於Binder,作為應用開發者你需要知道的全部

作者:rushjs

來自:https://www.jianshu.com/p/062a6e4f5cbe


github 地址:https://github.com/rushgit/zhongwenjun.github.com

csdn:https://blog.csdn.net/zwjemperor

為什麼要理解Binder?

一般Android應用開發很少直接用到跨行程信通訊(IPC),但如果你想知道:

  • App是如何啟動並初始化的?

  • Activity的啟動過程是怎樣的?

  • 行程間是如何通訊的?

  • AIDL的具體原理是什麼?

  • 眾多外掛化框架的設計原理 等等

就必須對Binder有所瞭解,無論是四大元件,還是各種系統Service,比如ActivityManagerService、PackageManagerService,它們的實現都依賴Binder的通訊機制,可見Binder在Android系統中的重要性,可以說Binder是邁入高階工程師的第一步。

Binder機制很複雜,想要徹底弄懂比較難,除了需要瞭解作業系統中的各種知識外,還需要看懂Binder驅動層的程式碼實現。最近看了很多關於Binder的文章,大部分過於抽象或者過於深入原始碼細節,真正淺顯易懂的文章很少。個人認為最有參考價值的是以下三篇:(其他參考文章附在文末)

  • Android Binder設計與實現 – 設計篇

  • Binder學習指南

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

這篇文章主要從宏觀的層面去理解Binder中的各種概念和基本通訊過程,只關註Java層的實現,底層實現不做介紹。對於應用開發者而言,理解Binder的基本設計原理和通訊過程已經夠了,想要深入理解Binder需要自行閱讀原始碼。

本文主要從三個方面來做分析:

1、為什麼是Binder?

  • 傳統Linux IPC機制的缺點

  • Linux的一些基本知識

  • 傳統Linux IPC機制的通訊原理


2、Binder的基本原理

  • Binder的底層原理

  • Binder的通訊模型

  • Binder的代理機制

  • 對Binder概念的重新理解


3、透過程式碼來理解Binder

  • 透過AIDL實體來瞭解Binder的用法

  • 透過手動編碼實現ActivityManagerService


1、為什麼是Binder?

1.1 傳統IPC機制的缺點

大家都知道Android系統是基於Linux核心實現的,Linux已經提供了多種行程間通訊機制,比如:管道、訊息佇列、共享記憶體和套接字(Socket)等等,為什麼還要再實現一套IPC機制呢?主要是基於兩方面的原因:

1.1.1 效能角度

管道、訊息佇列、Socket實現一次行程通訊都需要2次記憶體複製,效率太低;共享記憶體雖然不需要複製記憶體,但管理複雜;Binder只需要一次記憶體複製,從效能角度來看,低於共享記憶體方式,優於其它方式。

IPC機制 資料複製次數
共享記憶體 0
Binder 1
管道、訊息佇列、Socket 2
1.1.2 安全性考慮

傳統的IPC機制沒有安全措施,接收方無法獲得對方可靠的行程ID或使用者ID,完全靠上層的協議來保護,比如Socket通訊的IP地址是客戶端填入的,很可能被惡意程式篡改。Android作為面向終端使用者的開源平臺,應用市場中有海量的應用供使用者選擇,因此安全性極為重要。Android系統為每個已安裝的App都分配了使用者ID(UID),UID是鑒別行程身份的重要標識,透過UID可以進行一系列的許可權校驗。另一方面 ,傳統IPC的接入點是開放的,任何程式都可以根據協議進行訪問,無法阻止惡意程式的訪問,Android需要一種基於C/S架構的IPC機制,Server端需要能夠對Client的請求進行身份校驗,來保證資料的安全性。

1.2 Linux的一些基本知識

要知道Binder是如何只用一次記憶體複製即實現跨行程通訊的,首先需要弄清楚為什麼傳統IPC機製為什麼需要兩次記憶體複製,這就需要先瞭解一些作業系統的基礎知識。

1.2.1 行程隔離

先來看一下維基百科對“行程隔離”的定義:

行程隔離是為保護作業系統中行程互不幹擾而設計的一組不同硬體和軟體的技術。這個技術是為了避免行程A寫入行程B的情況發生。 行程的隔離實現,使用了虛擬地址空間。行程A的虛擬地址和行程B的虛擬地址不同,這樣就防止行程A將資料資訊寫入行程B。

也就是說,行程之間的資料是不共享的,A行程無法直接訪問B行程的資料,以此來保證資料的安全性。在行程隔離的作業系統中,行程之間的互動必須透過IPC機制。

行程隔離的實現使用了虛擬地址空間,什麼是虛擬地址空間呢?首先需要瞭解作業系統中的虛擬記憶體概念,它是一種提高程式設計效率和提高物理記憶體利用效率的一種技術。簡單來說,就是應用程式看到了都一片連續完整的記憶體地址空間,而實際上這些地壇空間是對映到碎片化的物理記憶體中的,這個對映的過程對應用程式來說是透明的。這個概念很重要,對於虛擬記憶體更深入的理解可以參考這篇文章:Linux 虛擬記憶體和物理記憶體的理解

1.2.2 行程空間:使用者空間/核心空間

現在的作業系統都採用虛擬記憶體,對32位的作業系統而言,定址空間是2的32次方,即4G。作業系統的核心是核心,核心擁有對底層裝置的所有訪問許可權,因此需要和普通的應用程式獨立開來,使用者行程不能直接訪問核心行程。作業系統從邏輯上把虛擬地址空間劃分為使用者空間(User Space)和核心空間(Kernel Space)。在32位的Linux作業系統中,將高位的1GB位元組供核心使用,稱之為核心空間;剩下的3GB位元組供使用者行程使用,稱之為使用者空間。

1.2.3 系統呼叫:使用者態/核心態

因為使用者空間的許可權低於核心空間,不可避免使用者空間需要訪問核心空間的資源,比如讀寫檔案和網路訪問,如何實現呢?唯一的方式就是透過作業系統提供的系統呼叫介面,透過系統呼叫介面,使用者程式可以在內核的控制下實現對核心資源的有限訪問,這樣既能滿足應用程式的資源請求,也能保障系統安全和穩定。

當使用者行程執行自己的程式碼時,行程當前就處於使用者執行態(使用者態),此時處理器執行使用者程式碼,許可權較低;當使用者行程透過系統呼叫執行核心程式碼時,行程就暫時進入了核心執行態(核心態),此時處理器許可權最高,可以執行特權指令。

1.2.4 核心模組/驅動

前面說了使用者空間可以透過系統呼叫訪問核心空間,那使用者空間之間(行程間)怎麼通訊呢?傳統的IPC機制都是透過核心來支援的,Binder也一樣,有一個執行在核心中的Binder驅動程式負責行程之間Binder的通訊。

驅動程式一般指的是裝置驅動程式(Device Driver),是一種可以使計算機和裝置通訊的特殊程式。相當於硬體的介面,作業系統只有透過這個介面。

Binder驅動是一種虛擬的字元裝置,註冊在/dev/binder中,其定義了一套Binder通訊協議,負責建立行程間的Binder通訊,提供了資料包在行程之間傳遞的一系列底層支援。應用行程訪問Binder驅動也是透過系統呼叫實現的。

1.3 傳統IPC機制的通訊原理

瞭解了上面的基礎知識後,我們來看看傳統IPC機制是如何實現,通常是下麵兩個步驟(共享記憶體機制除外):

1、傳送方行程透過系統呼叫(copy_from_user)將要傳送的資料存複製到核心快取區中。

2、接收方開闢一段記憶體空間,核心透過系統呼叫(copy_to_user)將核心快取區中的資料複製到接收方的記憶體快取區。


這種傳統IPC機制存在2個問題:

1、需要進行2次資料複製,第1次是從傳送方使用者空間複製到核心快取區,第2次是從核心快取區複製到接收方使用者空間。

2、接收方行程不知道事先要分配多大的空間來接收資料,可能存在空間上的浪費。

2、Binder的基本原理

2.1 Binder底層原理

傳統IPC機制需要複製2次記憶體,Binder是如何只用1次記憶體複製就實現行程間通訊的呢?前面我們已經瞭解到,Linux是使用的是虛擬記憶體定址方式,使用者空間的虛擬記憶體地址是對映到物理記憶體中的,對虛擬記憶體的讀寫實際上是對物理記憶體的讀寫,這個過程就是記憶體對映,這個記憶體對映過程是透過系統呼叫mmap()來實現的。

Binder藉助了記憶體對映的方法,在核心空間和接收方使用者空間的資料快取區之間做了一層記憶體對映。這樣一來,從傳送方使用者空間複製到核心空間快取區的資料,就相當於直接複製到了接收方使用者空間的資料快取區,從而減少了一次資料複製。

2.2 Binder通訊模型

Binder是基於C/S架構的,對於通訊雙方來說,發起請求的行程屬於Client,接收請求的行程屬於Server,由於存在行程隔離,雙方不能直接通訊,Binder是如何實現的呢?

寫給 Android 應用工程師的 Binder 原理剖析中舉的網路通訊例子很貼切,Binder的通訊過程與網路請求類似,網路通訊過程可以簡化為4個角色:Client、Server、DNS伺服器和路由器。一次完整的網路通訊大體過程如下:

1、Client輸入Server的域名


2、DNS解析域名

透過域名是無法直接找到相應Server的,必須先透過DNS伺服器將Server的域名轉化為具體的IP地址。


3、透過路由器將請求傳送至Server

Client透過DNS伺服器解析到Server的IP地址後,也還不能直接向Server發起請求,需要經過路由器的層層中轉才還到達Server。


4、Server傳回資料

Server接收到請求並處理後,再透過路由器將資料傳回給Client。

在Binder機制中,也定義了4個角色:Client、Server、Binder驅動和ServiceManager

  • Binder驅動:類似網路通訊中的路由器,負責將Client的請求轉發到具體的Server中執行,並將Server傳回的資料傳回給Client。

  • ServiceManager:類似網路通訊中的DNS伺服器,負責將Client請求的Binder描述符轉化為具體的Server地址,以便Binder驅動能夠轉發給具體的Server。Server如需提供Binder服務,需要向ServiceManager註冊。

具體的通訊過程是這樣的:

1、Server向ServiceManager註冊

Server透過Binder驅動向ServiceManager註冊,宣告可以對外提供服務。ServiceManager中會保留一份對映表:名字為zhangsan的Server對應的Binder取用是0x12345。


2、Client向ServiceManager請求Server的Binder取用

Client想要請求Server的資料時,需要先透過Binder驅動向ServiceManager請求Server的Binder取用:我要向名字為zhangsan的Server通訊,請告訴我Server的Binder取用。


3、向具體的Server傳送請求

Client拿到這個Binder取用後,就可以透過Binder驅動和Server進行通訊了。


4、Server傳回結果

Server響應請求後,需要再次透過Binder驅動將結果傳回給Client。

可以看到,Client、Server、ServiceManager之間的通訊都是透過Binder驅動作為橋梁的,可見Binder驅動的重要性。也許你還有一點疑問,ServiceManager和Binder驅動屬於兩個不同的行程,它們是為Client和Server之間的行程間通訊服務的,也就是說Client和Server之間的行程間通訊依賴ServiceManager和Binder驅動之間的行程間通訊,這就像是:“蛋生雞,雞生蛋,但第一個蛋得透過一隻雞孵出來”。Binder機制是如何創造第一隻下蛋的雞呢?

1、當Android系統啟動後,會建立一個名稱為servicemanager的行程,這個行程透過一個約定的命令BINDERSETCONTEXT_MGR向Binder驅動註冊,申請成為為ServiceManager,Binder驅動會自動為ServiceManager建立一個Binder物體(第一隻下蛋的雞);

2、並且這個Binder物體的取用在所有的Client中都為0,也就說各個Client透過這個0號取用就可以和ServiceManager進行通訊。Server透過0號取用向ServiceManager進行註冊,Client透過0號取用就可以獲取到要通訊的Server的Binder取用。

Android Binder設計與實現 – 設計篇中對Client、Server、Binder驅動和ServiceManager有更詳細的介紹。

2.3 Binder的代理機制

透過上面的分析,我們已經知道了Binder的基本通訊過程:Client向SerivceManger獲取到Server的Binder取用,Client透過Binder取用向Server發起具體請求。Client透過這個Binder取用具體是如何呼叫Server方法的呢?

比如一個Server提供add方法,Client實際請求add的流程是這樣的:Client先透過Binder驅動向ServiceManager獲取Server的Binder取用,這個取用就是一個Java Object,這個Object有一個add方法;Cient拿到這個Object後就可以直接請求add方法了。

實際上Client拿到的Object並不是Server真正的Binder物體,Binder驅動做了一層物件轉換,將這個Object包裝成了一個代理物件ProxyObject,這個ProxyObject和真正的Binder物體有相同的方法簽名,Client透過這個ProxyObject請求add方法時,Binder驅動會自動將請求轉發到具體的Binder物體中執行,這就是Binder的代理機制。由於ProxyObject和真正的Binder物體有相同的方法簽名,其實Client並不需要關心是ProxyObject還是真實的Object。

為了方便描述,下麵將Server真正的Binder物體稱為Binder本地物件;將Client中的Binder取用,即ProxyObject,稱之為Binder代理物件

2.4 對Binder概念的重新理解

經過上面的分析,我們已經大體清楚了Binder機制的基本通訊原理,現在回過頭來重新梳理下對Binder機制的認識:

總體來說,Binder是基於C/S結構的一種面向物件的IPC機制。包含:Client、Server、Binder驅動和ServiceManager四大組成部分。各組成部分中的Binder含義都有所有不同:

  • 對於Client

    Binder是Server本地物件的一個取用,這個取用實際上是一個代理物件,Client透過這個代理物件來間接訪問Server的本地物件;

  • 對於Server

    Binder是提供具體實現的本地物件,需向ServiceManager註冊;

  • 對於Binder驅動

    它是連線Client來Server的橋梁,負責將代理物件轉化為本地物件,並將Server的執行結果傳回給Client。

  • 對於ServiceManager

    它儲存了Server Binder字元名稱和Binder取用的對映,Client透過它來找到Server的Binder取用。

Binder驅動中保留了Binder代理物件和Binder本地物件的具體結構,由於我們只關心Binder的基本通訊機制,底層實現不做過多介紹,想具體瞭解的同學可以參考Android Binder設計與實現 – 設計篇。

3、透過程式碼來理解Binder

上面的介紹比較抽象,現在我們透過具體實體來理解Binder。

1、透過AIDL實體來瞭解Binder的用法

2、透過手動編碼實現ActivityManagerService

3.1 透過AIDL實體來瞭解Binder的用法

實現Binder通訊的最常用方法就是透過aidl,aidl介面定義了Client和Server進行通訊的介面,對aidl不瞭解的同學請參考官方檔案Android 介面定義語言 (AIDL)。

3.1.1 與Binder相關的幾個類的職責

在具體分析之前,我們需要先瞭解與Binder相關的幾個類的職責:

  • IBinder

    跨行程通訊的Base介面,它宣告了跨行程通訊需要實現的一系列抽象方法,實現了這個介面就說明可以進行跨行程通訊,Client和Server都要實現此介面。

  • IInterface

    這也是一個Base介面,用來表示Server提供了哪些能力,是Client和Server通訊的協議。

  • Binder

    提供Binder服務的本地物件的基類,它實現了IBinder介面,所有本地物件都要繼承這個類。

  • BinderProxy

    在Binder.java這個檔案中還定義了一個BinderProxy類,這個類表示Binder代理物件它同樣實現了IBinder介面,不過它的很多實現都交由native層處理。Client中拿到的實際上是這個代理物件。

  • Stub

    這個類在編譯aidl檔案後自動生成,它繼承自Binder,表示它是一個Binder本地物件;它是一個抽象類,實現了IInterface介面,表明它的子類需要實現Server將要提供的具體能力(即aidl檔案中宣告的方法)。

  • Proxy

    它實現了IInterface介面,說明它是Binder通訊過程的一部分;它實現了aidl中宣告的方法,但最終還是交由其中的mRemote成員來處理,說明它是一個代理物件,mRemote成員實際上就是BinderProxy。

3.1.2 AIDL實體

首先定義一個aidl檔案,這個介面中宣告了一個getPid方法:

// IRemoteService.aidl
package com.rush.demo.aidltest;

interface IRemoteService {
    int getPid();
}

下麵是編譯IRemoteService.aild後生成的java類:

// IRemoteService.java
package com.rush.demo.aidltest;

public interface IRemoteService extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {
        //Binder描述符
        private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService";

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

        public static com.rush.demo.aidltest.IRemoteService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.rush.demo.aidltest.IRemoteService))) {
                return ((com.rush.demo.aidltest.IRemoteService) iin);
            }
            return new com.rush.demo.aidltest.IRemoteService.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_getPid: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _result = this.getPid(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.rush.demo.aidltest.IRemoteService {
            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 getPid(java.lang.String name) 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.writeString(name);
                    mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

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

    public int getPid(java.lang.String name) throws android.os.RemoteException;
}

這個檔案中有3個類:

1、IRemoteService

繼承至IInterface介面,宣告了IRemoteService.aidl中宣告的getPid方法,它是Client和Service通訊的介面。

2、IRemoteService.Stub

IRemoteService的靜態抽象內部類,繼承自Binder,其子類需要實現IRemoteService介面,表明它是Server的Binder本地物件,需要實現getPid介面。

3、IRemoteService.Stub.Proxy

IRemoteService.Stub的靜態內部類,它並沒有繼承自Binder,而是包含了一個IBinder物件,這個物件其實是BinderProxy,說明它是Server在Client中的本地代理物件。Proxy實現了getPid介面,將引數序列化後交由mRemote(BinderProxy)處理,實際上就是交給Binder驅動來完成與遠端Stub的通訊。

先來看Stub中的asInterface方法,這個方法通常是Client在bindService成功後,由Client來呼叫的,作用是將系結成功後傳回的IBinder物件轉換為具體的IInterface介面,Client拿到這個IInterface介面後就可以和自由的呼叫Server提供的方法了。

public static com.rush.demo.aidltest.IRemoteService asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.rush.demo.aidltest.IRemoteService))) {
        return ((com.rush.demo.aidltest.IRemoteService) iin);
    }
    return new com.rush.demo.aidltest.IRemoteService.Stub.Proxy(obj);
}

asInterface方法中既可能傳回Stub本身的IRemoteService物件,也可能建立一個Proxy物件,這是為什麼呢?因為Binder雖然是跨行程通訊機制,但也可以為本行程服務,也就是說Client和Server可能在同一個行程,在同一個行程就沒必要透過Binder驅動來中轉了,直接訪問就可以了;如果Client和Server在不同的行程,就需要透過Binder代理物件來中轉。也就是說:

1、Client和Server在同一個行程,obj是Binder本地物件(Stub的子類),asInterface方法傳回的就是Binder本地物件;

2、Client和Server在不同的行程,obj實際上是Binder代理物件,asInterface傳回一個Proxy物件。


//Binder.java
/**
 * obj.queryLocalInterface是怎樣去查詢是否有本地的IInterface呢,從Binder的程式碼中可以看到,只是簡單的比較Binder的描述符和要查詢的描述符是否匹配,匹配的話直接傳回mOwner,這個mOwner就是Stub構造方法中呼叫attachInterface方法傳入的this引數。
 */

public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

final class BinderProxy implements IBinder {
    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }
}

public static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {
    // Binder描述符,值為介面類名全稱
    private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService";

    public Stub() {
        //向Binder中系結owner和descriptor
        this.attachInterface(this, DESCRIPTOR);
    }
}

obj.queryLocalInterface是怎樣去查詢是否有本地的IInterface呢,從Binder的程式碼中可以看到,只是簡單的比較Binder的描述符和要查詢的描述符是否匹配,匹配的話直接傳回mOwner,這個mOwner就是Stub構造方法中呼叫attachInterface方法傳入的this引數。而BinderProxy的queryLocalInterface方法直接傳回null。

Client中透過Binder呼叫Server方法有兩種場景:

1、Client和Server在同一個行程

Stub.asInterface方法傳回的是Stub物件,即Binder本地物件。也就是說和Binder跨行程通訊無關,直接呼叫即可,此時Client呼叫方和Server響應方在同一個執行緒中。


2、Client和Server在不同的行程

Stub.asInterface方法傳回的是Binder代理物件,需要透過Binder驅動完成跨行程通訊。這種場景下,Client呼叫方執行緒會被掛起(Binder也提供了非同步的方式,這裡不討論),等待Server響應後傳回資料。這裡要註意的是,Server的響應是在Server行程的Binder執行緒池中處理的,並不是主執行緒。

接下來分析跨行程場景下,Client呼叫getPid方法的具體流程:

1、Client呼叫Binder代理物件,Client執行緒掛起

Client中拿到的IRemoteService取用實際上是Proxy,呼叫getPid方法實際上是呼叫Proxy的getPid方法,這個方法只是將引數序列化後,呼叫了mRemote成員的transact方法。Stub類中為IRemoteService中的每個方法定義了方法編號,transact方法中傳入getPid方法的編號。此時Client呼叫方執行緒掛起,等待Server響應資料


// Stub.Proxy
public int getPid(java.lang.String name) throws android.os.RemoteException {
    ...
    _data.writeInterfaceToken(DESCRIPTOR);
    _data.writeString(name);
    mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
    _reply.readException();
    _result = _reply.readInt();
    ...
    return _result;
}


2、Binder代理物件將請求派發給Binder驅動

Proxy中的mRemote成員實際上是BinderProxy,而BinderProxy中的transact方法最終呼叫於transactNative方法,也就是說Client的請求派發給了Binder驅動來處理。


3、Binder驅動將請求派發給Server

Binder驅動經過一系列的處理後,將請求派發給了Server,即呼叫Server本地Binder物件(Stub)的onTransact方法最終在此方法中完成getPid方法的具體呼叫。在onTransact方法中,根據Proxy中呼叫transact時傳入的方法編號來區別具體要處理的方法。


// Stub
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    switch (code) {
        ...
        case TRANSACTION_getPid: {
            data.enforceInterface(DESCRIPTOR);
            //獲取方法引數
            java.lang.String _arg0 = data.readString();
            //呼叫getPid方法,這個方法在Stub的子類,即Server中實現
            int _result = this.getPid(_arg0);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}


4、喚醒Client執行緒,傳回結果

onTransact處理結束後,將結果寫入reply並傳回至Binder驅動,驅動喚醒掛起的Client執行緒,並將結果傳回。至此,一次跨行程通訊完成。

3.2 手動編碼來實現ActivityManagerService

透過前面的示例我們已經知道,aidl檔案只是用來定義C/S互動的介面,Android在編譯時會自動生成相應的Java類,生成的類中包含了Stub和Proxy靜態內部類,用來封裝資料轉換的過程,實際使用時只關心具體的Java介面類即可。為什麼Stub和Proxy是靜態內部類呢?這其實只是為了將三個類放在一個檔案中,提高程式碼的聚合性。透過上面的分析,我們其實完全可以不透過aidl,手動編碼來實現Binder的通訊,下麵我們透過編碼來實現ActivityManagerService。

首先定義IActivityManager介面:

public interface IActivityManager extends IInterface {
    //binder描述符
    String DESCRIPTOR = "android.app.IActivityManager";
    //方法編號
    int TRANSACTION_startActivity = IBinder.FIRST_CALL_TRANSACTION + 0;
    //宣告一個啟動activity的方法,為了簡化,這裡只傳入intent引數
    int startActivity(Intent intent) throws RemoteException;
}

其次,實現ActivityManagerService側的本地Binder物件基類:

// 名稱隨意,不一致叫Stub
public abstract class ActivityManagerNative extends Binder implements IActivityManager {

    public static IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in = (IActivityManager) obj.queryLocalInterface(IActivityManager.DESCRIPTOR);
        if (in != null) {
            return in;
        }
        //代理物件,見下麵的程式碼
        return new ActivityManagerProxy(obj);
    }

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

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            // 獲取binder描述符
            case INTERFACE_TRANSACTION:
                reply.writeString(IActivityManager.DESCRIPTOR);
                return true;
            // 啟動activity,從data中反序列化出intent引數後,直接呼叫子類startActivity方法啟動activity。
            case IActivityManager.TRANSACTION_startActivity:
                data.enforceInterface(IActivityManager.DESCRIPTOR);
                Intent intent = Intent.CREATOR.createFromParcel(data);
                int result = this.startActivity(intent);
                reply.writeNoException();
                reply.writeInt(result);
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }
}

再次,實現Client側的代理物件:

public class ActivityManagerProxy implements IActivityManager {
    private IBinder mRemote;

    public ActivityManagerProxy(IBinder remote) {
        mRemote = remote;
    }

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

    @Override
    public int startActivity(Intent intent) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        int result;
        try {
            // 將intent引數序列化,寫入data中
            intent.writeToParcel(data, 0);
            // 呼叫BinderProxy物件的transact方法,交由Binder驅動處理。
            mRemote.transact(IActivityManager.TRANSACTION_startActivity, data, reply, 0);
            reply.readException();
            // 等待server執行結束後,讀取執行結果
            result = reply.readInt();
        } finally {
            data.recycle();
            reply.recycle();
        }
        return result;
    }
}

最後,實現Binder本地物件(IActivityManager介面):

public class ActivityManagerService extends ActivityManagerNative {
    @Override
    public int startActivity(Intent intent) throws RemoteException {
        // 啟動activity
        return 0;
    }
}

簡化版的ActivityManagerService到這裡就已經實現了,剩下就是Client需要獲取到AMS的代理物件IActivityManager就可以通訊了。實際開發過程中透過aidl檔案能夠自動編譯出中間程式碼,並不需要我們手動去實現,不過手動編碼能夠加深對Binder機制的理解。在開發過程中我們也並不會直接使用到AMS,但瞭解AMS實現原理對熟悉Framework來說必不可少,關於AMS具體實現原理,我會在後續的文章中分析。

至此,Binder機制的基本通訊過程就介紹完了,由於Binder機制太過複雜,本人水平有限,文中難免出現錯誤或不足之處,歡迎大家指正。

參考資料

寫這篇文章學習了很多資料,整篇文章的思維結構和結構圖在很大程度上都參考了下麵的文章,真誠感謝各位作者。

  • Android Binder設計與實現 – 設計篇

  • Binder學習指南

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

  • Android行程間通訊(IPC)機制Binder簡要介紹和學習計劃

  • 為什麼 Android 要採用 Binder 作為 IPC 機制?

  • Linux 虛擬記憶體和物理記憶體的理解

  • 認真分析mmap:是什麼 為什麼 怎麼用

  • 使用者空間與核心空間

  • Android 介面定義語言 (AIDL)

  • ServiceManager行程守護


●編號353,輸入編號直達本文

●輸入m獲取到文章目錄

推薦↓↓↓

Java程式設計

更多推薦18個技術類公眾微信

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂