1. 程式人生 > >【朝花夕拾】一篇文章搞懂Android跨程序通訊

【朝花夕拾】一篇文章搞懂Android跨程序通訊

前言

       只要是面試中高階工程師崗位,Android跨程序通訊就是最受面試官青睞的知識點。Android系統的執行由大量相互獨立的程序相互協助來完成的,所以Android程序間通訊問題,是做好Android開發高階工程師必須要跨過的一道坎。如果您還對這方面的知識還做不到如數家珍,那就和我一起來攻克它吧!

       本文主要包含了如下內容:

 

      

 

       其行文脈絡大致如下,希望能加深讀者對這方面內容的記憶:(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過很多了,這裡不多說了,能不用則不用。

    (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的基本實現步驟。

       如下兩個圖展示了該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 
 4 // Declare any non-default types here with import statements
 5 
 6 interface IDemoService {
 7 
 8     void setName(String name);
 9 
10     String getName();
11 }

 

  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     <intent-filter>
5          <action android:name="com.songwei.aidl" />
6     </intent-filter>
7 </service>

 

     這裡有幾點需要注意:

    (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的初級開發者,這也是我花這麼長的篇幅寫這個基礎內容的原因。

       另外,有需要原始碼可以下載這個demo,網盤地址:https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A   提取碼:4auk 。

 

七、AIDL的深入使用和理解

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

  1、Client端是如何實現呼叫Server端方法的

  2、AIDL支援的資料型別

  3、AIDL資料類序列化問題

  4、AIDL回撥的使用

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

    (1)在Server端IDemoService.aidl同一目錄中新增一個新的.aidl介面檔案,我這裡命名為IDemoCallback.aidl,內容如下:

1 // IDemoCallback.aidl
2 package com.songwei.aidldemoserver;
3 
4 // Declare any non-default types here with import statements
5 
6 interface IDemoCallback {
7     void testCallback(String msg);
8 }

      

    (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<IDemoCallback> 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行就是回撥資訊,表示回撥成功。

      原始碼地址連結: https://pan.baidu.com/s/1eI8chrxYTGqSaIaeMZEOYg  提取碼: 84cd

 

結語

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