意义:
由于每个应用进程都有自己的独立进程空间,在android平台上,一个进程通常不能访问另一个进程的内存空间,而我们经常需要夸进程传递对象,就需要把对象分解成操作对象可以理解的基本单元,并且有序的通过进程边界。
定义:
AIDL(Android Interface Definition Language)是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
说明以及实现流程:
AIDL接口和普通的java接口没有什么区别,只是扩展名为.aidl,保存在src目录下,如果其他应用程序需要IPC,则也需要在src目录下创建同样的AIDL文件,创建完毕之后,通过ADT工具,会在工程的gen目录下生成相对应的.java文件。
一般实现两个进程之间的通信需要实现下面几个步骤
(1)在Eclipse的android工程目录下面创建一个.aidl扩展名的文件,语法和java定义接口的语法差不多,不过需要自己手动import对应的包名。(比如需要用到list集合,则需要import java.util.List;)
(2)如果aidl文件符合规范,ADT工具会帮助编译器在gen目录下生成相对应的.java文件。
(3)需要继承实现一个服务类,跨进程调用的基础。
(4)在service端实现AIDL接口,如果有回调则在client端实现callback的AIDL接口。
(5)在AndroidManifest.xml注册service。
注意:
实现AIDL,我们需要注意以下五点
1)AIDL只支持接口方法,不能公开static变量。
2)AIDL接口方法如果有参数,则需要注意in、out、inout的使用规则,对于基本数据类型,默认是in类型,可以不需要添加声明,非基本可变对象需要在变量名之前添加方法类型
in表示输入参数,调用者把值传递给使用者使用。
out表示输出参数,调用者把容器传递给使用者填充,然后自己使用处理。
inout标书输入输出参数,传送相应的值并接收返回。
列举一个out的使用例子:
服务端传参数给客户端,客户端填充,服务端调用完之后,可以读取到客户端填写的内容,具体的例子后面将给出。
3)AIDL定义的接口名必须和文件名一致。
4)oneway表示用户请求相应功能时不需要等待响应可直接调用返回,非阻塞效果,该关键字可以用来声明接口或者声明方法,如果接口声明中用到了oneway关键字,则该接口声明的所有方法都采用oneway方式。
5)AIDL传递非基本可变长度变量(非final对象),需要实现parcelable接口
实例:
下面列举一个例子,主要实现客户端调用服务端然后回调回来,具体实现功能改变客户端的文字和图片显示,这个例子暂时效果是图片的更改直接使用客户端已经准备好的图片,接下来几篇博客会基于这个功能完善,到达服务端可以发送文字、图片、文件句柄(I/O流),并且直接由服务端通过方法名称直接调用客户端方法,客户端只需要注册对应的view并且提供相应的方法给服务端使用,后面的两部的完善主要用到反射和重写MemoryFile(达到parcelable序列化效果)来实现。
1)首先按照我们上面的步骤需要创建aidl文件,分别创建调用和回调的aidl文件,为了阐述更详细一些,博主把parcelable对象也添加进去,仅仅作为测试。
IMyAidlService.aidl主要由服务端实现客户端调用
1
2
3
4
5
6
7
|
package
com.zlc.aidl;
import
com.zlc.aidl.DemoParcelable;
import
com.zlc.aidl.AIDLCallback;
interface
IMyAidlService{
void
registerClient(AIDLCallback cb);
//注册回调
void
saveDemoInfo(in DemoParcelable demo);
//实际调用方法
}
|
1
2
3
4
5
6
7
|
package
com.zlc.aidl;
import
com.zlc.aidl.DemoParcelable;
import
java.util.List;
interface
AIDLCallback {
int
returnResult(out List<DemoParcelable> list,
int
a);
//回调给客户端
void
testMethod(out Bundle params);
//用来测试参数in/out的使用
}
|
1
2
|
package
com.zlc.aidl;
parcelable DemoParcelable;
|
补充一点:out和in参数区别其实很明显我们直接查看adt生成在gen目录下对应的java文件就可以看出区别:当是out参数的时候是执行完之后从parcel对象读取值,而in参数时是写到parcel对象里面传过去。
我们看下当testMethod分别是out和in修饰时生成的文件
当时out的时候是从parcel对象里面读数据
1
2
3
4
5
|
mRemote.transact(Stub.TRANSACTION_testMethod, _data, _reply,
0
);
_reply.readException();
if
((
0
!=_reply.readInt())) {
params.readFromParcel(_reply);
}
|
1
2
3
4
5
6
7
8
9
|
if
((params!=
null
)) {
_data.writeInt(
1
);
params.writeToParcel(_data,
0
);
}
else
{
_data.writeInt(
0
);
}
mRemote.transact(Stub.TRANSACTION_testMethod, _data, _reply,
0
);
_reply.readException();
|
2)实现一个服务类用来实现进程之间通信MyAidlService.java,贴出部分代码,详细代码会在后面上传。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
@Override
public
IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
Log.d(TAG,
"MyAidlService onBind"
);
return
mBinder;
}
private
final
IMyAidlService.Stub mBinder =
new
IMyAidlService.Stub() {
private
AIDLCallback cb;
@Override
public
void
saveDemoInfo(DemoParcelable demo)
throws
RemoteException {
if
(demo !=
null
) {
if
(
"meinv1"
.equals(demo.getDemo_name())) {
demo.setDemo_name(
"meinv2"
);
}
list.add(demo);
Log.d(TAG,
"saveDemoInfo list.size = "
+ list.size() +
" list = "
+ list);
cb.returnResult(list,
5
);
Bundle params =
new
Bundle();
cb.testMethod(params);
int
width = params.getInt(
"width"
,
0
);
int
height = params.getInt(
"height"
,
0
);
Log.d(TAG,
"width = "
+ width +
" height = "
+height);
}
}
@Override
public
void
registerClient(AIDLCallback cb)
throws
RemoteException {
cb.asBinder().linkToDeath(
new
DeathRecipient() {
@Override
public
void
binderDied() {
try
{
Log.i(TAG,
"[ServiceAIDLImpl]binderDied."
);
}
catch
(Throwable e) {
}
}
},
0
);
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
private
ServiceConnection mRemoteConnection =
new
ServiceConnection() {
@Override
public
void
onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
Log.d(TAG,
"onServiceDisconnected"
);
}
@Override
public
void
onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
Log.d(TAG,
"onServiceConnected"
);
mRemoteService = (IMyAidlService) IMyAidlService.Stub
.asInterface(service);
if
(mRemoteService !=
null
)
Log.d(TAG,
"onServiceConnected success"
);
}
};
……
btn.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
// TODO Auto-generated method stub
String actionName =
"com.zlc.aidl.server.MyAidlService"
;
Intent intent =
new
Intent(actionName);
boolean
ret = bindService(intent, mRemoteConnection,
Context.BIND_AUTO_CREATE);
Log.d(TAG,
" ret ?="
+ ret);
if
(ret) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
// TODO Auto-generated method stub
try
{
DemoParcelable demo =
new
DemoParcelable();
List<String> list =
new
ArrayList<String>();
list.add(
"like dance"
);
demo.setDemo_id((Integer) img.getTag());
demo.setDemo_name(
"meinv1"
);
demo.setDemo_list(list);
mRemoteService.registerClient(callback);
mRemoteService.saveDemoInfo(demo);
}
catch
(Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
});
}
……
private
final
AIDLCallback callback =
new
AIDLCallback.Stub() {
@Override
public
int
returnResult(List<DemoParcelable> list,
int
a)
throws
RemoteException {
if
(list !=
null
)
Log.d(TAG,
"list.size = "
+ list.size()+
" a="
+a);
for
(DemoParcelable demoParcelable : list) {
doFresh(demoParcelable);
}
return
0
;
}
@Override
public
void
testMethod(Bundle outParams)
throws
RemoteException {
// TODO Auto-generated method stub
if
(outParams !=
null
) {
outParams.putInt(
"width"
,
11
);
outParams.putInt(
"height"
,
12
);
}
}
};
|
4)在androidManifest.xml里面注册service服务。
注意一点:android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。
通过ps直接看pid进程号就可以看出。
让应用的组件在一个单独的进程中运行,如果带冒号: ,则创建一个专属于当前进程的进程,如果不带冒号,需要使用标准的命名规范命名进程名,例如com.xxx.xxx.xxx,而且该进程是全局共享的进程,即不同应用的组件都可以运行于该进程。
这可以突破应用程序的24M(或16M)内存限制。
总之,使用带:remote的属性的进程id pid不同,父进程ID PPID是一样的。而使用不带冒号的remote则会创建两个完全独立的进程。