android-IPC/Binder/D-BUS(Binder/Messager/AIDL)程式間通訊(訊息機制)

desaco發表於2016-01-28

> 自定義Binder,服務端的onTransact(), 客戶端的transact()。
AIDL、Messenger等常用的程式間通訊框架都是對Binder的封裝。
Android自定義Binder的使用,實現程式間通訊- https://github.com/OboBear/MyBinder

-- 程式間通訊機制已經存在好多種,Corba,DCOP,COM,SOAP, XML-RPC ...

- Android中的Handler的Native層研究- https://my.oschina.net/u/3863980/blog/1933086
由於Native的Handler涉及到c++以及對飲Linux系統介面的呼叫:
Linux中的匿名管道
epoll函式
Handler的Native層原始碼研究
 Android的Native原始碼中運用的是匿名管道

程式間的通訊方式:
管道(pipe)和命名管道(FIFO)
訊號(signal)
訊息佇列
共享記憶體
訊號量
套接字(socket)

  管道是Linux中進行程式通訊或者執行緒通訊的一種手段之一,管道分為匿名管道(pipe)以及命名管道(named pipe),管道是核心維護的一個快取, 它提供兩個 fd, 從一個fd寫入資料, 從另一個fd讀出資料. 所以它是半雙工的。
  epoll是Linux對於select以及poll的增強版,在Linux的2.6核心提出。對於select,poll以及epoll的而言,三個都是IO多路複用的機制,可以監視多個描述符的讀/寫等事件,一旦某個描述符就緒(一般是讀或者寫事件發生了),就能夠將發生的事件通知給關心的應用程式去處理該事件。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而非同步I/O則無需自己負責進行讀寫,非同步I/O的實現會負責把資料從核心拷貝到使用者空間。

  呼叫Natvie層程式碼在Native初始化一個NativeMessageQueue和Looper,在Looper中會開啟一個匿名管道,由epoll來監聽I/O事件的變化,當管道中有資料的時候,通過epoll通知系統讀取資料。最後返回一個NativeMessageQueue的指標交由Java層的MessageQueue方便下次定址訪問。
  在主執行緒的MessageQueue沒有訊息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裡,詳情見Android訊息機制1-Handler(Java層),此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生,通過往pipe管道寫端寫入資料來喚醒主執行緒工作。這裡採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主執行緒大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

-- AIDL/IPC/IBinder , 

AIDL中支援的資料型別有:Java基本型別,String,CharSequence, List, Map,其他AIDL定義的AIDL介面,實現Parcelable的類
Android的IPC機制-- http://blog.csdn.net/zizidemenghanxiao/article/details/50341773
Android IBinder機制簡單介紹-- http://blog.csdn.net/mr_liabill/article/details/49837851
  執行在同一個程式中的元件是屬於同一個虛擬機器和同一個Application的,同理執行在不同程式的元件是屬於兩個不同的虛擬機器和Application的。
  Client端使用的Poxy裡面封裝了一個binder與Server端的stub(也是一個binder物件)進行互動,兩個binder作為介面呼叫BinderDriver的transact來傳送資料包,以及onTransact接收處理資料包。
  Binder作為一種程式間通訊機制,負責提供遠端呼叫的功能(RPC),它的系統元件主要包括四種:Client, Server, ServiceManager, Binder Driver. 
  Client, Server, ServiceManager執行在系統的使用者態,而Binder Driver執行在核心態。為了完成Client端到Server端的通訊任務,使用者空間的需要操作Binder Driver提供的/dev/binder檔案來完成互動。那麼ServiceManager的工作是什麼呢?      ServiceManager負責管理Server並向Client端提供一個Server的代理介面(proxy)。通過代理介面中定義的方法,Client端就可以使用Server端提供的服務了。整個過程如下:
  Client端呼叫代理介面的方法,將Client的引數打包為parcel物件傳送給核心空間中BinderDriver;
  Server端讀取到BinderDriver中的請求資料,將parcel物件解包並處理;
  處理好後,將處理結果打包返回給BinderDriver,再交給Client端。
  另外,Client端與Server端的呼叫過程是同步的,即在Server返回結果之前,Client端是阻塞的。

> Android Binder

  管道、訊息佇列、Socket實現一次程式通訊都需要2次記憶體拷貝,效率太低;共享記憶體雖然不需要拷貝記憶體,但管理複雜;Binder只需要一次記憶體拷貝,從效能角度來看,低於共享記憶體方式。
  Android系統為每個已安裝的App都分配了使用者ID(UID),UID是鑑別程式身份的重要標識,通過UID可以進行一系列的許可權校驗。另一方面 ,傳統IPC的接入點是開放的,任何程式都可以根據協議進行訪問,無法阻止惡意程式的訪問,Android需要一種基於C/S架構的IPC機制,Server端需要能夠對Client的請求進行身份校驗,來保證資料的安全性。
  程式隔離的實現使用了虛擬地址空間,什麼是虛擬地址空間呢?首先需要了解作業系統中的虛擬記憶體概念,它是一種提高程式設計效率和提高實體記憶體利用效率的一種技術。簡單來說,就是應用程式看到了都一片連續完整的記憶體地址空間,而實際上這些地壇空間是對映到碎片化的實體記憶體中的,這個對映的過程對應用程式來說是透明的。

  Linux是使用的是虛擬記憶體定址方式,使用者空間的虛擬記憶體地址是對映到實體記憶體中的,對虛擬記憶體的讀寫實際上是對實體記憶體的讀寫,這個過程就是記憶體對映,這個記憶體對映過程是通過系統呼叫mmap()來實現的。
  Binder藉助了記憶體對映的方法,在核心空間和接收方使用者空間的資料快取區之間做了一層記憶體對映。這樣一來,從傳送方使用者空間拷貝到核心空間快取區的資料,就相當於直接拷貝到了接收方使用者空間的資料快取區,從而減少了一次資料拷貝。

  Binder是基於C/S架構的,對於通訊雙方來說,發起請求的程式屬於Client,接收請求的程式屬於Server,由於存在程式隔離,雙方不能直接通訊,Binder是如何實現的呢?  ServiceManager和Binder驅動屬於兩個不同的程式,它們是為Client和Server之間的程式間通訊服務的,也就是說Client和Server之間的程式間通訊依賴ServiceManager和Binder驅動之間的程式間通訊。

-- Binder機制是如何創造第一隻下蛋的雞呢?
  當Android系統啟動後,會建立一個名稱為servicemanager的程式,這個程式通過一個約定的命令BINDERSETCONTEXT_MGR向Binder驅動註冊,申請成為為ServiceManager,Binder驅動會自動為ServiceManager建立一個Binder實體(第一隻下蛋的雞);
  並且這個Binder實體的引用在所有的Client中都為0,也就說各個Client通過這個0號引用就可以和ServiceManager進行通訊。Server通過0號引用向ServiceManager進行註冊,Client通過0號引用就可以獲取到要通訊的Server的Binder引用。

-- Binder是基於C/S結構的一種物件導向的IPC機制。包含:Client、Server、Binder驅動和ServiceManager四大組成部分。各組成部分中的Binder含義都有所有不同:
 1.對於Client,Binder是Server本地物件的一個引用,這個引用實際上是一個代理物件,Client通過這個代理物件來間接訪問Server的本地物件;
 2.對於Server,Binder是提供具體實現的本地物件,需向ServiceManager註冊;
 3.對於Binder驅動,它是連線Client來Server的橋樑,負責將代理物件轉化為本地物件,並將Server的執行結果返回給Client。
 4.對於ServiceManager,它儲存了Server Binder字元名稱和Binder引用的對映,Client通過它來找到Server的Binder引用。
 Binder驅動中保留了Binder代理物件和Binder本地物件的具體結構

> Linux D-BUS , Windows D-Bus,D-Bus,kdbus和Binder
 libdbus庫,常見的框架(qt,Glib,Java,Python,C sharp etc.),訊息有四種型別:METHOD_CALL, METHOD_RETURN, ERROR, and SIGNAL.
 D-BUS官方網站:http://www.freedesktop.org/wiki/Software/dbus
 “D-Bus is a message bus system, a simple way for applications to talk to one another. In addition to interprocess communication, D-Bus helps coordinate process lifecycle; it makes it simple and reliable to code a "single instance" application or daemon, and to launch applications and daemons on demand when their services are needed. ”
 D-Bus的特點是輕量級、快速,為主流桌面環境提供統一的程式間通訊介面。Android通過D-Bus來耦合使用者介面和服務這種劃分策略是軟體系統安全設計的一個典型分離許可權設計手法
 D-Bus 設計用來作為使用者互動介面與系統服務(守護程式)之間的分隔和通訊以及系統服務(守護程式)之間的通訊。D-Bus 設計之初並未考慮為Cluster,P2P,分散式應用之間的通訊.
 D-Bus適合於控制任務,但並不適合傳輸大量的資料。例如,D-Bus能通知音訊服務程式改變音量(D-Bus著名的例子),卻不適合傳送音訊資料。
 採用C/S架構的IPC機制,D-Bus是在使用者空間實現的方法,效率低,訊息拷貝次數和上下文切換次數都明顯多過於Binder。針對D-Bus這些缺陷,於是就產生了kdbus,這是D-Bus在核心實現版,效率得到提升,與Binder一樣在核心作為字元設計,通過open()開啟裝置,mmap()對映記憶體。

> binder,OpenBinder
跨程式通訊- https://github.com/BaronZ88/HelloBinder
binder的kernel實現- https://github.com/android/kernel_common.git 

binder的kernel實現- https://android.googlesource.com/platform/frameworks/native

android程式間通訊:使用AIDL-- http://blog.csdn.net/saintswordsman/article/details/5130947

 

-- Binder 令牌(Tokens)
  安卓的一項核心設計思想是希望能提供一個不需要依賴中央檢驗部門來檢驗程式請求的開放平臺.為此,Android使用了程式沙盒和Linux的程式分離來防止程式以無法控制和不安全的方式訪問系統內部或者其他程式.這種架構是開發者與使用者共同選擇的:既不需要額外的保護來防止惡意程式,同時系統要自動的處理好所有事情.
-- 每個Binder物件引用都是由以下兩者之一分配的:
1.同一個程式中指向一個Binder物件的虛擬記憶體地址,或
2.一個唯一的32位控制程式碼(由Binder核心驅動分配的)指向不同程式中Binder的虛擬記憶體地址.
  系統的frameword廣泛地使用Binder令牌來保證跨程式互動的安全性:一個客戶端可以通過建立一個Binder物件作為令牌與服務程式共享,並且服務端能使用它驗證來自客戶端的請求排除所有偽造請求.

-- 視窗令牌(Window Tokens),系統彈窗
  一個視窗令牌是一種特殊的Binder令牌,視窗管理器用於唯一標識系統中的一個視窗.視窗令牌對於安全十分重要,因為它們會阻止惡意程式出現在其他程式介面之上.視窗管理器通過要求應用程式將它們的視窗令牌作為新增或者刪除一個視窗的引數傳遞過來(擁有 android.permission.SYSTEM_ALERT_WINDOW 許可權的程式,即"在其他程式介面上繪製"許可權.

 -- IPC/Binder: Binder/Messager/AIDL

  IPC機制之一:簡介、多程式模式:http://blog.csdn.net/huivs12/article/details/49020039;
  Android系統沒有采用上述提到的各種程式間通訊機制,而是採用Binder機制,難道是因為考慮到了移動裝置硬體效能較差、記憶體較低的特點?不得而知。Binder其實也不是Android提出來的一套新的程式間通訊機制,它是基於OpenBinder來實現的。OpenBinder最先是由Be Inc.開發的,接著Palm Inc.也跟著使用。現在OpenBinder的作者Dianne Hackborn就是在Google工作,負責Android平臺的開發工作。
Binder- http://www.angryredplanet.com/~hackbod/openbinder/docs/html/BinderIPCMechanism.html

  涉及到底層的功能性的應用,比如遊戲加速,修改記憶體,掛機指令碼神馬的,發現裡面的通訊機制無一例外的都是使用的socket

-- Liunx 中跨程式通訊涉及到的一些基本概念:
1.程式隔離;

2.程式空間劃分:使用者空間(User Space)/核心空間(Kernel Space);

3.系統呼叫:使用者態/核心態。
-- Android 系統是基於 Linux 核心的,Linux 已經提供了管道、訊息佇列、共享記憶體和 Socket 等 IPC 機制。
Binder 是一種程式間通訊機制,基於開源的 OpenBinder 實現;OpenBinder 起初由 Be Inc. 開發,後由 Plam Inc. 接手。
如何在Android下使用Binder- https://blog.csdn.net/xnwyd/article/details/7188223
  binder在Android中提供從一個任務到另一個任務裡的執行緒的同步呼叫(CPU)。這個過程中呼叫執行緒掛起直到應答執行緒返回,不需要訊息佇列。RAM只是用來在不同的呼叫者間共享資料。Android裡binder庫的關鍵是呼叫程式把自己的CPU時間片讓給應答程式。
  Binder是基於OpenBinder,在Android系統上使用的程式間通訊機制。Binder基於Client-Server通訊模式,本質上可以理解為它實現了Client對Server物件的遠端呼叫。Binder機制定義了四個元件,分別是Client,Server,ServiceManager和binder驅動,其中Client,Server,ServiceManager執行於使用者空間,binder驅動執行於核心空間。
  binder驅動是核心中的一個字元裝置驅動/dev/binder,它是整個Binder通訊機制的核心。Client,Server,ServiceManager通過open()和ioctl()檔案操作函式與binder驅動進行通訊,從而實現了Client向Server傳送請求,Server處理請求並返回結果到Client。具體來說,它負責程式之間Binder通訊的建立,Binder在程式之間的傳遞,Binder引用計數管理,資料包在程式之間的傳遞和互動等一系列底層支援。
  ServiceManager是一個守護程式,負責管理服務,即所有的Server需要向ServiceManager註冊服務。同時,ServiceManager向Client提供查詢和獲取Server的介面。
  基於Client-Server的通訊方式廣泛應用於從網際網路和資料庫訪問到嵌入式手持裝置內部通訊等各個領域。智慧手機平臺特別是Android系統中,為了嚮應用開發者提供豐富多樣的功能,這種通訊方式更是無處不在,諸如媒體播放,視音訊頻捕獲,到各種讓手機更智慧的感測器(加速度,方位,溫度,光亮度等)都由不同的Server負責管理,應用程式只需做為Client與這些Server建立連線便可以使用這些服務,花很少的時間和精力就能開發出令人眩目的功能。
  另一方面是傳輸效能。socket作為一款通用介面,其傳輸效率低,開銷大,主要用在跨網路的程式間通訊和本機上程式間的低速通訊。訊息佇列和管道採用儲存-轉發方式,即資料先從傳送方快取區拷貝到核心開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程。共享記憶體雖然無需拷貝,但控制複雜,難以使用。

-- 傳統的 IPC 通訊方式有兩個問題:
 1.效能低下,一次資料傳遞需要經歷:記憶體快取區 --> 核心快取區 --> 記憶體快取區,需要 2 次資料拷貝;
 2.接收資料的快取區由資料接收程式提供,但是接收程式並不知道需要多大的空間來存放將要傳遞過來的資料,因此只能開闢儘可能大的記憶體空間或者先呼叫 API 接收訊息頭來獲取訊息體的大小,這兩種做法不是浪費空間就是浪費時間。

-- Linux現有的所有程式間IPC通訊方式:
 1. 管道:在建立時分配一個page大小的記憶體,快取區大小比較有限;
 2. 訊息佇列:資訊複製兩次,額外的CPU消耗;不合適頻繁或資訊量大的通訊;
 3. 共享記憶體:無須複製,共享緩衝區直接付附加到程式虛擬地址空間,速度快;但程式間的同步問題作業系統無法實現,必須各程式利用同步工具解決;
 4. 套接字:作為更通用的介面,傳輸效率低,主要用於不通機器或跨網路的通訊;
 5. 訊號量:常作為一種鎖機制,防止某程式正在訪問共享資源時,其他程式也訪問該資源。因此,主要作為程式間以及同一程式內不同執行緒之間的同步手段。
 6. 訊號: 不適用於資訊交換,更適用於程式中斷控制,比如非法記憶體訪問,殺死某個程式等;

-- 在Linux中使用的IPC通訊機制如下:
 1.傳統IPC:無名pipe, signal, trace, 有名管道
 2.AT&T Unix 系統V:共享記憶體,訊號燈,訊息佇列
 3.BSD Unix:Socket

 在linux通訊機制中,目前只有socket支援C/S的通訊模式。

 Binder中有兩種索引,一是本地程式地址空間的一個地址,另一個是一個抽象的32位控制程式碼(HANDLE),它們之間是互斥的:所有的程式本地物件的索引都是本地程式的一個地址(address, ptr, binder),所有的遠端程式的物件的索引都是一個控制程式碼(handle)

> AIDL機制,AIDL/IPC/多應用/多執行緒;AIDL(以及Binder);Binder/IBinder ; Messenger

 代理實現/存根通訊。

Binder詳解: http://blog.csdn.net/yangzhiloveyou/article/details/14043801

Android深入淺出之Binder機制: http://www.cnblogs.com/innost/archive/2011/01/09/1931456.html
Android的IPC機制Binder的各個部分: http://blog.chinaunix.net/uid-9185047-id-3281772.html
Android實戰技術:理解Binder機制: http://blog.csdn.net/hitlion2008/article/details/9842289
Android系統篇之----Binder機制和遠端服務呼叫機制分析-- http://blog.csdn.net/jiangwei0910410003/article/details/52467919
    在Android系統的虛擬機器中,每一個app執行都是在一個獨立的沙箱裡面的,這樣的話,一個應用崩潰也不會影響到其他應用,這樣我們就提出了一個問題,跨程式如如何進行通訊?如何傳遞資料?其實兩個程式是不能直接通訊的,他需要Android系統底層的幫助來進行通訊!那就是我們每個人都知道的四大元件
    如果您不需要執行併發IPC在不同的應用程式中 你就用Binder ,或者如果你想執行IPC,但不需要處理多執行緒,實現你的介面Messenger,無論如何,確保你瞭解實現AIDL之前繫結服務。

1.基本資料型別 2.String 3.CharSequence 4.List 5.Map 6.Parcelable(序列化)
關於AIDL的介紹在文件:docs/guide/developing/tools/aidl.html
關於IBinder的介紹在文件:docs/reference/android/os/IBinder.html
以及Binder:docs/reference/android/os/Binder.html

-- COM的一個概念---------Proxy/Stub結構(代理/存根結構) 
 Android就是在傳統的C/S架構中加入了一層,實現IPC。我們下面來詳細介紹一下android的aidl實現原理:
    AIDL(Android介面定義語言)是類似於其他你遇到過的IDL。它允許您定義的程式設計介面,客戶端和服務達成一致,以互相交流使用程式間通訊(IPC)。在Android上,一個程式無法正常訪問另一個程式的記憶體,而AIDL可以為你實現。
-- Binder實現的是RPC,它與具體語言無關,所以理論上,基於Binder可以讓Native的程式與Android Java層的應用程式通訊。最關鍵的,也是最麻煩點就在於客戶端如何獲取服務端的Service的IBinder物件,但也非不可能,要通過JNI和ClassLoader等一系列方式可以獲得Java層的物件,其實Java層API的實現也是以這樣子的方式。
  Binder是什麼?它可以叫作:IPC、RPC、執行緒遷移、遠端物件訪問,本文中理解它為遠端物件訪問更貼切些,簡而言之就是一個程式能訪問另一個程式中的物件,呼叫該物件的方法,就好像物件在自己的程式中一樣,這種訪問是同步的訪問,當然Binder也能實現非同步的通訊。
  Binder分為Java和C++兩套實現,分別用於Java應用和Native應用開發,Java Binder實際上也是基於C++ Binder的一個封裝,因此本文只分析C++ Binder。
服務分為2種:Native Service、Android Service。
Native Service:是在系統init階段通過init.rc指令碼建立的服務,完全在C++空間完成的服務。
Androids service:是系統二階段(init2)初始化時建立的服務,是指在JVM空間完成的服務,雖然也要使用Navite上的框架,但是服務主體存在於Android空間,所有的Androids service都執行在一個程式中:systemsever程式。
  -- Binder實現原理
    Binder本質上說就是一種資料傳輸方式,當通過服務代理呼叫服務物件的方法時,服務代理把引數序列化進行傳輸,服務物件反序列化取出引數,然後呼叫服務物件的方法。
   程式間的通訊是通過Android專門為Linux增加的一個裝置(/dev/binder)來實現的。
    本質上是使用了共享記憶體來進行通訊,但該共享記憶體和我們平常理解會有一點不一樣。
我們平常使用的共享記憶體是兩個程式之間,即點到點的,如果有N個程式要兩兩通訊而又不相互干擾,那麼就必須有N*N個共享記憶體。Binder使用的共享記憶體是程式與binder裝置之間,即binder做為一箇中間者進行傳遞,類似會議電視的MCU。
使用了共享記憶體,在驅動中還是會有一次拷貝的,程式A向程式B傳遞資料時,資料會被驅動從程式A中拷貝到binder和程式B之間的共享記憶體中,然後程式B就可以直接讀了。

---------------------

非實時,通知性的方式

第一種方式就是Intent,Intent可以非常方便的通訊,但是它是非實時的,無法進行實時的像函式呼叫那樣的實時的通訊。

實時的函式呼叫
    但是IPC的根本目的還是為了實現函式的呼叫,即使是傳遞資料也是要通過函式呼叫的方式,為什麼呢?因為程式執行總是要知道狀態,要有邏輯上的行為,因此必須通訊函式才能體現出行為。
    IPC的機制除了程式,或者說不同的應用程式之間進行通訊,同時也能夠讓不同的元件之間進行像普通物件那樣進行實時的呼叫。因為Android的元件都是由系統框架統一的進行構建和銷燬,所以你就無法建立物件,因此,就沒有辦法像普通的物件那樣進行組合或者聚合,從而也就沒有辦法進行直接呼叫。但是IPC的方式就可以讓Activity/Service獲取另外一個Service物件的引用,從而直接呼叫其上的方法。
    還有,就是這些IPC方法總是產生客戶/服務端模式的呼叫,也即是客戶端元件(Activity/Service)持有服務端Service的元件,只能是客戶端主動呼叫服務端的方法,服務端無法反過來呼叫客戶端的方法,因為IPC的另一端Service無法獲取客戶端的物件。

> 有三種方式可以進行IPC通訊:
1. 直接使用Binder物件
   缺點是這種方式不能進行跨程式,跨應用程式的函式呼叫。只能實現在同一個程式之中,同一個應用程式之中的不同的元件之間通訊。
   優點就是這種方式使用起來特別簡單,對於公開出來的方法沒有任何的限制,可以傳遞任何的物件,甚至是回撥等等。
   總體上來講如果不需要跨程式,這是最好的實現方式,可以完全實現像普通物件之間的組合和聚合。但是這裡,最好不要像Android文件中的示例那樣,直接返回Service物件,因為Service物件會有很多Public的方法,如果直接返回Service物件會導致公開很多不必須的方法,一旦Client端呼叫這些方法,將導致奇怪的現象和Bug,一個方法就是用Proxy對Service物件進行封裝,只公開需要的介面。

示例:
1.service: 
public class BinderPrinterService extends Service {  
    private static final String TAG = "PlayerService";  
    private IBinder mBinder;  
  
    @Override  
    public void onCreate() {  
        mBinder = new ProxyService(this);  
    }  
      
    @Override  
    public IBinder onBind(Intent intent) {  
        return mBinder;  
    }  
      
    public void print(String msg, TextView tv) {  
        try {  
            Log.e(TAG, "Preparing printer...");  
            tv.setText("Preparing printer...");  
            Thread.sleep(1000);  
            Log.e(TAG, "Connecting printer...");  
            tv.setText("Connecting printer...");  
            Thread.sleep(1000);  
            Log.e(TAG, "Printing.... " + msg);  
            tv.setText("Printing.... ");  
            Thread.sleep(1000);  
            Log.e(TAG, "Done");  
        } catch (InterruptedException e) {  
        }  
        tv.setText(msg);  
        Toast.makeText(this, "Printing is done.", Toast.LENGTH_SHORT).show();  
    }  
}  
  
class ProxyService extends Binder {  
    private BinderPrinterService mService;  
      
    public ProxyService(BinderPrinterService svc) {  
        mService = svc;  
    }  
      
    public void print(String msg, TextView tv) {  
        mService.print(msg, tv);  
    }  
}
  
client:
public class BinderClientActivity extends Activity {  
    ProxyService mService;  
    private TextView mStatusPanel;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.printer_activity);  
        setTitle("Binder client Activity");  
        mStatusPanel = (TextView) findViewById(R.id.status);  
        ((Button) findViewById(R.id.play)).setText("Print via extending Binder");  
    }  
  
    @Override  
    protected void onStart() {  
        super.onStart();  
        doBindService();  
    }  
  
    private void doBindService() {  
        Intent intent = new Intent(this, BinderPrinterService.class);  
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);  
    }  
  
    @Override  
    protected void onStop() {  
        super.onStop();  
        doUnbindService();  
    }  
  
    private void doUnbindService() {  
        if (mService != null) {  
            unbindService(mConnection);  
        }  
    }  
      
    public void onButtonClick(View v) {  
        if (mService == null) {  
            return;  
        }  
        mService.print("Tree of liberty must be refreshed from time to time with blood of patroits and tyrants",  
                mStatusPanel);  
    }  
      
    private ServiceConnection mConnection = new ServiceConnection() {  
        @Override  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            mService = (ProxyService) service;  
        }  
  
        @Override  
        public void onServiceDisconnected(ComponentName arg0) {  
            mService = null;  
        }  
    };  
}  

2. 使用Messenger物件
    這是利用了訊息迴圈佇列來進行通訊的,也就是說服務端封裝成了一個Handler,客戶端向這個Handler傳送Message,服務端再處理這個Message從而實現通訊。
    優點,最突出的優點就是它可以保證執行緒上的安全,或者說時序上的安全。因為客戶端是向Handler傳送訊息,而不是直接的函式呼叫,所以呢?Message都是按先後的順序放到服務端的訊息佇列當中,然後再由服務端Service決定如何處理這些Message。因為直接的函式呼叫會導致被呼叫的函式也會出現在呼叫者的執行緒之中,也就是說被呼叫到的函式也會繼續存在於呼叫者的呼叫棧中,因此有可能產生執行緒的問題。而Messenger方式,並不是直接的函式呼叫,而是僅向Service的Handler傳送一個Message,然後呼叫者就此返回,其呼叫棧也就此停止,Service可以選擇如何處理這一個Message。Messenger方式即可以在同一個程式之中,也可以跨程式實現真正的IPC。
    但是它的缺點也是相當的明顯的,就是它是傳送一個Message物件,而不是直接的函式呼叫,所以非常的不直觀,另外,Message物件也無法方便的攜帶過多的引數,如果超過一個物件,只能封裝打包成一個物件然後再放到Message.obj中。需要注意的是,如果是在同一個程式之中,Message可以攜帶任何物件,但如果是跨程式,則Message.obj中存放的必須是實現了Parcelable介面的物件,否則無法實現IPC,會有Exception。還有一個缺點,就是Message物件的標識(Message.what)必須是Client端和Service端都定義一致,否則就無法通訊,這在除錯的時候必須注意,因為這不會有編譯或者執行時錯誤,但就是無法正常工作,是比較隱蔽的Bug。

示例:
Service:
public class MessengerPrinterService extends Service {  
    static final int MSG_PRINT = 1;  
  
    private static final String TAG = "PrinterService";  
  
    private Handler mServiceHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            switch (msg.what) {  
                case MSG_PRINT:  
                    print("Freedom is nothing but a chance to be better!", (TextView) msg.obj);  
                    break;  
                default:  
                    super.handleMessage(msg);  
            }  
        }  
    };  
  
    final Messenger mMessenger = new Messenger(mServiceHandler);  
  
    @Override  
    public IBinder onBind(Intent intent) {  
        return mMessenger.getBinder();  
    }  
      
    public void print(String msg, TextView tv) {  
        try {  
            Log.e(TAG, "Preparing printer...");  
            if (tv != null) {  
                tv.setText("Preparing printer...");  
            }  
            Thread.sleep(1000);  
            Log.e(TAG, "Connecting printer...");  
            if (tv != null) {  
                tv.setText("Connecting printer...");  
            }  
            Thread.sleep(1000);  
            Log.e(TAG, "Printing.... " + msg);  
            if (tv != null) {  
                tv.setText("Printing.... ");  
            }  
            Thread.sleep(1000);  
            Log.e(TAG, "Done");  
        } catch (InterruptedException e) {  
        }  
        if (tv != null ) {  
            tv.setText(msg);  
        }  
        Toast.makeText(this, "Messenger Printing is done.", Toast.LENGTH_LONG).show();  
    }  

}  

 

Local client(in the same application):
public class MessengerClientActivity extends Activity {  
    Messenger mService = null;  
  
    private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            mService = new Messenger(service);  
        }  
  
        public void onServiceDisconnected(ComponentName className) {  
            mService = null;  
        }  
    };  
  
    public void onButtonClick(View v) {  
        if (mService == null) return;  
        // Create and send a message to the service, using a supported 'what' value  
        Message msg = Message.obtain(null, MessengerPrinterService.MSG_PRINT, 0, 0);  
        msg.obj = findViewById(R.id.status);  
        try {  
            mService.send(msg);  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
    }  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.printer_activity);  
        setTitle("Messenger client Activity");  
        ((Button) findViewById(R.id.play)).setText("Print via constructing Messenger");  
    }  
  
    @Override  
    protected void onStart() {  
        super.onStart();  
        bindService(new Intent(this, MessengerPrinterService.class), mConnection,  
            Context.BIND_AUTO_CREATE);  
    }  
  
    @Override  
    protected void onStop() {  
        super.onStop();  
        if (mService != null) {  
            unbindService(mConnection);  
        }  
    }  
}  
遠端的Client(在另外一個應用程式程式裡面):
public class AnotherMessengerClientActivity extends Activity {  
    private static final int MSG_PRINT = 1;  
  
    Messenger mService = null;  
  
    private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            mService = new Messenger(service);  
        }  
  
        public void onServiceDisconnected(ComponentName className) {  
            mService = null;  
        }  
    };  
  
    public void onButtonClick(View v) {  
        if (mService == null) return;  
        // Create and send a message to the service, using a supported 'what' value  
        Message msg = Message.obtain(null, MSG_PRINT, 0, 0);  
        try {  
            mService.send(msg);  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
    }  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.printer_activity);  
        setTitle("Another Messenger client Activity");  
        ((Button) findViewById(R.id.play)).setText("Print via constructing Messenger");  
    }  
  
    @Override  
    protected void onStart() {  
        super.onStart();  
        Intent intent = new Intent();  
        intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MessengerPrinterService");  
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);  
    }  
  
    @Override  
    protected void onStop() {  
        super.onStop();  
        if (mService != null) {  
            unbindService(mConnection);  
        }  
    }  
}  

3. 使用AIDL程式通訊
  這是最正統的IPC方式,實際上Messenger的跨程式通訊的底層也是通過AIDL來實現的。它的優點就是完全是為跨程式而設計的,介面由AIDL檔案指定,Client端和Service端都通過AIDL所描述的介面來進行呼叫和實現,不容易出錯。需要注意的是介面中的所有的物件都必須實現了Parcelable介面,即使僅僅是在同一個程式之中。它的缺點就是必須要在每一個想要通訊的地方定義一個完全一樣的AIDL檔案,否則IPC不會成功,而且它是嚴格的按照IPC的機制來的,所以即使是在同一程式之內,所有的介面的引數和返回值的物件必須是實現了Parcelable介面的,但Binder物件和Messenger就無此限制。需要一點:定義AIDL會嚴格按照IPC的方式程式即使是在同一個程式之中。所以,除非是真的要跨程式通訊,否則不要使用AIDL。

AIDL檔案:
interface PrinterInterface {  
    void print(String msg);  

 
AIDL Service:
public class AIDLPrinterService extends Service {  
    private static final String TAG = "AIDLPrinterService";  
    private Handler mHandler = new Handler();  
    @Override  
    public IBinder onBind(Intent intent) {  
        return mBinder;  
    }  
      
    private PrinterInterface.Stub mBinder = new PrinterInterface.Stub() {  
        @Override  
        public void print(String msg) throws RemoteException {  
            AIDLPrinterService.this.print(msg);  
        }  
    };  
      
    public void print(String msg) {  
        try {  
            Log.e(TAG, "Preparing printer...");  
            Thread.sleep(1000);  
            Log.e(TAG, "Connecting printer...");  
            Thread.sleep(1000);  
            Log.e(TAG, "Printing.... " + msg);  
            Thread.sleep(1000);  
            Log.e(TAG, "Done");  
        } catch (InterruptedException e) {  
        }  
        mHandler.post(new Runnable() {  
            @Override  
            public void run() {  
                Toast.makeText(AIDLPrinterService.this, "via AIDL Printing is done.", Toast.LENGTH_LONG).show();  
            }  
        });  
    }  

 
Local client:
public class AIDLClientActivity extends Activity {  
    private static final String TAG = "PrinterClientActivity";  
    PrinterInterface mService;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.printer_activity);  
        setTitle("Local AIDL client Activity");  
        ((Button) findViewById(R.id.play)).setText("Print via AIDL");  
    }  
  
    @Override  
    protected void onStart() {  
        super.onStart();  
        doBindService();  
    }  
  
    private void doBindService() {  
        Intent intent = new Intent(this, AIDLPrinterService.class);  
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);  
    }  
  
    @Override  
    protected void onStop() {  
        super.onStop();  
        doUnbindService();  
    }  
  
    private void doUnbindService() {  
        if (mService != null) {  
            unbindService(mConnection);  
        }  
    }  
      
    public void onButtonClick(View v) {  
        if (mService == null) {  
            Log.e(TAG, "what the fucl service is not ready");  
            return;  
        }  
        try {  
            mService.print("This message is from local client via AIDL interface");  
        } catch (RemoteException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
      
    private ServiceConnection mConnection = new ServiceConnection() {  
        @Override  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            mService = PrinterInterface.Stub.asInterface(service);  
        }  
  
        @Override  
        public void onServiceDisconnected(ComponentName arg0) {  
            mService = null;  
        }  
    };  

 
client in another application process:
public class AnotherAIDLClientActivity extends Activity {  
    private static final String TAG = "PrinterClientActivity";  
    PrinterInterface mService;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.printer_activity);  
        setTitle("Another AIDL client Activity");  
        ((Button) findViewById(R.id.play)).setText("Print via AIDL");  
    }  
  
    @Override  
    protected void onStart() {  
        super.onStart();  
        doBindService();  
    }  
  
    private void doBindService() {  
        Intent intent = new Intent();  
        intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.AIDLPrinterService");  
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);  
    }  
  
    @Override  
    protected void onStop() {  
        super.onStop();  
        doUnbindService();  
    }  
  
    private void doUnbindService() {  
    if (mService != null) {  
            unbindService(mConnection);  
        }  
    }  
      
    public void onButtonClick(View v) {  
        if (mService == null) {  
            Log.e(TAG, "what the fucl service is not ready");  
            return;  
        }  
        try {  
            mService.print("call PrinterService via AIDL from another application");  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
    }  
      
    private ServiceConnection mConnection = new ServiceConnection() {  
        @Override  
        public void onServiceConnected(ComponentName className, IBinder service) {  
            mService = PrinterInterface.Stub.asInterface(service);  
        }  
  
        @Override  
        public void onServiceDisconnected(ComponentName arg0) {  
            mService = null;  
        }  
    };  
}  

結論:
對比且總結了一下:如果是在同一個程式(應用程式)之中,且不涉及複雜的執行緒模式,直接使用Binder物件方式是最方便快捷的;如果是涉及跨程式操作,且不涉及複雜的執行緒模式就使用AIDL方式;無論是同一程式內還是跨程式,如果涉及比較複雜的執行緒模式就推薦使用Messenger的方式。

相關文章