1. 程式人生 > >帶你一起剖析Android AIDL跨程序通訊的實現原理

帶你一起剖析Android AIDL跨程序通訊的實現原理

640?wx_fmt=png&wxfrom=5&wx_lazy=1

今日科技快訊

從8月份釋出混改方案開始,中國聯通作為首批混改試點中惟一一家集團層面整體混改的央企,也是全球首例“電信運營商+網際網路巨頭”戰略融合先行試水者,一直倍受關注。而就在昨日,有業內人士稱:中國人壽、騰訊、百度、京東、阿里、蘇寧等戰略投資者,已經完成了750億元真金白銀的投入,彰顯了對聯通混改前景的信心。

作者簡介

明天就是週末啦,提前祝大家週末愉快!

本篇依舊來自 凶殘的程式設計師 的投稿,繼上一篇Android跨程序通訊,深入淺出AIDL的進階篇,今天帶領大家深入瞭解AIDL,希望大家喜歡!

凶殘的程式設計師  的部落格地址:

http://blog.csdn.net/qian520ao

前言

今天我們來深入探討一下AIDL為什麼能夠完成這個跨程序操作,這其中是否隱藏著一些不為人知的祕密,讓我們跟著筆者的思路,慢慢撥開籠罩在AIDL上的謎團。

概要

0?wx_fmt=png

先用上圖整體描述這個AIDL從客戶端(Client)發起請求至服務端(Server)相應的工作流程,我們可以看出整體的核心就是 Binder

解剖

asInterface

用於將服務端的Binder物件轉換成客戶端所需的AIDL介面型別的物件,這種轉換過程是區分程序的【如果客戶端和服務端位於同一程序,那麼此方法返回的就是服務端的Stub物件本身,否則返回的是系統封裝後的Stub.proxy物件】

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

在 ServiceConnection 繫結服務的方法裡,我們通過IDemandManager.Stub.asInterface(service)方法 獲得 IDemandManager物件,我們跟著 asInterface 步伐看看裡面有什麼名堂。

PS : onServiceConnected方法 的 (IBinder)service引數 在 ActivityThread 建立,他們之間還會扯出 ActivityManagerService,ManagerService 等,有興趣的可以深入瞭解這篇

Activity的啟動過程

http://blog.csdn.net/qian520ao/article/details/78156214

0?wx_fmt=png

從上圖的類結構圖中我們可以看出,這個 IDemandManager.aidl檔案 通過編譯成為一個介面類,而這個類最核心的成員是 Stub類 和 Stub的內部代理類Proxy

順著 asInterface 方法,結合上面對該方法的描述,可以看出通過 DESCRIPTOR 標識判斷

如果是同一程序,那麼就返回Stub物件本身(obj.queryLocalInterface(DESCRIPTOR)),否則如果是跨程序則返回Stub的代理內部類Proxy

也就是說這個 asInterface 方法返回的是一個遠端介面具備的能力(有什麼方法可以呼叫),在我們專案裡,asInterface 的能力就是 get/setDemand 和 註冊/解綁監聽介面。

asBinder

緊接著 asInterface,我們看到一個簡潔的方法 asBinder

顧名思義,asBinder 用於返回當前 Binder物件。

//Stub
@Override
public android.os.IBinder asBinder() {    return this; }
//Proxy
private android.os.IBinder mRemote; @Override
public android.os.IBinder asBinder() {    return mRemote; }

根據程式碼我們可以追溯到,proxy 的這個 mRemote 就是繫結服務(bindService)時候由IDemandManager.Stub.asInterface(binder) 傳入的 IBinder物件。

這個 Binder物件 具有跨程序能力,在 Stub類 裡面(也就是本程序)直接就是 Binder本地物件,在 Proxy類 裡面返回的是遠端代理物件(Binder代理物件)。[所以跨程序的謎團,會隨著對 Binder 的分析和研究,逐漸變得清晰起來。]

因為我們這個編譯生成的 IDemandManager介面 繼承了 android.os.IInterface介面,所以我們先分析 IInterface介面。

public interface IDemandManager extends android.os.IInterface

IInterface

而這個 IInterface介面 就只聲明瞭一個方法,但是 Stub 和 Proxy 都分別間接的實現了該介面。

/** 
 * Base class for Binder interfaces.  When defining a new interface, 
 * you must derive it from IInterface. 
 */
public interface IInterface {    /**     * Retrieve the Binder object associated with this interface.     * You must use this instead of a plain cast, so that proxy objects     * can return the correct result.     */    public IBinder asBinder(); }

Binder介面的基類。 定義新介面時,你必須實現IInterface介面。

檢索與此介面關聯的 Binder物件。你必須使用它而不是一個簡單的轉換,這樣代理物件才可以返回正確的結果。

從上面的系統註釋中我們可以理解出:

1. 要宣告(或者是手動diy建立)AIDL性質的介面,就要繼承 IInterface

2. 代表 遠端server物件 具有的能力,具體是由 Binder 表達出這個能力。

DESCRIPTOR

//系統生成的
private static final java.lang.String DESCRIPTOR = "qdx.aidlserver.IDemandManager";

Binder 的唯一標識,一般用當前 Binder 的類名錶示。

onTransact(服務端接收)

我們發現 IDemandManager介面,實際上並沒有太多複雜的方法,看完了asInterface 和 asBinder方法,我們再來分析 onTransact方法。

onTransact方法 執行在服務端中的 Binder 執行緒池中。客戶端發起跨程序請求時,遠端請求會通過系統底層封裝後交給此方法來處理。如果此方法返回 false,那麼客戶端的請求就會失敗。

  • code : 確定客戶端請求的目標方法是什麼。(專案中的 getDemand 或者是 setDemandIn方法)

  • data : 如果目標方法有引數的話,就從data取出目標方法所需的引數。

  • reply : 當目標方法執行完畢後,如果目標方法有返回值,就向reply中寫入返回值。

  • flag : Additional operation flags. Either 0 for a normal RPC, or FLAG_ONEWAY for a one-way RPC.(暫時還沒有發現用處,先標記上英文註釋)

0?wx_fmt=png

也就是說,這個 onTransact方法 就是服務端處理的核心,接收到客戶端的請求,並且通過客戶端攜帶的引數,執行完服務端的方法,返回結果。下面通過系統生成的程式碼,我們簡要的分析一下 onTransact方法 裡我們專案寫的 setDemandIn和setDemandOut方法。

case TRANSACTION_setDemandIn: {  
    data.enforceInterface(DESCRIPTOR);  
    qdx.aidlserver.MessageBean _arg0;  
    if ((0 != data.readInt())) {  
        _arg0 = qdx.aidlserver.MessageBean.CREATOR.createFromParcel(data);  
    } else {  
        _arg0 = null;  
    }  
    this.setDemandIn(_arg0);  
    reply.writeNoException();  
    return true;  
}
case TRANSACTION_setDemandOut: {      data.enforceInterface(DESCRIPTOR);      qdx.aidlserver.MessageBean _arg0;      _arg0 = new qdx.aidlserver.MessageBean();      this.setDemandOut(_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;

此段程式碼並沒有多少玄機,就是負責資料的讀寫,以及結果的返回。

但是有一個知識點可以在此驗證,就是 定向tag 的作用,上面的方法 定向tag 分別是 in out,上篇文章介紹 定向tag 的作用就是跨程序中資料的流向,setDemandIn方法 中我們可以看到我們讀取了客戶端傳遞過來的資料引數data,即客戶端->服務端。out方法 可以同理自行分析

AIDL中的in,out,inout分析

http://www.jianshu.com/p/ddbb40c7a251

transact(客戶端呼叫)

分析完了 Stub,就剩下 Stub 的內部代理類 Proxy

驚奇的發現 Proxy類 主要是用來方法呼叫,也就是用來客戶端的跨程序呼叫。

0?wx_fmt=png

transact方法 執行在客戶端,首先它建立該方法所需要的 輸入型Parcel物件 _data、輸出型Parcel物件 _reply;

接著呼叫繫結服務傳來的 IBinder物件 的 transact方法 來發起遠端過程呼叫(RPC)請求,同時當前執行緒掛起;

然後服務端的 onTransact方法 會被呼叫,直到 RPC 過程返回後,當前執行緒繼續執行,並從 _reply 中取出 RPC 過程的返回結果,也就是返回 _reply 中的資料。

我們看到獲得 Parcel 的方法為 Parcel.obtain(),按照套路這個應該就是從 Parcel池 中獲取該物件,減少建立物件的開支,跟進方法我們可以看得的確是建立了一個 POOL_SIZE 為 6 的池用來獲取Parcel物件。

所以在跨程序通訊中Parcel是通訊的基本單元,傳遞載體。

而這個 transact方法 是一個本地方法,在 native層 中實現,功力不足,點到為止。

分析到了這裡,感覺頓悟了許多,再一次引用開頭概述的圖片來看大概,整體的思路便浮在腦海中。so 噠死內~

0?wx_fmt=png

總結

通過對AIDL的分析,我們發現原來這一切圍繞著Binder有序的展開

AIDL 通過 Stub類 用來接收並處理資料,Proxy代理類 用來發送資料,而這兩個類也只是通過對 Binder 的處理和呼叫,下一篇我們將深入摸清這個 Binder 究竟為何物,能夠輕鬆遊走於跨程序之中。

0?wx_fmt=png

所以所謂的服務端和客戶端,我們拆開看之後發現原來竟是不同類的處理

Stub充當服務端角色,持有Binder實體(本地物件)。

  • 獲取客戶端傳過來的資料,根據方法 ID 執行相應操作。

  • 將傳過來的資料取出來,呼叫本地寫好的對應方法。

  • 將需要回傳的資料寫入 reply 流,傳回客戶端。

Proxy代理類充當客戶端角色,持有Binder引用(控制代碼)。

  • 生成 _data 和 _reply 資料流,並向 _data 中存入客戶端的資料。

  • 通過 transact() 方法將它們傳遞給服務端,並請求服務端呼叫指定方法。

  • 接收 _reply 資料流,並從中取出服務端傳回來的資料。

而且所謂的服務端和客戶端都是相對而言的,服務端不僅可以接收和處理訊息,而且可以定時往客戶端傳送資料,與此同時服務端使用Proxy類跨程序呼叫,相當於充當了”Client”。

並且有一點要理解是,跨程序通訊的時候,傳遞的資料物件並不是從程序A原原本本的發給程序B。庫克說要送你蘋果,而你收到的iphone X就真的是美國生產的嗎?不,它也可能是made in China.

終上所述,AIDl這個工具就已經分析結束,如果文中有不足之處還望指出。如果有什麼更好的見解也可以留言,最終的目標都是共同進步。有興趣的可以繼續看

Android Binder之應用層總結與分析

http://blog.csdn.net/qian520ao/article/details/78089877

歡迎長按下圖 -> 識別圖中二維碼

或者 掃一掃 關注我的公眾號

640.png?

640?wx_fmt=jpeg