ZEROMAKE | keep codeing and thinking!
2016-01-03 | android

Android 的 aidl 相互通信机制

Service 使用 aidl 的与 Activity 相互交互

一、为什么要使用 aidl

原因是当我们的 Client 和 Service 不是同一个进程时是无法直接使用的,而在 android 中进程间通讯的方法有 Activity、Content Provider、Broadcast 和 Service。
其中 Activity 需要界面,隐式调用没有回调 Broadcast 的接收对象经常会重新被实例化,且以上两种都是通过 intent 传送。回调不能完成交互。
Content Provider 则只提供数据,Service 有 aidl 这门进程间调用函数的机制。

二、普通单向 aidl 使用

需要一个 Client 和 Service 以及 aidl 接口,其中 Client 和 Service 是可以不在同一个应用中的。下面是 aidl:IMyAidl.aidl

1
package zero.aidldemo.aidl;
2
3
interface IMyAidl {
4
void show(String str);
5
}

以上写完会在 gen 下同包名中生成与 aidl 文件相同的 java 文件打开后这个类中有一个抽象内部类 Stub 实现 Binder 和自身我们在 Service 中写一个继承这个 Stub 的内部类
Service:ServiceMain.java 把当前 Service 的 class 全名作为 Action 注册以便于其他进程调用。

1
2
public class ServiceMain extends Service{
3
public final static String SERVICE_CLASS_NAME = "zero.musicplay.service.ServiceMain";
4
//继承IMyAidl.Stub
5
private class MyAidl extends IMyAidl.Stub{
6
@Override
7
public void show(String str) throws RemoteException {
8
Log.i("zerolog","ServiceMain_MyAidl_show="+str);
9
}
10
}
11
@Override
12
public IBinder onBind(Intent intent) {
13
return new ServiceBinder();
14
}
15
}

Client 用一个 Activity 来实现:ActivityClient.java

1
public class ActivityClient extends Activity implements OnClickListener,ServiceConnection{
2
//bind后的接口实现对象。
3
private IMyAidl mAsInterface;
4
//Service的Intent
5
private Intent _intent;
6
protected void onCreate(Bundle savedInstanceState) {
7
super.onCreate(savedInstanceState);
8
//界面加载和一个按键监听。。。
9
setContentView(R.layout.activity_main);
10
findViewById(R.id.btn1).setOnClickListener(this);
11
//绑定Service
12
Intent _intent=new Intent();
13
_intent.setAction(ServiceMain.SERVICE_CLASS_NAME);
14
//在4.多后具体哪个版本不记得了隐式bindService需要设置Service的包名。
15
_intent.setPackage(getPackageName());
16
//不知道是怎么回事现在我用api23编的如果不先startService就没法bindService不知道为什么。
17
//statrtService(_intent);
18
//第一个Intent,ServiceConnection对象,flags自己查api吧没懂什么用。
19
bindService(_intent, this, 0);
20
}
21
@Override
22
public void onClick(View v) {
23
//bind是异步的防止mAsInterface还未获得而FC
24
if (mAsInterface != null) {
25
try {
26
mAsInterface.show("MainActivity->show");
27
} catch (RemoteException e) {
28
e.printStackTrace();
29
}
30
}
31
}
32
@Override
33
protected void onDestroy() {
34
//记得解绑
35
if(mAsInterface != null){
36
unbindService(this);
37
stopService(_intent);
38
}
39
super.onDestroy();
40
}
41
@Override
42
public void onServiceConnected(ComponentName name, IBinder service) {
43
mAsInterface = IMyAidl.Stub.asInterface(service);
44
}
45
46
@Override
47
public void onServiceDisconnected(ComponentName name) {
48
}
49
}

效果就是点击按钮触发 Service 里的实现接口函数。

三、双向 aidl 使用

单向的 aidl 接口中函数也可以有返回值,但是只适用于同步且时间较短。如果需要的函数执行的是一个异步任务就不好用了。
所以这里用双向 aidl 比较合适。

与上面相比多了一个 aidl 的回调先看回调的 aidl:IMyAidlCallBack.aidl

1
package zero.aidldemo.aidl;
2
3
interface IMyAidlCallBack {
4
void callBackShow();
5
}

没什么变化但是在原来的调用 Service 的 aidl 中需要加入两个方法:IMyAidl.aidl 方法名不是固定的

1
package zero.aidldemo.aidl;
2
//eclipse对aidl文件编辑支持比较差有时无法自动导包。
3
import zero.aidldemo.aidl.IMyAidlCallBack;
4
interface IMyAidl {
5
void show(String str);
6
//注册回调
7
void registerCallback(IMyAidlCallBack cb);
8
//解除回调
9
void unregisterCallback(IMyAidlCallBack cb);
10
}

Service:ServiceMain.java

1
public class ServiceMain extends Service {
2
public final static String SERVICE_CLASS_NAME = "zero.musicplay.service.ServiceMain";
3
// 回调列表对象
4
private RemoteCallbackList<IMyAidlCallBack> mCallbackList;
5
6
private class MyAidl extends IMyAidl.Stub {
7
@Override
8
public void show(String str) throws RemoteException {
9
Log.i("zerolog", "ServiceMain_MyAidl_show=" + str);
10
// 取出已绑定的回调对象个数并开始广播
11
int conut = mCallbackList.beginBroadcast();
12
for (int j = 0; j < conut; j++) {
13
// 遍历调用回调方法。
14
mCallbackList.getBroadcastItem(j).callBackShow();
15
}
16
// 解除回调广播。
17
mCallbackList.finishBroadcast();
18
}
19
20
@Override
21
public void registerCallback(IMyAidlCallBack cb) throws RemoteException {
22
// 添加到回调列表中
23
mCallbackList.register(cb);
24
}
25
26
@Override
27
public void unregisterCallback(IMyAidlCallBack cb)
28
throws RemoteException {
29
// 从回调列表中移除
30
mCallbackList.unregister(cb);
31
}
32
}
33
34
@Override
35
public void onCreate() {
36
// 在服务被创建时实例化一个为空的回调列表对象
37
mCallbackList = new RemoteCallbackList<IMyAidlCallBack>();
38
super.onCreate();
39
}
40
41
@Override
42
public IBinder onBind(Intent intent) {
43
return new MyAidl();
44
}
45
}

和上面说的一样在原有的 aidl 中添加两个方法用于给回调列表对象添加删除。并需要一个回调列表对象。
所以我在 onCreate() 实例化。

然后是 Client:ActivityClient.java

1
public class ActivityClient extends Activity implements OnClickListener,
2
ServiceConnection {
3
// bind后的接口实现对象。
4
private IMyAidl mAsInterface;
5
// Service的Intent
6
private Intent _intent;
7
// 回调实现对象
8
private MyCallBack mCallBack;
9
10
protected void onCreate(Bundle savedInstanceState) {
11
super.onCreate(savedInstanceState);
12
// 界面加载和一个按键监听。。。
13
setContentView(R.layout.activity_main);
14
findViewById(R.id.btn1).setOnClickListener(this);
15
// 实例化回调实现
16
mCallBack = new MyCallBack();
17
// 绑定Service
18
Intent _intent = new Intent();
19
_intent.setAction(ServiceMain.SERVICE_CLASS_NAME);
20
// 在4.+后具体哪个版本不记得了隐式bindService需要设置Service的包名。
21
_intent.setPackage(getPackageName());
22
// 不知道是怎么回事现在我用api23编的如果不先startService就没法bindService不知道为什么。
23
// statrtService(_intent);
24
// 第一个Intent,ServiceConnection对象,flags自己查api吧没懂什么用。
25
bindService(_intent, this, 0);
26
}
27
28
@Override
29
public void onClick(View v) {
30
// bind是异步的防止mAsInterface还未获得而FC
31
if (mAsInterface != null) {
32
try {
33
mAsInterface.show("MainActivity->show");
34
} catch (RemoteException e) {
35
e.printStackTrace();
36
}
37
}
38
}
39
40
@Override
41
protected void onDestroy() {
42
// 记得解绑
43
if (mAsInterface != null) {
44
try {
45
mAsInterface.unregisterCallback(mCallBack);
46
} catch (RemoteException e) {
47
// TODO Auto-generated catch block
48
e.printStackTrace();
49
}
50
unbindService(this);
51
stopService(_intent);
52
}
53
super.onDestroy();
54
}
55
56
@Override
57
public void onServiceConnected(ComponentName name, IBinder service) {
58
mAsInterface = IMyAidl.Stub.asInterface(service);
59
// 在绑定完成后将通过定义好的方法添加到Service的回调列表中
60
try {
61
mAsInterface.registerCallback(mCallBack);
62
} catch (RemoteException e) {
63
// TODO Auto-generated catch block
64
e.printStackTrace();
65
}
66
}
67
68
@Override
69
public void onServiceDisconnected(ComponentName name) {
70
}
71
72
// 实现回调接口中的方法。
73
private class MyCallBack extends IMyAidlCallBack.Stub {
74
75
@Override
76
public void callBackShow() throws RemoteException {
77
Log.i("zerolog", "callBackShow");
78
// 千万记住不能在这里再调用Service的aidl方法会报异常
79
// java.lang.IllegalStateException: beginBroadcast() called while
80
// already in a broadcast
81
// mAsInterface.show("");
82
}
83
84
}
85
}

好了以上就是一个交互式的 aidl 小例子,回调可以在 Service 的任意地方调用不一定要在 Service 的 aidl 被调用时调用。
以及这些内部类也都可以提取出来,但是就不好访问 Service 和视图了。

四、注意

1
1.回调对象的实现方法不能直接调用Service的aidl方法。可以用Handler来调用。
2
2.如果发觉只用bindSrevice无法启动Service可以先startService再bindSrevice

五、例子源码

度盘