天天看點

Android跨程序通信篇

前言

轉載請聲明,轉自【https://www.cnblogs.com/andy-songwei/p/10256379.html】,謝謝!

   隻要是面試進階工程師崗位,Android跨程序通信就是最受面試官青睐的知識點之一。Android系統的運作由大量互相獨立的程序互相協助來完成的,是以Android程序間通信問題,是做好Android開發進階工程師必須要跨過的一道坎。但是,我們是否真的清楚,Android中都有哪些方式實作跨程序通信呢?這些方式都有哪些優缺點?如何選擇這些通信方式?Binder是什麼?為什麼要引入Binder?Binder是這麼樣實作跨程序通信的?AIDL是什麼?AIDL和Binder又有什麼關系呢?......

   本文将對Android的跨程序通、程序内通信等方面做一些總結,以及比較詳細地介紹AIDL的使用,主要内容如下:



  



   其行文脈絡大緻如下,希望能加深讀者對這方面内容的記憶:(1)Android基于Linux系統,是以先說系統程序相關知識和Linux IPC。(2)總結Android的IPC,順帶總結了Android程序内元件之間的通信方式。(3)Android為了克服Linux IPC中的缺點,引入了Binder,是以對Binder做了一些宏觀上的介紹。(4)AIDL是實作Binder最常用的工具,是以詳細介紹了AIDL相關内容。
           

一、基礎知識簡介

在介紹Android跨程序通信之前,筆者先簡單啰嗦一下程序隔離、跨程序通信。

1、程序隔離

在作業系統中,程序與程序間的記憶體和資料都是不共享的。兩個程序就好像大海中互相獨立的兩個島嶼,各自生活在互相平行的兩個世界中,互不幹擾,各自為政。這樣做的目的,是為了避免程序間互相操作資料的現象發生,進而引起各自的安全問題。為了實作程序隔離,采用了虛拟位址空間,兩個程序各自的虛拟位址不同,從邏輯上來實作彼此間的隔離。
           

2、跨程序通信

馬克思主義哲學說,人是一切社會關系的總和。任何一個個體都不可能完全隔離于外界,都不可避免地與外界“互通有無”。程序也一樣,每一個程序完成的功能有限,就像現在的生成線一樣,往往就是隻完成某一類功能,而不是把所有事情都給做了,就這樣,每個程序就時不時需要與其他程序之間通信了。兩個程序之間要進行通信,就需要采用特殊的通信機制:程序間通信(IPC:Inter-Process Communication,即程序間通信或跨程序通信,後文以IPC替代,在此聲明)。
           

二、Linux跨程序通信

我們知道,Android系統就是基于Linux核心實作的,咱們先簡單了解一下Linux系統的IPC方式。雖然不同的資料對各種方式的名稱和種類說法不完全相同,但是主要來說有如下6種方式:(1)管道 Pipe;(2)信号Signal;(3)信号量Semaphore;(4)消息隊列Message Queue;(5)共享記憶體Shared Memmory;(6)套接字Socket。讀者若想深入了解這些方式,可以自行查閱,這裡不展開介紹,隻需要知道有這六種方式即可。
           

三、Android跨程序通信

Android IPC的方式在不同的資料中有不同的說法,但從大方向上看,可以歸納為如下四種(這裡僅對各種方式做簡單介紹和優劣對比,對具體如何使用,不做講解):
           

1、Activity方式

Activity是四大元件中使用最頻繁的,咱們先從它說起。使用Activity方式實作,就是使用startActivity()來啟動另外一個程序的Activity。

(1)場景

   我們在使用App的使用,往往會遇到如下幾種情形:(1)浏覽器中看到一篇比較不錯的文章,分享到微信朋友圈或者微網誌;(2)在某個App中點選某個網址,然後界面跳轉到浏覽器中進行閱讀;(3)使用美團外賣app,看到店家的電話,點選聯系商家時,跳轉到了電話撥打界面......這樣的操作再頻繁不過了。這些就是通過startActivity的方式從一個App,跳轉到了另外一個App的Activity,進而實作了跨程序通信。

(2)使用

   我們知道,在調用startActivity(Intent intent)的時候,intent有兩個類型:顯式Intent和隐式Intent。

   1)顯式Intent的使用方式如下,用于程序内元件間通信:
           

1 Intent intent = new Intent(this,OtherActivity.class);

2 startActivity(intent);

這種方式顯式地指定了要跳轉的Activtiy的class名稱,不知道是不是因為這個原因而被稱為顯式intent的,筆者沒有查證。這種方式用于程序内Activity的跳轉,是跨子產品間通信,而不是跨程序間通信。

2)隐式intent的使用方式如下,用于IPC:
           

1 Intent intent = new Intent();

2 intent.setAction(Intent.ACTION_CALL);

3 startActivity(intent);//startActivityForResult()同樣,這裡不贅述

Intent.ACTION_CALL就是字元串常量“android.intent.action.CALL”,這種方式通過setAction的方式來啟動目标app的Activity,上述代碼就是啟動電話app的撥号界面,有時候還可以帶上電話号碼等參數。

由上可知,Activity實作跨程序通信的方式,适合于不同App之間功能界面的跳轉。

2、Content provider(後面簡稱CP)方式

(1)場景   

   當我們開發App需要用到聯系人,多媒體資訊等資料的時候,往往會通過系統提供Uri,采用CP的方式去擷取。Android系統中,資料主要存儲在自帶的SqlLite資料庫中。應用要共享SqlLite中的資料給其他App操作(增、删、改、查),就要用到CP,也就是說,CP主要用于跨程序資料庫共享。Android系統提供了很多的CP來供其它App使用,如多媒體資訊、聯系人、月曆等。如下圖顯示了Android系統提供的CP,包名都是以"com.android.providers“開頭的:

   

  這些用于共享的資料其實都是存儲在系統資料庫中的,如下顯示了meida CP中的資料庫:

   

   App開發者也可以自定義CP,把自己的資料提供給其它app使用,也可以自己定義操作權限,如隻允許其它app讀取自己的資料,而不允許修改等。

(2)特點

  1)CP的使用場景,是提供資料共享。

  2)CP本質上還是在操作資料庫,資料存儲在sdcard中,是以建立連接配接和操作資料都是耗時操作,是以注意開辟子線程去操作。

  3)當資料庫中資料有變化時,Content Observer監聽到資料庫變化也是有一定的滞後。
           

3、Broadcase方式

Broadcast使用非常簡單,注冊好廣播,添加上action,就可以等着接收其他程序發出的廣播。發送和接收廣播時,還可以借助Intent來攜帶資料。但是廣播的使用存在很多問題,被很多程式員吐槽,甚至鄙夷,是以選擇用廣播進行跨程序通信,是下下策。下面盤點一下Broadcast的槽點:

(1)Broadcast是一種單向的通信方式。當一個程式發送廣播後,其他應用隻能被動地接收,無法向發送者回報。

(2)Broadcast非常消耗系統資源,會導緻系統性能下降。

(3)速度慢,容易造成系統ANR。且除了Parall Broadcast外,無法保證接收到的時間,甚至不一定能收得到。

(4)如果使用Ordered Broadcast,一個Receiver執行時間過長,會影響後面接收者的接收時間,甚至還有可能被中間某個Receiver攔截,導緻後面Receiver無法接收到。

(5)發送者無法确定誰會接收該廣播,而接收者也無發确認是誰發來的廣播。

(6)如果是靜态注冊的廣播,一個沒有開啟的程序,都有可能被該廣播激活。

  ......

  總而言之,言而總之,使用Broadcast來實作跨程序通信,是下下之策!
           

4、Service方式

啟動Service的方式有多種,有的用于跨程序通信,有的用于程序内部子產品之間的通信,下面僅簡單介紹一下跨程序通信的方式。

(1)startService()方式
           

1 Intent startIntent = new Intent ();

2 ComponentName componentName = new ComponentName(string packageName,string serviceClassName);

3 startIntent.setComponent(componentName );

4 startService( startIntent);

該方式啟動遠端Service實作跨程序通信,耦合度比較低,功能及代碼結構清晰,但是存在以下缺點:

1)沒有好的機制立即傳回執行結果,往往Service完成任務後,還需要其他方式向Client端回報。

  2)Service端無法識别Client端是誰,隻知道有啟動指令,但無法知道是誰下的指令。

  3)在已經建立好Service情況下,每次調用startService,就會執行onStartCommand()生命周期方法,相比于bindService,效率低下。

  4)如果Client端忘記調用stopService()了,那麼該Service會一直運作下去,這也是一個隐患。

  是以,針對上述缺點,往往建議startService()方式用于同一個App内,跨程序的方式用後面将要講到的AIDL來實作。
           

(2)bindService、Handler、Messager結合

這種方式也可以實作跨程序通信,據說和AIDL具有相同的功效,但比AIDL使用簡單,詳細可以閱讀博文【Android總結篇系列:Android Service】。但是從筆者工作這些年來看,從沒見過誰使用過這種方式,可能是筆者太孤陋寡聞了,這裡就不多介紹了。
           

(3)AIDL

這種方式也是bindService()啟動方式的一種使用情況,也是廣受程式員們推崇的方式。前面說startService()和Broadcast如何不好,就是為了襯托AIDL如何的好!這是本文的主角,本文後面會專門詳細講解,這裡不贅述。

   值得注意的是,從Android L開始,系統對Service的隐式啟動做了限制,需要至少包含包名或類名,具體可以檢視博文【Android 5.0之後隐式聲明Intent 啟動Service引發的問題】,是以隐式啟動Service時需要多注意這一點。
           

5、總結

從如上的介紹來看,其實Android中跨程序通信的實作,就是利用四大元件來實作的。對方式的選擇,我們總結一下:

(1)如果跨程序需要界面上的互動操作,用隐式startActivity()方式實作。

(2)如果需要共享資料,用Content Provider方式實作。

(3)排除前兩種情形,就用AIDL。

(4)僅僅為了完成功能,又确實不會用AIDL的,就用Broadcast吧!!!雖然很low,但比實作不了功能還是強多了
           

四、Android程序内通信

前面總結了幾種跨程序通信的方式,這裡順便總結一下Android程序内部元件之間的通信方式。

(1)顯示調用startActivity()。可以用Intent攜帶資料,而且如果用startActivityForResult(),還可以得到目标Activity完成任務後的回報。

(2)startService/bindService。用于與service通信,其中Intent,回調方法等會攜帶資料或者對象,選擇哪一個需要視情況而定。

(3)Boradcast。對于全局廣播,前面已經diss過很多了,這裡不多說了,能不用則不用。但是還有一種局部廣播——LocalBroadcastManager,隻能在app内部使用,也是一種程序内通信的方法,且在性能、安全等方面都要優于全局廣播,對于它的使用,可以參考我的另外一篇文章【【朝花夕拾】四大元件之(一)廣播篇】。

(4)Handler方式。這種方式在子線程和主線程通信中用得很普遍,跨子產品使用也常見到,使用友善。需要注意的是Looper和線程問題。

(5)回調。最普遍的方式了,使用也很友善。一般會結合面向接口程式設計來實作,如果調用的層次比較深,同一個接口注冊的地方太多,可讀性會比較差,經常看得頭暈。甚至有時候這些注冊回調的地方會互相幹擾。使用的時候要注意退出該子產品的時候,把注冊的回調變量置空銷毀。

(6)EventBus。使用簡單,耦合度低,代碼可讀性比較好,也受到很多開發者的喜愛。但也有一些缺點:1)需要導入第三方庫。2)發送消息出去後,被注冊的地方都會收到該消息,如果處理不當,開發者甚至不容易意識到哪些地方收到了消息并做了處理,帶來不必要的混亂。3)當注冊了EventBus的子產品退出後,容易忘記反注冊。

(7)SharePreference(簡稱SP)等資料存儲方式。這個就是資料的持久化和子產品間共享資料了,除了SP,還有網絡存儲,Sqilite資料庫,檔案存儲等多種方式,功能可以類比跨程序通信的ContentProvider。

  和跨程序通行方式選擇一樣,元件間的通信方式也要根據具體需要來确定了。從完成功能上講,前面講到的跨程序通信的方式,在程序内元件間通信也可以用,但是這樣就像用牛刀殺雞,大炮打蚊子了。出于性能方面的考慮,就不要将跨程序的方式用于程序内子產品間通信了。
           

五、Binder概述

Binder是Android架構中非常重要的一個機制,在Framework中被廣泛使用,了解Binder對閱讀Framework源碼有着很大的幫助。當然,在應用層等層面的跨程序通信中,也被廣泛使用。有人說,了解Binder,是跨入進階Android工程師行列的第一步,先不管是不是誇大其詞了,但足以說明Binder對于Android有多麼重要,也說明了解Binder有一定的難度。
           

1、Binder是什麼

Binder英文單詞的意思就是“粘合劑”,把兩個物體粘合在一起。說起Binder是什麼,在IPC過程中,從不同的角度來看,它可以被定義為一種機制,或者實體類,或者遠端代理,甚至是傳輸對象,咱們這裡說的Binder是從宏觀定義來看的,是說的Binder機制。在Android中,它是一種高效的IPC方式,是Android所特有的機制。它采用C/S架構模式,基于記憶體映射(mmap()),在系統核心空間将Client端和Service端兩個使用者空間的程序聯系在一起。
           

2、Binder的由來

Binder前身是某公司開發的OpenBinder項目,後來該項目的作者加入了Google,同時把該項目帶進了Android。從此以後,Binder就成為了Android中主要的跨程序通信機制。
           

3、Android為什麼要引入Binder

前面第二點提到,Linux已經具備了這麼多的IPC方式,為什麼Android中還要引入Binder呢?原因是從性能、穩定性和安全性三方面考慮,Linux本身的那些方式,往往隻能具備一部分因素,無法兼顧。但是Binder卻不然,從性能上看,隻需要一次資料拷貝,性能上僅次于共享記憶體;從穩定性上看,基于C/S架構,職責明确、架構清晰,穩定性好;從安全性上看,它為每個APP配置設定UID,而程序的UID是鑒别程序身份的重要标志。對于更詳細的對比,推薦閱讀【Android Bander設計與實作 - 設計篇的“引言”部分】,本文重點不是探索Linux,就不贅述了,咱們隻需要知道Binder比Linux其它IPC方式更給力就夠了。
           

4、Binder機制原理

Binder機制相關知識點和源碼非常龐雜,很多技術書籍往往要花很大篇幅來闡述,比如羅升陽的《Android 系統源碼情景分析》就花了100多頁來剖析它。俗話說,要想倒出一碗水,你得先有一桶水。很不好意思,筆者現在一碗水都不到,這裡就不獻醜了,推薦一篇講得比較好的博文:【寫給 Android 應用工程師的 Binder 原理剖析】,看完後收獲挺大的。
           

六、AIDL基本使用

前面我們講到,為了克服Linux中IPC各種方式的缺點,在Android中引入了Binder機制。但是當說起Binder在Android中的使用時,幾乎所有的資料都是在說AIDL的使用。AIDL的全稱是Android Interface Definition Language,即Android接口定義語言,是Binder機制實作Android IPC時使用比較廣泛的工具。本節将以一個Demo示範一下AIDL的基本實作步驟。

  源碼位址:https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A   提取碼:4auk 。

  如下兩個圖展示了該Demo的結構圖和AIDL關鍵檔案:

      

                                        圖6.1                                                                                     圖6.2
           

1、建立兩個App,分别為Client端和Server端。

這個比較 好了解,Server端就是包含了Service真正幹活的那一端;Client端就是通過遠端操控指揮的那一端,分别在不同的App中。如下圖所示:
           

2、在Server端main目錄下建立aidl檔案夾以及.aidl檔案,并copy一份到Client端,如圖6.1中②處所示結構。注意,Client端和Server端②處是一模一樣的。另外,AS中提供了快捷方式建立aidl檔案,在main處點選右鍵 > New > AIDL > AIDL File檔案,按照提示給aidl檔案命名即可自動建立完成,可以看到檔案路徑也是該項目的包名。

這裡給aidl命名為IDemoService.aidl,這裡需要注意的是命名規範,一般都是以“I”開頭,表示是一個接口,其内容如下:

複制代碼

1 //========== IDemoService.aidl========

2 package com.songwei.aidldemoserver;

3 // Declare any non-default types here with import statements

4 interface IDemoService {

5 void setName(String name);

6 String getName();

7 }

複制代碼

3、Server端建立Service檔案 AidlService.java,如圖6.1中③處所示,代碼如下:

View Code

為了下文分析流程及生命周期,在其中各個方法中都添加了Log。

同時,在Server端的AndroidManifest.xml檔案中添加該Service的注冊資訊。
           

複制代碼

1 <service

2 android:name=".AidlService"

3 android:exported=“true”>

4

5

6

7

複制代碼

這裡有幾點需要注意:

(1)exported屬性值,如果有“intent-filter”,則預設值為true,否則為false。是以這裡其實可以去掉,因為有“intent-filter”,其預設值就是true。

(2)由于筆者在後面啟動該service的時候用的action的方式,是以這裡就有了“intent-filter”裡面的action。如果用其他方式啟動,這個service的注冊資訊就需要相應的改動了,有一定開發經驗的讀者應該都知道,就不展開講了,主要是怕讀者容易忽略這裡,是以特别提醒一下。
           

4、編譯Sever端和Client端App,生成IDemoService.java檔案。

當編譯的時候,AS會自動為我們生成IDemoService.java檔案,如圖6.1和圖6.2中④處所示。當你打開該檔案的時候,是不是看到了如下場景?

   

   驚不驚喜?意不意外?是不是一臉懵逼,大驚,卧槽,這尼瑪啥玩意啊?

   AIDL是Android接口定義語言,IDemoService.java是一個java中的interface(接口),現在是不是若有所思了呢?AIDL正是定義了IDemoService.java這個接口!!! 這個接口檔案就是AIDL幫助咱們生成的Binder相關代碼,這些代碼就是用來幫助實作Client端和Server端通信的。前面第2步中提到的IDemoService.aidl檔案,其作用就是作為原料通過AIDL來生成這些你貌似看不懂的代碼的,第3步中的AidlService.java和後續在Client端App連接配接Server端App的時候,其實這個aidl檔案就從來沒有出現過,也就是說,它已經沒有什麼價值了。是以說,AIDL的作用就是用來自動生成Binder相關接口代碼的,而不需要開發者手動編寫。有些教程中說,可以不使用AIDL而手動編寫這份Binder代碼,AIDL不是Binder實作通信所必需的,筆者也沒有嘗試過手動編寫,如果讀者您想挑戰,可以嘗試一下!

   咱們繼續!

   打開IDemoService.java檔案後,點選主菜單蘭Code > Reformat Code (或 Ctrl + Alt +L快捷健),你會發現畫面變成了下面這個樣子:
           

View Code

驚不驚喜?意不意外?這下是不是有種似曾相識的趕腳?這就是一個很普通的java中的接口檔案而已,結構也非常簡單。

神秘的面紗揭開一層了吧!後面在講完Client端和Server端的連接配接及通信後,還會繼續深入剖析這個檔案。
           

5、Client端ClientActivity連接配接Server端AidlService并通信

ClientActivity.java的内容如下,布局檔案在此省略,比較簡單,就兩個按鈕,一個用于綁定,一個用于解綁,看Button命名也很容易分辨。
           

View Code

代碼中對一些關鍵和容易忽略的地方做了注釋,可以結合起來進行了解。

6、運作

運作的時候,需要先啟動Service端程序,才能在Client端中點選“綁定”的時候綁定成功。完成一次“綁定”和“解綁”,得到的log如下所示:
           

複制代碼

1 01-08 15:29:43.109 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate]

2 01-08 15:29:43.110 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onBind]

3 01-08 15:29:43.113 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]componentName=ComponentInfo{com.songwei.aidldemoserver/com.songwei.aidldemoserver.AidlService}

4 01-08 15:29:43.114 13532-13547/com.songwei.aidldemoserver I/aidlDemo: server:[setName]

5 01-08 15:29:43.114 13532-13546/com.songwei.aidldemoserver I/aidlDemo: server:[getName]

6 01-08 15:29:43.114 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song

7 01-08 15:36:07.570 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceDisconnected]

複制代碼

可以結合前面的ClientActivity.java和AidlService.java代碼中的添加的log,來了解一下這個流程。當然,最好是能夠按照上面的步驟,親自動手實作一遍,比看10遍更有效果。

本節對介紹了AIDL最基本的知識,出于項目性質的原因,可能有些Android開發人員工作了好幾年都不一定需要完整寫一個AIDL實作兩個App通信的功能。筆者就是這樣,在前幾年的工作中,雖然很早就知道AIDL這個東西,但确實是項目中就沒有寫過這個功能,直到最近兩年。是以即便是讀者您有不少開發經驗了,也可能和筆者一樣是個AIDL的初級開發者,這也是我花這麼長的篇幅寫這個基礎内容的原因。
           

七、AIDL的深入使用和了解

前面一節講了AIDL最進本的知識,這一節中将會結合更複雜的場景,更深入地介紹AIDL。(這一節的内容會持續補充完整)
           

1、Client端是如何實作調用Server端方法的

2、AIDL支援的資料類型

3、AIDL資料類序列化問題

4、AIDL回調的使用

當Server端某個操作執行完後,需要通知Client端自己完成了任務,這個時候回調就帶來了很大的便利,和在同一個App中使用回調效果一樣。例如在上一節的例子中,Server完成了setName()這個操作(耗時的異步操作更能展現回調的作用)後,要通知Client端自己完成了任務,可以進行下一步的操作了,就是這樣一個場景。現在在前面AIDL例子基礎上,對回調的使用步驟進行說明。

   源碼位址連結: https://pan.baidu.com/s/1eI8chrxYTGqSaIaeMZEOYg  提取碼: 84cd 。

(1)在Server端IDemoService.aidl同一目錄中添加一個新的.aidl接口檔案,我這裡命名為IDemoCallback.aidl,内容如下:
           

1 // =IDemoCallback.aidl==

2 package com.songwei.aidldemoserver;

3 // Declare any non-default types here with import statements

4 interface IDemoCallback {

5 void testCallback(String msg);

6 }

(2)在IDemoService.aidl中添加注冊/反注冊兩個方法

複制代碼

1 // =IDemoService.aidl

2 package com.songwei.aidldemoserver;

3

4 // Declare any non-default types here with import statements

5 import com.songwei.aidldemoserver.IDemoCallback;

6

7 interface IDemoService {

8

9 void setName(String name);

10

11 String getName();

12

13 void registerCallback(IDemoCallback cb);

14

15 void unregisterCallback(IDemoCallback cb);

16 }

複制代碼

第13行和第15行為新增的方法。将這兩個.aidl檔案同步到Client端,使C/S兩端的aidl檔案完全一樣,均為

最好将兩個app都編譯一遍,這樣後面有些地方可以用代碼補全,而不用手動書寫。當然在AidlService.java中實作接口的時候肯定會報錯的,把新增的方法補上就可以了。

(3)在AidlService.java中添加如下加粗部分的代碼,
           

複制代碼

1 private RemoteCallbackList mCallbacks = new RemoteCallbackList<>();

2

3 private void callback(String msg) {

4 int N = mCallbacks.beginBroadcast();

5 for (int i = 0; i < N; i++) {

6 try {

7 mCallbacks.getBroadcastItem(i).testCallback(msg);

8 } catch (RemoteException e) {

9 e.printStackTrace();

10 }

11 }

12 mCallbacks.finishBroadcast();

13 }

14

15 class MyBinder extends IDemoService.Stub {

16 private String mName = “”;

17

18 public void setName(String name) throws RemoteException {

19 Log.i(TAG, “server:[setName]”);

20 mName = name;

21 callback("‘Andy song’ is setted");

22 }

23

24 @Override

25 public String getName() throws RemoteException {

26 Log.i(TAG, “server:[getName]”);

27 return mName;

28 }

29

30 @Override

31 public void registerCallback(IDemoCallback cb) throws RemoteException {

32 Log.i(TAG,“server:[registerCallback]”);

33 if(cb != null){

34 mCallbacks.register(cb);

35 }

36 }

37

38 @Override

39 public void unregisterCallback(IDemoCallback cb) throws RemoteException {

40 Log.i(TAG,“server:[unregisterCallback]”);

41 if(cb != null){

42 mCallbacks.unregister(cb);

43 }

44 }

45

46 }

複制代碼

RemoteCallbackList是系統提供的一個用于存儲回調對象的清單,其對象mCallbacks用于存儲注冊的IDemoCallback對象。通過第3行的callback()方法中的内容,我們可以推測它是采用一種類似于Broadcast的方式來實作回調的。

當setName()方法執行完畢後,callback("‘Andy song’ is setted");就會把回調資訊回報給Client中注冊該回調的地方了。

(4)在ClientActivity.java中注冊回調并處理相關邏輯

複制代碼

1 …

2 public void onServiceConnected(ComponentName componentName, IBinder binder) {

3 //Log.i(TAG, “client:[onServiceConnected]componentName=” + componentName);

4 mIsBinded = true;

5 //得到一個遠端Service中的Binder代理,而不是該Binder執行個體

6 mDemoService = IDemoService.Stub.asInterface(binder);

7 try {

8 mDemoService.registerCallback(mDemoCallback);

9 //遠端控制設定name值

10 mDemoService.setName(“Andy Song”);

11 //遠端擷取設定的name值

12 String myName = mDemoService.getName();

13 Log.i(TAG, “client:[onServiceConnected]myName=” + myName);

14

15 } catch (RemoteException e) {

16 e.printStackTrace();

17 }

18 …

19 private IDemoCallback mDemoCallback = new IDemoCallback.Stub() {

20 @Override

21 public void testCallback(String msg) throws RemoteException {

22 Log.i(TAG, “client:[testCallback] msg=” + msg);

23 }

24 };

25 …

26 private void unRegisterCallback() {

27 try {

28 mDemoService.unregisterCallback(mDemoCallback);

29 } catch (RemoteException e) {

30 e.printStackTrace();

31 }

32 }

33 …

複制代碼

在解綁定的地方調用unRegisterCallback()反注冊回調即可,這樣就完成了代碼整個代碼的編寫。這裡需要注意注冊回調的時機,一定要在setName()執行前注冊,否則Client端收不到回調資訊。

(5)運作C/S端,然後“綁定”/“解綁”,就會看到如下log資訊:
           

複制代碼

1 01-11 14:11:44.714 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate]

2 01-11 14:11:44.715 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onBind]

3 01-11 14:11:44.720 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[registerCallback]

4 01-11 14:11:44.720 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[setName]

5 01-11 14:11:44.721 6572-6572/com.songwei.aidldemoclient I/aidlDemo: client:[testCallback] msg=‘Andy song’ is setted

6 01-11 14:11:44.722 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[getName]

7 01-11 14:11:44.722 6572-6572/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song

8 01-11 14:11:58.589 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[unregisterCallback]

9 01-11 14:11:58.616 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onUnbind]

10 01-11 14:11:58.617 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onDestroy]

複制代碼

第5行就是回調資訊,表示回調成功。

結語

本文主要是筆者用來整理跨程序通信的知識點,以及複習近兩年才真正會用的AIDL,是以詳略上是前半部分略,後半部分詳,完全是按照筆者對知識點掌握程度來行文的。讀者在閱讀中,如果有些部分因為寫得太簡略而看得不過瘾,隻能自己去查更詳細的資料了;如果有些部分因為寫得太詳細而嫌啰嗦,完全可以跳着看;如果有些知識點因為筆者經驗和水準問題寫得有誤或者闡述欠妥,請不吝賜教,萬分感激!!!