1. 程式人生 > >通過一個簡單的音樂播放器探討 Android Aidl 的實現原理

通過一個簡單的音樂播放器探討 Android Aidl 的實現原理

眾所周知,音樂播放器的播放不應該在前臺程序,而是要在另外一個程序的 Service 中進行,這樣才能實現後臺播放功能,同時不影響 UI 程序且不共用記憶體資源從而減少雙方被 kill 的可能性。
由於不同程序間是無法直接通訊的,因此在這種情況我們會使用 AIDL 去實現程序間通訊。那麼為什麼使用 AIDL 就能實現程序間通訊呢?AIDL 實質上做了什麼呢?我們接下來通過實現一個音樂播放器來了解下。

我們先來寫兩個 AILD 介面,如下:

// IMusicServiceAidl.aidl
package com.android.mikechenmj.myapplication.aidl;

import com
.android.mikechenmj.myapplication.aidl.ICallbackAidl; interface IMusicServiceAidl { void startMusic(); void pauseMusic(); void switchMusic(); oneway void setCallback(ICallbackAidl callback); }
// ICallbackAidl.aidl
package com.android.mikechenmj.myapplication.aidl;


interface ICallbackAidl {

        void update();
String getMusicPath(); }

IMusicServiceAidl.aidl 由後臺服務實現,在對應的方法上實現播放音樂、暫停音樂以及切換音樂的操作。除此之外還通過 setCallback 方法儲存客戶端實現的 ICallbackAidl.aidl 引用,從而進行服務端向客戶端的通訊。

ICallbackAidl.aidl 由前臺程序(主介面 Activity)實現,目的是接受服務端的訊息,然後在主介面作出相應的邏輯操作。

後臺服務 MusicService 的工作是通過 MediaPlayer 類控制音樂的播放暫停切換等操作,並通知 MusicListActivity 進行更新。

主介面活動 MusicListActivity 主要實現 UI 邏輯。在主介面活動 MusicListActivity 中,我們獲取儲存在手機的音樂列表,並將其展示在一個 ListView 上。並在使用者按下播放、暫停、和切換音樂功能按鍵的時候,呼叫從 ServiceConnection 物件的 onServiceConnected 方法返回的 IMusicServiceAidl 引用的對應方法,控制音樂播放行為,實現跨程序呼叫。
上述所說就是實現基本的思路,具體實現可參考 github 上的程式碼:通過aidl實現跨程序通訊的音樂播放器

接下來我們進入原始碼探討下為什麼 Aidl 能實現跨程序通訊的。其實 Aidl 實現跨程序通訊是通過 Binder 機制來實現的,我們檢視 IMusicServiceAidl.java 或者 ICallbackAidl.java 的程式碼就能發現這一點。Aidl 對 Binder 進行了封裝,讓我們更加方便地跨程序通訊。(注:閱讀此文需要對 Binder 有基礎的瞭解。)

以 IMusicServiceAidl.java 為例,我們可以發現抽象內部類 Stub 在實現 IMusicServiceAidl 介面的同時還繼承了 android.os.Binder 類的,並實現了一個關鍵方法:onTransact。瞭解 Binder 機制的同學都知道,onTransact 方法會在 transact 方法中被呼叫,負責處理從 transact 方法傳遞過來的事件,因此實現了這個方法的 IMusicServiceAidl.Stub 類就充當了服務端的角色。程式碼如下:

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_startMusic: {
                    data.enforceInterface(DESCRIPTOR);
                    this.startMusic();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_pauseMusic: {
                    data.enforceInterface(DESCRIPTOR);
                    this.pauseMusic();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_switchMusic: {
                    data.enforceInterface(DESCRIPTOR);
                    this.switchMusic();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_setCallback: {
                    data.enforceInterface(DESCRIPTOR);
                    com.android.mikechenmj.myapplication.aidl.ICallbackAidl _arg0;
                    _arg0 = com.android.mikechenmj.myapplication.aidl.ICallbackAidl.Stub.asInterface(data.readStrongBinder());
                    this.setCallback(_arg0);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

在 onTransact 方法中,判斷 code 值執行相應的操作,並藉助 Parcel 類實現物件的跨程序傳遞。 以開始音樂的方法為例,當 code 為 TRANSACTION_startMusic 時,會執行到 startMusic() 方法,而該方法的實現是在 MusicService.java 中的 IMusicServiceAidl.Stub() 的初始化中。

    private IMusicServiceAidl mIMusicServiceAidl = new IMusicServiceAidl.Stub() {

        @Override
        public void startMusic() throws RemoteException {
            mMediaPlayer.start();
        }

        @Override
        public void pauseMusic() throws RemoteException {
            mMediaPlayer.pause();
        }

        @Override
        public void switchMusic() throws RemoteException {
            String path = mCallback.getMusicPath();
            initMediaPlayer(path);

            if (DEBUG) {
                mMediaPlayer.seekTo((int) (mMediaPlayer.getDuration() * 0.9f));
            }

            mMediaPlayer.start();
        }

        @Override
        public void setCallback(ICallbackAidl callback) throws RemoteException {
            mCallback = callback;
        }
    };

IMusicServiceAidl.Stub() 充當了服務端的角色,那麼誰充當了客戶端的角色呢?我們繼續檢視 IMusicServiceAidl.java 類可以發現,在 Stub 類裡面,有一個靜態內部類 Proxy,且該類也實現了 IMusicServiceAidl 介面,那是不是這個類充當了客戶端的角色呢?我們接下來再去檢視下程式碼。

在 MusicListActivity.java 主活動中,我們呼叫 bindService 方法成功繫結服務之後,會回撥 ServiceConnection 的 onServiceConnected 方法,如下:

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIMusicServiceAidl = IMusicServiceAidl.Stub.asInterface(service);
            if (mIMusicServiceAidl == null || !service.isBinderAlive()) {
                Toast.makeText(MusicListActivity.this,"連線服務失敗", Toast.LENGTH_SHORT).show();
                return;
            }

可以看到,我們是通過 IMusicServiceAidl.Stub.asInterface(service) 方法獲取 mIMusicServiceAidl,並通過它去與服務跨程序通訊,而檢視 asInterface 方法可知,該方法實質上是返回了一個 IMusicServiceAidl.Stub.Proxy 物件,並把 IBinder 作為建構函式引數傳入。

從上面的分析可知,IMusicServiceAidl.Stub.Proxy 確實是充當了客戶端的角色,MusicListActivity 通過呼叫 Proxy 的對應方法,通知 MusicService 中的 Stub 進行對應的實際邏輯。還是以 startMusic 方法為例,Proxy 類的 startMusic 方法如下:

            @Override
            public void startMusic() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_startMusic, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

重點就在於 mRemote.transact(Stub.TRANSACTION_startMusic, _data, _reply, 0); 這一段程式碼。在這段程式碼中方法傳入了 Stub.TRANSACTION_startMusic 作為標誌 code,從而呼叫 Stub 的 onTransact 方法通知服務端 Stub 類呼叫實際邏輯的 startMusic 方法,從而實現跨程序通訊。

一句話總結,Aidl 其實就是對於 Binder 機制的封裝。運用代理機制,Aidl 讓 Proxy 作為客戶端發出資訊,Stub 作為服務端接收資訊,從而實現跨程序通訊。

既然如此,我們是不是也可以模仿 Aidl 去實現自己的跨程序通訊呢?答案自然是肯定的。修改後的程式碼已經上傳到 github 上了:模仿 Aidl 實現跨程序通訊的音樂播放器,修改的關鍵點在於把 Proxy 的邏輯移到客戶端當中去,把 Stub 的邏輯移到服務端去。比如對於 IMusicServiceAidl 而言,MusicListActivity 為客戶端,MusicService 為服務端,而對於 ICallbackAidl 而言,MusicService 為客戶端,MusicListActivity 為服務端。

此篇部落格到此結束,如有錯漏歡迎提出,謝謝。