天天看點

通過一個簡單的音樂播放器探讨 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 為服務端。

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