1. 程式人生 > >使用AIDL,客戶端呼叫和Service回撥,以及一些需要注意的細節

使用AIDL,客戶端呼叫和Service回撥,以及一些需要注意的細節

前言

  • 模擬一個簡單的音樂播放demo(未實現真實的音樂播放器),使用AIDLService進行跨程序通訊,並總結。
  • 本文資料來源網路公開資源,並根據個人實踐純手打整理,如有錯誤請隨時指出。
  • 本文主要用於個人積累及分享,文中可能引用其他技術大牛文章(僅引用連結不轉載),如有侵權請告知必妥善處理。

AIDL介紹

  • AIDLJava比較類似,但更簡單,因為AIDL只是作為兩個原本互不影響的程序之間相互“溝通”的工具,基本只做資料的傳遞,不使用AIDL做邏輯處理。

  • AIDL支援的資料型別有:

    1. 基本型別:byte、short、int、long、float、double、boolean、char
    2. Java常用資料型別:String、CharSequence、List、Map
    3. .aidl檔案的interface型別(介面類,用於程序間互相呼叫、回撥)
    4. .aidl檔案的parcelable型別(資料類,需要配合同包路徑的.java資料類,這個.java資料類需要實現Parcelable序列化)

    特別說明
    String、CharSequence型別的引數的定向tag預設為in,並且只能是 in

  • AIDL檔案一般有兩大類:資料類,介面類(包含兩類:互動介面,回撥介面)

AIDL使用和配置

  1. 自定義AIDL檔案的存放路徑,如 src/main/java/ 包下任意位置,建立一個aidl

    資料夾

    並任意建立一個.aidl檔案

  2. build.gradle中配置 aidl.srcDirs

  3. 在aidl資料夾中,任意建立一個aidl檔案,並點選Android Studio工具欄“Build–>Make Project”或“clean Project”,等候編譯完成,將會在 app\build\generated\source\aidl\debug\ 下自動生成對映的.java檔案,此檔案請勿做任何修改

至此,AIDL檔案已可正常編譯

示例分析

模擬一個簡單的音樂播放demo,但這裡不會真正得實現音樂播放功能,只是實現App程序(客戶端)和單獨的Service程序(播放服務端)

之間的互動和回撥。

AIDL

AIDL資料檔案

在aidl包中建立一個數據檔案Music.aidl

package com.sp.personal.music.aidl;

parcelable Music;

在同包內建立Music.java檔案,Music類必須implements Parcelable。

  • 目前比較好用的Parcelable自動生成程式碼外掛可通過“File–>Settings–>Plugins–>Browse repositories…”,輸入搜尋“Android Parcelable code generator”,安裝即可。
  • 這裡要注意的是,Music.java需要和Music.aidl在同一個包裡
  • Music.java檔案就不貼程式碼了,比較通常的一個數據類。

AIDL介面以及回撥檔案

AIDL回撥檔案

Service程序(播放服務端)回撥的介面
定義一個PlayCallback.aidl檔案

package com.sp.personal.music.aidl;

interface PlayCallback {
    //state:1,onPrepare;2,onStart;3,onPlaying;4,onPause;5,onEnd
    void stateNow(int state, String songId, int position);

}

AIDL介面檔案

供App程序(客戶端)呼叫的介面AIDLPlayService.aidl

package com.sp.personal.music.aidl;
import com.sp.personal.music.aidl.Music;
import com.sp.personal.music.aidl.PlayCallback;

interface AIDLPlayService{

    void prepare(in Music music);
    void start();
    void seekTo(String id, int position);
    void pause(String id);

    void addPlayCallback(PlayCallback playCallback);
    void removePlayCallback(PlayCallback playCallback);
}

這裡需要注意的是:
* import,是必須的,無論是否在同一個包中
* 自定義型別需要指定其引數“定向”,這裡Music music例項由App程序(客戶端)傳遞過來,則指定為in

服務端(Service)

用於播放音樂的單獨程序的Service,以及繫結AIDL介面到Service

提供一個Service,並指定這個Service在單獨的程序執行:

<service
    android:name=".code.playctrl.PlayService"
    android:process=":remote"/>

(使用remote是自定義的,可以隨意。冒號:這個字首是必須的,它將把這個名字附加到你的包所執行的標準程序名字的後面作為新的程序名稱)

PlayService.java

public class PlayService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //將AIDLPlayService作為IBinder返回
        return mIBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }


    //建立一個RemoteCallbackList用來管理PlayCallback,用來通過Broadcast傳送回撥到客戶端
    RemoteCallbackList<PlayCallback> remoteCallbackList = new RemoteCallbackList<>();

    //服務端用來接收客戶端的請求
    AIDLPlayService.Stub mIBinder = new AIDLPlayService.Stub() {

        @Override
        public void prepare(Music music) throws RemoteException {
            //接收到準備播放的音樂物件
        }

        @Override
        public void start() throws RemoteException {
            //接收到客戶端的請求,開始播放
        }

        @Override
        public void pause(String id) throws RemoteException {
            //接收到客戶端的請求,暫停播放

            //暫停播放的測試:暫停後客戶端將收到已暫停的回撥通知
            pausePlay(id);
        }

        @Override
        public void addPlayCallback(PlayCallback playCallback) throws RemoteException {
            if (playCallback != null) {
                //客戶端設定回撥,這裡使用RemoteCallbackList註冊這個PlayCallback
                remoteCallbackList.register(playCallback);
            }
        }

        @Override
        public void removePlayCallback(PlayCallback playCallback) throws RemoteException {
            if (playCallback != null) {
                //客戶端登出回撥,這裡使用RemoteCallbackList登出這個PlayCallback
                remoteCallbackList.unregister(playCallback);
            }
        }


        @Override
        public void seekTo(String id, int position) throws RemoteException {
            //接收到客戶端的請求,播放到指定位置(或時間)
        }
    };

    //舉例,測試回撥,將在客戶端程式碼中列印
    private void pausePlay(String id) {
        //假如已知目前播放的位置是:position=100(秒)
        int position = 100;
        playCallback(4, id, position);
    }

    /**
     *呼叫回撥到客戶端
     * @param state    1,onPrepare;2,onStart;3,onPlaying;4,onPause;5,onEnd
     * @param songId
     * @param position
     */
    private void playCallback(int state, String songId, int position) {
        //準備開始呼叫最近註冊的Callback並且返回,已註冊playCallback數(在客戶端註冊),目前是1個
        int N = remoteCallbackList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                remoteCallbackList.getBroadcastItem(i).stateNow(state, songId, position);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        //必須的,和beginBroadcast()成對出現
        mPlayCallback.finishBroadcast();
    }
}

客戶端(App程序)

Application中啟動Service,並獲取到AIDLPlayService物件

public class PMApplication extends Application {

    public static AIDLPlayService aidlPlayService;

    @Override
    public void onCreate() {
        super.onCreate();

        initPlayService();
    }


    private void initPlayService() {
        Intent intent = new Intent(this, PlayService.class);
        this.startService(intent);
        this.bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                aidlPlayService = AIDLPlayService.Stub.asInterface(service);

            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                aidlPlayService = null;
            }
        }, Context.BIND_AUTO_CREATE);
    }
}

Activity(或其他View)添加回調,並呼叫pause方法測試回撥

try {
    PMApplication.aidlPlayService.addPlayCallback(new PlayCallback.Stub() {

        @Override
        public void stateNow(int state, String songId, int position) throws RemoteException {
            //測試是否收到回撥,列印
            Log.d("Play","state=" + state + ";songId=" + songId + ";position=" + position);
        }
    });

    PMApplication.aidlPlayService.pause("123456");
} catch (RemoteException e) {
    e.printStackTrace();
}
  • 切記addPlayCallback只能呼叫一次,如不需要使用該回調時,需要登出這個回撥removePlayCallback,否則可能導致回撥的資訊混亂或之前已完成的回撥資訊再次回撥。其中的原因是RemoteCallbackList中的訊息機制,允許註冊多個回撥並存(請見RemoteCallbackList原始碼)。

結語

以上經過測試,服務端可以收到客戶端的請求,並可以回撥給客戶端資料,已完成了基本的跨程序AIDL資料互動。如一般的音樂播放器完全可以按照這樣的互動流程進行處理。