前言
- 模拟一个简单的音乐播放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
数据交互。如一般的音乐播放器完全可以按照这样的交互流程进行处理。