Android面試一天一題(Day 36:AIDL)

上一章節,我們講到了Android中的Binder機制,一個Android開發天天用到,但又不明就理的神密存在。這一節接著Binder這個話題,講一講AIDL,讓大家對Binder機制有一個直觀的認識。

面試題:AIDL是什么?你有使用過它嗎,它支持哪些數據類型?

AIDL是Android Interface Definition Language的簡寫,即Android接口定義語言。我們知道Android系統為每一個應用開啟一個獨立的虛擬機,每個應用都運行在各自進程里(默認情況下),彼此之間相互獨立,無法共享內存。當一個應用想要訪問另一個應用的數據或調用其方法,就要用到Android系統提供的IPC機制。而AIDL就是Android實現IPC機制的方式之一。

除了AIDL,Android還提供了Messenger來實現跨進程通信,不過Messenger是以單線程串行方式(消息隊列)來處理來自不同客戶端的訪問的,并不適合多線程并發訪問。當需要提供跨進程以及多線程并發服務時就需要AIDL上場了。

Messenger實際上也是以AIDL作為其底層結構。

其實要應對AIDL相關的面試題,除了了解清楚它的作用和注意的事項外,最有效的手段莫過于自己動手寫幾次。這里給大家定兩個小目標:

  • 會使用AIDL進行進程間通信;
  • 會手寫AIDL的編碼,加深對Binder機制的理解。

創建AIDL

我們先實現第一個,在Android Studio中創建一個簡單的AIDL項目,實現IPC通信。

Step1. 創建.aidl文件
我們在對應的src的Package下創建一個AIDL文件(Android Studio->File->New->AIDL->AIDL file),創建后Android Studio會自動把這個.aidl文件放到一個aidl的目錄下。

Android SDK Tool會根據我們的.aidl文件自動生成一個同名的.java文件,如:AIDLTest/app/build/generated/source/aidl/debug/net/goeasyway/aidltest/IRemoteService.java

basicTypes方法中給我們展示了AIDL支持的基本數據類型,除此之外,AIDL還支持:CharSequence, List & Map(List和Map中的所有元素都必須是AIDL支持的數據類型、其他AIDL生成的接口或您聲明的可打包類型。)

Step2. 創建一個Service暴露AIDL接口并實現AIDL的接口函數
如下代碼,創建一個Service:

public class RemoteService extends Service {
    public RemoteService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder; //暴露給客戶端
    }

    // 實現AIDL接口
    private final IRemoteService.Stub binder = new IRemoteService.Stub() {

        @Override
        public int getPid() throws RemoteException {
            return Process.myPid();
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                               double aDouble, String aString) throws RemoteException {

        }
    };
}

然后在MainActivity通過bindService綁定這個服務,即可以獲得AIDL的接口調用的引用。運行前,我們設置一下AndroidManifest.xml文件記這個Service運行在一個單獨的進程中:

        <service
            android:name=".RemoteService"
            android:process=":remote"
            android:enabled="true"
            android:exported="true"/>

現在來bindService:

public class MainActivity extends AppCompatActivity {
    private final static String TAG = "MainActivity";
    private IRemoteService remoteService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent();
        intent.setClass(this, RemoteService.class);
        bindService(intent, connection, Service.BIND_AUTO_CREATE); // 綁定服務
    }


    private ServiceConnection connection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            remoteService = IRemoteService.Stub.asInterface(service); //獲取AIDL的接口實現引用
            try {
                Log.i(TAG, "Client pid= " + Process.myPid());
                Log.i(TAG, "RemoteService pid= " + remoteService.getPid());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            Log.e(TAG, "Service has unexpectedly disconnected");
            remoteService = null;
        }
    };
}

從輸出的日志看到Service和Activity運行在兩個不同的進程中:

02-05 09:51:40.154 18992-18992/net.goeasyway.aidltest I/MainActivity: Client pid= 18992
02-05 09:51:40.154 18992-18992/net.goeasyway.aidltest I/MainActivity: RemoteService pid= 19022

到這里,我們完成了一個AIDL的范例,有幾個地方可能會對我們造成困擾:

  • Stub類:Binder的實現類,服務端需要實現這個類來提供服務。
  • asInterface函數: 一個靜態函數,用來將IBinder轉換成對應的Binder的引用。先通過queryLocalInterface查詢,如果服務端和客戶端都是在同一個進程,那么就不需要跨進程了,直接將IRemoteService當做普通的對象來使用,否則會返回遠程對象的代理對象(Proxy)。
public static net.goeasyway.aidltest.IRemoteService asInterface(android.os.IBinder obj)
{
    if ((obj==null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof net.goeasyway.aidltest.IRemoteService))) {
        return ((net.goeasyway.aidltest.IRemoteService)iin);
    }
    return new net.goeasyway.aidltest.IRemoteService.Stub.Proxy(obj);
}

通過IPC傳遞對象

現在,我們加大一下難度,AIDL的接口使用一個我們自己定義的類為參數(或者返回值)。實現步驟如下:

  • 添加一個自定義對象類,并且要實現Parcelable接口,如MyProcess.java;
  • 在AIDL目錄下的相同Pacage下添加一個同名的AIDL文件,如MyProcess.aidl;

注意:通過“Android Studio->File->New->AIDL->AIDL file”不讓你創建和MyProcess.java同名的AIDL文件,你可以直接用通過“Android Studio->File->New->File”創建一個MyProcess.aidl。

  • 在AIDL接口類中添加一個接口函數,使用MyProcess做為參數或者返回值;

其他的細節大家可以直接查看Github上的代碼:https://github.com/goeasyway/AIDL_Test (或者查看提交的說明找到具體每次的代碼區別:https://github.com/goeasyway/AIDL_Test/commits/master

in、out & inout
這節我們看到“MyProcess getProcess(in MyProcess clientProcess);”這個接口的參數有一個“in”修飾符,這也是一個常見的面試題,可以考察一下對方是否真的寫過AIDL的代碼。

問題:AIDL中的接口函數有時會使用in、out或者inout的參數修飾符,它們各表示什么意思?在什么情況下要使用呢?

in、out和inout表示數據的流向。大家可以把AIDL的客戶端和服務端理解成兩個進程(其實大多數情況也是這樣才會使用AIDL),從客戶端流向服務端用in表示,表示這個對象是從客戶端中傳遞到服務端,在服務端修改這個對象不會對客戶端輸入的對象產生影響。

而out則表示,數據只能從服務端影響客戶端,即客戶端輸入這個參數時,服務端并不能獲取到客戶端的具體實例中的數據,而是生成一個默認數據,但是服務端對這個默認數據的修改會影響到客戶端的這個類對象實例發生相應的改變。

理解了in、out之后,inout自然不需要再解釋了。AIDL默認支持的數據類型使用in修飾符,對于我們自定義的Parcelable對象,一般情況下我們也是使用in,如果沒有必要,應該盡量避免inout。

Intent也是Parcelable實現
也許你會想到,我們在Activity間可以通過Intent攜帶參數,其實你去看的源碼的話會發現Intent也是一個Parcelable的實現類,而且在系統的工程中也有一個Intent.aidl文件(路徑:/frameworks/base/core/java/android/content/Intent.aidl)。所以,它才可以在進程間傳遞。

注:目前Client端和Server端在同一工程中,如果分開在不同的工程的話,Client端所在的工程要把Server端提供的.aidl復制到同名Pacage的AIDL代碼目錄下。

手動方式創建AIDL(不依賴AIDL工具,手寫遠程AIDL的代碼完成跨進程通信)

通過AIDL,可以讓本地調用遠程服務的接口就像調用本地接口那么簡單,讓用戶無需關注內部細節,只需要實現自己的業務邏輯接口,內部復雜的參數序列化發送、接收、客戶端調用服務端的邏輯,用戶并不需要關心。

AIDL的代碼生成器,已經根據.aidl文件自動幫我們生成Proxy、Stub(抽象類)兩個類,并且把客戶端代理mRemote的transact()過程以及服務器端的onTtransact()過程默認實現好了,我們只需要在服務端繼承Stub,實現自己的業務方法即可。

但現在,為了進一步加深對Binder機制的理解,我們來做一個手動實現編寫AIDL相關代碼的練習。

具體的代碼大家可以參考:https://github.com/goeasyway/AIDL_Test/tree/master/app/src/main/java/net/goeasyway/aidltest/diy

這個包里有三個類:IRmote.java為接口,Stub.java為Binder實現類(Service端要實例化它并在onBind返回),Proxy.java為代理類,提供給客戶端使用的,通過Binder驅動和服務端通信。

大家可以看到這個練習不需要.aidl文件。

在這幾個代碼中,大家需要搞清楚這個類(或者接口)的關系:

  • Binder
    Binder本地對象。
  • IBinder
    IBinder是一個接口,它代表了一種跨進程傳輸的能力。
  • IInterface
    IBinder負責數據傳輸,那么client與server端的調用契約呢?這里的IInterface代表的就是遠程server對象具有什么能力。具體來說,就是aidl里面的接口。
  • Proxy
    代表遠程進程的Binder對象的本地代理,繼承自IBinder,因而具有跨進程傳輸的能力。實際上,在跨越進程的時候,Binder驅動會自動完成代理對象和本地對象的轉換。
  • Stub
    這個類繼承了Binder, 說明它是一個Binder本地對象,它實現了IInterface接口,表明它具有遠程Server承諾給Client的能力;Stub是一個抽象類,具體的IInterface的相關實現需要我們手動完成。

如果覺得有點難理解的話,不妨先動手寫了再來看。

小結

如何使用AIDL應該是一個高級工程師必備技能,如果你之前不太了解它的話,那么我強烈建議你完成上面的兩個小練習,再回過頭去看它的解說。

之后,你將不再懼怕和AIDL相關的面試題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容