使用AIDL,客戶端呼叫和Service回撥,以及一些需要注意的細節
前言
- 模擬一個簡單的音樂播放demo(未實現真實的音樂播放器),使用
AIDL
和Service
進行跨程序通訊,並總結。 - 本文資料來源網路公開資源,並根據個人實踐純手打整理,如有錯誤請隨時指出。
- 本文主要用於個人積累及分享,文中可能引用其他技術大牛文章(僅引用連結不轉載),如有侵權請告知必妥善處理。
AIDL介紹
AIDL
和Java
比較類似,但更簡單,因為AIDL
只是作為兩個原本互不影響的程序之間相互“溝通”的工具,基本只做資料的傳遞,不使用AIDL做邏輯處理。AIDL
支援的資料型別有:- 基本型別:
byte、short、int、long、float、double、boolean、char
- Java常用資料型別:
String、CharSequence、List、Map
.aidl
檔案的interface型別(介面類,用於程序間互相呼叫、回撥).aidl
檔案的parcelable型別(資料類,需要配合同包路徑的.java
資料類,這個.java
資料類需要實現Parcelable
序列化)
特別說明:
String、CharSequence型別的引數的定向tag預設為in,並且只能是 in- 基本型別:
AIDL
檔案一般有兩大類:資料類,介面類(包含兩類:互動介面,回撥介面)
AIDL使用和配置
自定義AIDL檔案的存放路徑,如
src/main/java/
包下任意位置,建立一個aidl
並任意建立一個
.aidl
檔案在
build.gradle
中配置aidl.srcDirs
在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
資料互動。如一般的音樂播放器完全可以按照這樣的互動流程進行處理。