前言html
轉載請聲明,轉自【http://www.javashuo.com/article/p-umuauqar-g.html】,謝謝!java
只要是面試高級工程師崗位,Android跨進程通訊就是最受面試官青睞的知識點之一。Android系統的運行由大量相互獨立的進程相互協助來完成的,因此Android進程間通訊問題,是作好Android開發高級工程師必需要跨過的一道坎。可是,咱們是否真的清楚,Android中都有哪些方式實現跨進程通訊呢?這些方式都有哪些優缺點?如何選擇這些通訊方式?Binder是什麼?爲何要引入Binder?Binder是這麼樣實現跨進程通訊的?AIDL是什麼?AIDL和Binder又有什麼關係呢?......android
本文將對Android的跨進程通、進程內通訊等方面作一些總結,以及比較詳細地介紹AIDL的使用,主要內容以下:程序員
其行文脈絡大體以下,但願能加深讀者對這方面內容的記憶:(1)Android基於Linux系統,因此先說系統進程相關知識和Linux IPC。(2)總結Android的IPC,順帶總結了Android進程內組件之間的通訊方式。(3)Android爲了克服Linux IPC中的缺點,引入了Binder,因此對Binder作了一些宏觀上的介紹。(4)AIDL是實現Binder最經常使用的工具,因此詳細介紹了AIDL相關內容。面試
1、基礎知識簡介數據庫
在介紹Android跨進程通訊以前,筆者先簡單囉嗦一下進程隔離、跨進程通訊。編程
一、進程隔離瀏覽器
在操做系統中,進程與進程間的內存和數據都是不共享的。兩個進程就好像大海中相互獨立的兩個島嶼,各自生活在互相平行的兩個世界中,互不干擾,各自爲政。這樣作的目的,是爲了不進程間相互操做數據的現象發生,從而引發各自的安全問題。爲了實現進程隔離,採用了虛擬地址空間,兩個進程各自的虛擬地址不一樣,從邏輯上來實現彼此間的隔離。安全
二、跨進程通訊微信
馬克思主義哲學說,人是一切社會關係的總和。任何一個個體都不可能徹底隔離於外界,都不可避免地與外界「互通有無」。進程也同樣,每個進程完成的功能有限,就像如今的生成線同樣,每每就是隻完成某一類功能,而不是把全部事情都給作了,就這樣,每一個進程就時不時須要與其餘進程之間通訊了。兩個進程之間要進行通訊,就須要採用特殊的通訊機制:進程間通訊(IPC:Inter-Process Communication,即進程間通訊或跨進程通訊,後文以IPC替代,在此聲明)。
2、Linux跨進程通訊
咱們知道,Android系統就是基於Linux內核實現的,我們先簡單瞭解一下Linux系統的IPC方式。雖然不一樣的資料對各類方式的名稱和種類說法不徹底相同,可是主要來講有以下6種方式:(1)管道 Pipe;(2)信號Signal;(3)信號量Semaphore;(4)消息隊列Message Queue;(5)共享內存Shared Memmory;(6)套接字Socket。讀者若想深刻了解這些方式,能夠自行查閱,這裏不展開介紹,只須要知道有這六種方式便可。
3、Android跨進程通訊
Android IPC的方式在不一樣的資料中有不一樣的說法,但從大方向上看,能夠概括爲以下四種(這裏僅對各類方式作簡單介紹和優劣對比,對具體如何使用,不作講解):
一、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之間功能界面的跳轉。
二、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監聽到數據庫變化也是有必定的滯後。
三、Broadcase方式
Broadcast使用很是簡單,註冊好廣播,添加上action,就能夠等着接收其餘進程發出的廣播。發送和接收廣播時,還能夠藉助Intent來攜帶數據。可是廣播的使用存在不少問題,被不少程序員吐槽,甚至鄙夷,因此選擇用廣播進行跨進程通訊,是下下策。下面盤點一下Broadcast的槽點:
(1)Broadcast是一種單向的通訊方式。當一個程序發送廣播後,其餘應用只能被動地接收,沒法向發送者反饋。
(2)Broadcast很是消耗系統資源,會致使系統性能降低。
(3)速度慢,容易形成系統ANR。且除了Parall Broadcast外,沒法保證接收到的時間,甚至不必定能收穫得。
(4)若是使用Ordered Broadcast,一個Receiver執行時間過長,會影響後面接收者的接收時間,甚至還有可能被中間某個Receiver攔截,致使後面Receiver沒法接收到。
(5)發送者沒法肯定誰會接收該廣播,而接收者也無發確認是誰發來的廣播。
(6)若是是靜態註冊的廣播,一個沒有開啓的進程,都有可能被該廣播激活。
......
總而言之,言而總之,使用Broadcast來實現跨進程通訊,是下下之策!
四、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時須要多注意這一點。
五、總結
從如上的介紹來看,其實Android中跨進程通訊的實現,就是利用四大組件來實現的。對方式的選擇,咱們總結一下:
(1)若是跨進程須要界面上的交互操做,用隱式startActivity()方式實現。
(2)若是須要共享數據,用Content Provider方式實現。
(3)排除前兩種情形,就用AIDL。
(4)僅僅爲了完成功能,又確實不會用AIDL的,就用Broadcast吧!!!雖然很low,但比實現不了功能仍是強多了
4、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。
和跨進程通行方式選擇同樣,組件間的通訊方式也要根據具體須要來肯定了。從完成功能上講,前面講到的跨進程通訊的方式,在進程內組件間通訊也能夠用,可是這樣就像用牛刀殺雞,大炮打蚊子了。出於性能方面的考慮,就不要將跨進程的方式用於進程內模塊間通訊了。
5、Binder概述
Binder是Android框架中很是重要的一個機制,在Framework中被普遍使用,理解Binder對閱讀Framework源碼有着很大的幫助。固然,在應用層等層面的跨進程通訊中,也被普遍使用。有人說,理解Binder,是跨入高級Android工程師行列的第一步,先無論是否是誇大其詞了,但足以說明Binder對於Android有多麼重要,也說明理解Binder有必定的難度。
一、Binder是什麼
Binder英文單詞的意思就是「粘合劑」,把兩個物體粘合在一塊兒。提及Binder是什麼,在IPC過程當中,從不一樣的角度來看,它能夠被定義爲一種機制,或者實體類,或者遠程代理,甚至是傳輸對象,我們這裏說的Binder是從宏觀定義來看的,是說的Binder機制。在Android中,它是一種高效的IPC方式,是Android所特有的機制。它採用C/S架構模式,基於內存映射(mmap()),在系統內核空間將Client端和Service端兩個用戶空間的進程聯繫在一塊兒。
二、Binder的由來
Binder前身是某公司開發的OpenBinder項目,後來該項目的做者加入了Google,同時把該項目帶進了Android。今後之後,Binder就成爲了Android中主要的跨進程通訊機制。
三、Android爲何要引入Binder
前面第二點提到,Linux已經具有了這麼多的IPC方式,爲何Android中還要引入Binder呢?緣由是從性能、穩定性和安全性三方面考慮,Linux自己的那些方式,每每只能具有一部分因素,沒法兼顧。可是Binder卻否則,從性能上看,只須要一次數據拷貝,性能上僅次於共享內存;從穩定性上看,基於C/S架構,職責明確、架構清晰,穩定性好;從安全性上看,它爲每一個APP分配UID,而進程的UID是鑑別進程身份的重要標誌。對於更詳細的對比,推薦閱讀【Android Bander設計與實現 - 設計篇的「引言」部分】,本文重點不是探索Linux,就不贅述了,我們只須要知道Binder比Linux其它IPC方式更給力就夠了。
四、Binder機制原理
Binder機制相關知識點和源碼很是龐雜,不少技術書籍每每要花很大篇幅來闡述,好比羅昇陽的《Android 系統源碼情景分析》就花了100多頁來剖析它。俗話說,要想倒出一碗水,你得先有一桶水。很很差意思,筆者如今一碗水都不到,這裏就不獻醜了,推薦一篇講得比較好的博文:【寫給 Android 應用工程師的 Binder 原理剖析】,看完後收穫挺大的。
6、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
一、創建兩個App,分別爲Client端和Server端。
這個比較 好理解,Server端就是包含了Service真正幹活的那一端;Client端就是經過遠程操控指揮的那一端,分別在不一樣的App中。以下圖所示:
二、在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 }
三、Server端建立Service文件 AidlService.java,如圖6.1中③處所示,代碼以下:
1 package com.songwei.aidldemoserver; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.os.RemoteException; 7 import android.util.Log; 8 9 public class AidlService extends Service { 10 private final static String TAG = "aidlDemo"; 11 12 public AidlService() { 13 } 14 15 @Override 16 public void onCreate() { 17 super.onCreate(); 18 Log.i(TAG, "server:[onCreate]"); 19 } 20 21 @Override 22 public IBinder onBind(Intent intent) { 23 // TODO: Return the communication channel to the service. 24 Log.i(TAG, "server:[onBind]"); 25 return new MyBinder(); 26 } 27 28 @Override 29 public boolean onUnbind(Intent intent) { 30 Log.i(TAG, "server:[onUnbind]"); 31 return super.onUnbind(intent); 32 } 33 34 @Override 35 public void onDestroy() { 36 super.onDestroy(); 37 Log.i(TAG, "server:[onDestroy]"); 38 } 39 40 class MyBinder extends IDemoService.Stub { 41 private String mName = ""; 42 43 public void setName(String name) throws RemoteException{ 44 Log.i(TAG, "server:[setName]"); 45 mName = name; 46 } 47 48 @Override 49 public String getName() throws RemoteException { 50 Log.i(TAG, "server:[getName]"); 51 return mName; 52 } 53 } 54 }
爲了下文分析流程及生命週期,在其中各個方法中都添加了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的註冊信息就須要相應的改動了,有必定開發經驗的讀者應該都知道,就不展開講了,主要是怕讀者容易忽略這裏,因此特別提醒一下。
四、編譯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快捷健),你會發現畫面變成了下面這個樣子:
1 /* 2 * This file is auto-generated. DO NOT MODIFY. 3 * Original file: D:\\ASWorkspace\\testDemo\\aidldemoclient\\src\\main\\aidl\\com\\songwei\\aidldemoserver\\IDemoService.aidl 4 */ 5 package com.songwei.aidldemoserver; 6 7 public interface IDemoService extends android.os.IInterface { 8 /** 9 * Local-side IPC implementation stub class. 10 */ 11 public static abstract class Stub extends android.os.Binder implements com.songwei.aidldemoserver.IDemoService { 12 private static final java.lang.String DESCRIPTOR = "com.songwei.aidldemoserver.IDemoService"; 13 14 /** 15 * Construct the stub at attach it to the interface. 16 */ 17 public Stub() { 18 this.attachInterface(this, DESCRIPTOR); 19 } 20 21 /** 22 * Cast an IBinder object into an com.songwei.aidldemoserver.IDemoService interface, 23 * generating a proxy if needed. 24 */ 25 public static com.songwei.aidldemoserver.IDemoService asInterface(android.os.IBinder obj) { 26 if ((obj == null)) { 27 return null; 28 } 29 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 30 if (((iin != null) && (iin instanceof com.songwei.aidldemoserver.IDemoService))) { 31 return ((com.songwei.aidldemoserver.IDemoService) iin); 32 } 33 return new com.songwei.aidldemoserver.IDemoService.Stub.Proxy(obj); 34 } 35 36 @Override 37 public android.os.IBinder asBinder() { 38 return this; 39 } 40 41 @Override 42 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { 43 switch (code) { 44 case INTERFACE_TRANSACTION: { 45 reply.writeString(DESCRIPTOR); 46 return true; 47 } 48 case TRANSACTION_setName: { 49 data.enforceInterface(DESCRIPTOR); 50 java.lang.String _arg0; 51 _arg0 = data.readString(); 52 this.setName(_arg0); 53 reply.writeNoException(); 54 return true; 55 } 56 case TRANSACTION_getName: { 57 data.enforceInterface(DESCRIPTOR); 58 java.lang.String _result = this.getName(); 59 reply.writeNoException(); 60 reply.writeString(_result); 61 return true; 62 } 63 } 64 return super.onTransact(code, data, reply, flags); 65 } 66 67 private static class Proxy implements com.songwei.aidldemoserver.IDemoService { 68 private android.os.IBinder mRemote; 69 70 Proxy(android.os.IBinder remote) { 71 mRemote = remote; 72 } 73 74 @Override 75 public android.os.IBinder asBinder() { 76 return mRemote; 77 } 78 79 public java.lang.String getInterfaceDescriptor() { 80 return DESCRIPTOR; 81 } 82 83 @Override 84 public void setName(java.lang.String name) throws android.os.RemoteException { 85 android.os.Parcel _data = android.os.Parcel.obtain(); 86 android.os.Parcel _reply = android.os.Parcel.obtain(); 87 try { 88 _data.writeInterfaceToken(DESCRIPTOR); 89 _data.writeString(name); 90 mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0); 91 _reply.readException(); 92 } finally { 93 _reply.recycle(); 94 _data.recycle(); 95 } 96 } 97 98 @Override 99 public java.lang.String getName() throws android.os.RemoteException { 100 android.os.Parcel _data = android.os.Parcel.obtain(); 101 android.os.Parcel _reply = android.os.Parcel.obtain(); 102 java.lang.String _result; 103 try { 104 _data.writeInterfaceToken(DESCRIPTOR); 105 mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0); 106 _reply.readException(); 107 _result = _reply.readString(); 108 } finally { 109 _reply.recycle(); 110 _data.recycle(); 111 } 112 return _result; 113 } 114 } 115 116 static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 117 static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 118 } 119 120 public void setName(java.lang.String name) throws android.os.RemoteException; 121 122 public java.lang.String getName() throws android.os.RemoteException; 123 124 }
驚不驚喜?意不意外?這下是否是有種似曾相識的趕腳?這就是一個很普通的java中的接口文件而已,結構也很是簡單。
神祕的面紗揭開一層了吧!後面在講完Client端和Server端的鏈接及通訊後,還會繼續深刻剖析這個文件。
五、Client端ClientActivity鏈接Server端AidlService並通訊
ClientActivity.java的內容以下,佈局文件在此省略,比較簡單,就兩個按鈕,一個用於綁定,一個用於解綁,看Button命名也很容易分辨。
1 package com.songwei.aidldemoclient; 2 3 import android.content.ComponentName; 4 import android.content.Intent; 5 import android.content.ServiceConnection; 6 import android.os.IBinder; 7 import android.os.RemoteException; 8 import android.support.v7.app.AppCompatActivity; 9 import android.os.Bundle; 10 import android.util.Log; 11 import android.view.View; 12 import android.widget.Button; 13 14 import com.songwei.aidldemoserver.IDemoService; 15 16 public class ClientActivity extends AppCompatActivity { 17 18 private final static String TAG = "aidlDemo"; 19 private Button mBindBtn, mUnBindBtn; 20 private IDemoService mDemoService; 21 private boolean mIsBinded = false; 22 private ServiceConnection mConn = new ServiceConnection() { 23 //當與遠程Service綁定後,會回調該方法。 24 @Override 25 public void onServiceConnected(ComponentName componentName, IBinder binder) { 26 Log.i(TAG, "client:[onServiceConnected]componentName=" + componentName); 27 mIsBinded = true; 28 //獲得一個遠程Service中的Binder代理,而不是該Binder實例 29 mDemoService = IDemoService.Stub.asInterface(binder); 30 try { 31 //遠程控制設置name值 32 mDemoService.setName("Andy Song"); 33 //遠程獲取設置的name值 34 String myName = mDemoService.getName(); 35 Log.i(TAG, "client:[onServiceConnected]myName=" + myName); 36 } catch (RemoteException e) { 37 e.printStackTrace(); 38 } 39 } 40 41 //該回調方法通常不會調用,若是在解綁的時候,發現該方法沒有調用,不要驚慌,由於該方法的調用時機是Service被意外銷燬時,好比內存不足時。 42 @Override 43 public void onServiceDisconnected(ComponentName name) { 44 Log.i(TAG, "client:[onServiceDisconnected]"); 45 mIsBinded = false; 46 mDemoService = null; 47 } 48 }; 49 50 @Override 51 protected void onCreate(Bundle savedInstanceState) { 52 super.onCreate(savedInstanceState); 53 setContentView(R.layout.activity_main); 54 mBindBtn = (Button) findViewById(R.id.btn_bind); 55 mBindBtn.setOnClickListener(new View.OnClickListener() { 56 @Override 57 public void onClick(View v) { 58 //Android5.0及之後,出於對安全的考慮,Android系統對隱式啓動Service作了限制,須要帶上包名或者類名,這一點須要注意。 59 Intent intent = new Intent(); 60 intent.setAction("com.songwei.aidl"); 61 intent.setPackage("com.songwei.aidldemoserver"); 62 bindService(intent, mConn, BIND_AUTO_CREATE); 63 } 64 }); 65 mUnBindBtn = (Button) findViewById(R.id.btn_unbind); 66 mUnBindBtn.setOnClickListener(new View.OnClickListener() { 67 @Override 68 public void onClick(View v) { 69 //解除綁定,當調用unbindService時,必定要判斷當前service是不是binded的,若是沒有,就會報錯。 70 if (mIsBinded) { 71 unbindService(mConn); 72 mDemoService = null; 73 mIsBinded = false; 74 } 75 } 76 }); 77 } 78 }
代碼中對一些關鍵和容易忽略的地方作了註釋,能夠結合起來進行理解。
六、運行
運行的時候,須要先啓動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的初級開發者,這也是我花這麼長的篇幅寫這個基礎內容的緣由。
7、AIDL的深刻使用和理解
前面一節講了AIDL最進本的知識,這一節中將會結合更復雜的場景,更深刻地介紹AIDL。(這一節的內容會持續補充完整)
一、Client端是如何實現調用Server端方法的
二、AIDL支持的數據類型
三、AIDL數據類序列化問題
四、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<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行就是回調信息,表示回調成功。
結語
本文主要是筆者用來整理跨進程通訊的知識點,以及複習近兩年才真正會用的AIDL,因此詳略上是前半部分略,後半部分詳,徹底是按照筆者對知識點掌握程度來行文的。讀者在閱讀中,若是有些部分由於寫得太簡略而看得不過癮,只能本身去查更詳細的資料了;若是有些部分由於寫得太詳細而嫌囉嗦,徹底能夠跳着看;若是有些知識點由於筆者經驗和水平問題寫得有誤或者闡述欠妥,請不吝賜教,萬分感激!!!