【朝花夕拾】Android性能篇之(七)Android跨進程通訊篇

前言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 }
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的註冊信息就須要相應的改動了,有必定開發經驗的讀者應該都知道,就不展開講了,主要是怕讀者容易忽略這裏,因此特別提醒一下。

  四、編譯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 }
View Code

      驚不驚喜?意不意外?這下是否是有種似曾相識的趕腳?這就是一個很普通的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 }
View Code

    代碼中對一些關鍵和容易忽略的地方作了註釋,能夠結合起來進行理解。

  六、運行

       運行的時候,須要先啓動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,因此詳略上是前半部分略,後半部分詳,徹底是按照筆者對知識點掌握程度來行文的。讀者在閱讀中,若是有些部分由於寫得太簡略而看得不過癮,只能本身去查更詳細的資料了;若是有些部分由於寫得太詳細而嫌囉嗦,徹底能夠跳着看;若是有些知識點由於筆者經驗和水平問題寫得有誤或者闡述欠妥,請不吝賜教,萬分感激!!!

相關文章
相關標籤/搜索