在Android Service中我们对长时间运行的服务、应用内交互的服务进行了相关介绍,本文主要介绍使用Service进行应用间的交互。关于Service的介绍见Android Service介绍。
1、介绍
Android使用AIDL来完成进程间通信(IPC),AIDL全程为Android Interface Definition Language。在服务需要接受不同应用多线程的请求时才需要使用AIDL,如果是同一个应用内的请求使用Binder实现即可,见应用内交互的服务;如果只是应用间通信而不是多线程处理的话使用Messenger,当然这两种情况也可以使用AIDL。本地进程和远程进程使用AIDL有所不同,本地进程内调用时会都在调用的线程内执行,远程进程使用是通过Service进程内一个由系统维护的线程池发出调用,所以可能是未知线程同时调用,需要注意线程安全问题。
2、示例
在被调用应用内(后面称服务端)的Service内有个属性count,需要在调用者(后面称客户端)内对这个属性进行操作。下面将介绍服务器端和客户端的主要代码。
服务器端(代码见AndroidDemo@GoogleCode):
(1) 服务器端新建AIDL文件定义接口
在服务器端的src目录下新建以.aidl为后缀的文件,在这个文件中定义一个接口,声明服务器端和客户端交互的api,语法跟普通java接口声明一样,可以添加中英文注释。不同的是:
a. 除了Java基本数据类型 (int, long, char, boolean等)、String、CharSequence、List、Map外,其他复杂类型都需要显式import(包括其他AIDL定义的接口),即便复杂类型在同一个包内定义。
b. 支持泛型实例化的List,如List<String>;不支持泛型实例化的Map,如Map<String, String>。对于List为参数接收者接收到的始终是ArrayList;对于Map为参数接收者接收到的始终是HashMap。
c. interface和函数都不能带访问权限修饰符。
d. 接口内只允许定义方法,不允许定义静态属性。
我们定义MyAIDLInterface.aidl文件如下:
1 | package com.trinea.android.demo; |
2 | |
3 | interface MyAIDLInterface { |
4 | int getCount(); |
5 | void setCount(int count); |
6 | } |
声明两个接口在后面供客户端调用。保存文件,编译后,Sdk工具在gen目录下相同子目录中自动生成同名的以.java为后缀的文件,这里文件名为MyAIDLInterface.java。
(2) 自动生成的同名java文件解析
MyAIDLInterface.java为Sdk工具自动生成,内容如下:
1 | /* |
2 | * This file is auto-generated. DO NOT MODIFY. |
3 | * Original file: D:\\Eclipse\\AndroidDemo\\MapDemo\\src\\com\\trinea\\android\\demo\\MyAIDLInterface.aidl |
4 | */ |
5 | package com.trinea.android.demo; |
6 | |
7 | public interface MyAIDLInterface extends android.os.IInterface |
8 | { |
9 | /** Local-side IPC implementation stub class. */ |
10 | public static abstract class Stub extends android.os.Binder implements com.trinea.android.demo.MyAIDLInterface |
11 | { |
12 | private static final java.lang.String DESCRIPTOR = "com.trinea.android.demo.MyAIDLInterface"; |
13 | /** Construct the stub at attach it to the interface. */ |
14 | public Stub() |
15 | { |
16 | this.attachInterface(this, DESCRIPTOR); |
17 | } |
18 | /** |
19 | * Cast an IBinder object into an com.trinea.android.demo.MyAIDLInterface interface, |
20 | * generating a proxy if needed. |
21 | */ |
22 | public static com.trinea.android.demo.MyAIDLInterface asInterface(android.os.IBinder obj) |
23 | { |
24 | if ((obj==null)) { |
25 | return null; |
26 | } |
27 | android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR); |
28 | if (((iin!=null)&&(iin instanceof com.trinea.android.demo.MyAIDLInterface))) { |
29 | return ((com.trinea.android.demo.MyAIDLInterface)iin); |
30 | } |
31 | return new com.trinea.android.demo.MyAIDLInterface.Stub.Proxy(obj); |
32 | } |
33 | public android.os.IBinder asBinder() |
34 | { |
35 | return this; |
36 | } |
37 | @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException |
38 | { |
39 | switch (code) |
40 | { |
41 | case INTERFACE_TRANSACTION: |
42 | { |
43 | reply.writeString(DESCRIPTOR); |
44 | return true; |
45 | } |
46 | case TRANSACTION_getCount: |
47 | { |
48 | data.enforceInterface(DESCRIPTOR); |
49 | int _result = this.getCount(); |
50 | reply.writeNoException(); |
51 | reply.writeInt(_result); |
52 | return true; |
53 | } |
54 | case TRANSACTION_setCount: |
55 | { |
56 | data.enforceInterface(DESCRIPTOR); |
57 | int _arg0; |
58 | _arg0 = data.readInt(); |
59 | this.setCount(_arg0); |
60 | reply.writeNoException(); |
61 | return true; |
62 | } |
63 | } |
64 | return super.onTransact(code, data, reply, flags); |
65 | } |
66 | private static class Proxy implements com.trinea.android.demo.MyAIDLInterface |
67 | { |
68 | private android.os.IBinder mRemote; |
69 | Proxy(android.os.IBinder remote) |
70 | { |
71 | mRemote = remote; |
72 | } |
73 | public android.os.IBinder asBinder() |
74 | { |
75 | return mRemote; |
76 | } |
77 | public java.lang.String getInterfaceDescriptor() |
78 | { |
79 | return DESCRIPTOR; |
80 | } |
81 | public int getCount() throws android.os.RemoteException |
82 | { |
83 | android.os.Parcel _data = android.os.Parcel.obtain(); |
84 | android.os.Parcel _reply = android.os.Parcel.obtain(); |
85 | int _result; |
86 | try { |
87 | _data.writeInterfaceToken(DESCRIPTOR); |
88 | mRemote.transact(Stub.TRANSACTION_getCount, _data, _reply, 0); |
89 | _reply.readException(); |
90 | _result = _reply.readInt(); |
91 | } |
92 | finally { |
93 | _reply.recycle(); |
94 | _data.recycle(); |
95 | } |
96 | return _result; |
97 | } |
98 | public void setCount(int count) throws android.os.RemoteException |
99 | { |
100 | android.os.Parcel _data = android.os.Parcel.obtain(); |
101 | android.os.Parcel _reply = android.os.Parcel.obtain(); |
102 | try { |
103 | _data.writeInterfaceToken(DESCRIPTOR); |
104 | _data.writeInt(count); |
105 | mRemote.transact(Stub.TRANSACTION_setCount, _data, _reply, 0); |
106 | _reply.readException(); |
107 | } |
108 | finally { |
109 | _reply.recycle(); |
110 | _data.recycle(); |
111 | } |
112 | } |
113 | } |
114 | static final int TRANSACTION_getCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); |
115 | static final int TRANSACTION_setCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); |
116 | } |
117 | public int getCount() throws android.os.RemoteException; |
118 | public void setCount(int count) throws android.os.RemoteException; |
119 | } |
其中有几个主要的部分:
a. 抽象类Stub,继承Binder实现自定义接口,作用同进程内通信服务中自定义的Binder,客户端通过它对服务进行调用。
b. 静态类Proxy,实现自定义接口,代理模式接收对Stub的调用。
(3) 服务器端定义服务
跟一般的服务定义类似,新建个上面自动生成的MyAIDLInterface.java文件中的Stub的对象,在onBind中返回,代码如下:
1 | package com.trinea.android.demo; |
2 | |
3 | import android.app.Service; |
4 | import android.content.Intent; |
5 | import android.os.IBinder; |
6 | import android.os.RemoteException; |
7 | import android.widget.Toast; |
8 | |
9 | public class MyAIDLService extends Service { |
10 | |
11 | private int mCount; |
12 | private MyAIDLInterface.Stub myBinder = new MyAIDLInterface.Stub() { |
13 | |
14 | @Override |
15 | public void setCount(int count) throws RemoteException { |
16 | mCount = count; |
17 | } |
18 | |
19 | @Override |
20 | public int getCount() throws RemoteException { |
21 | return mCount; |
22 | } |
23 | }; |
24 | |
25 | @Override |
26 | public void onCreate() { |
27 | mCount = 0; |
28 | Toast.makeText(this, "Service onCreate", Toast.LENGTH_SHORT).show(); |
29 | super.onCreate(); |
30 | } |
31 | |
32 | @Override |
33 | public void onDestroy() { |
34 | Toast.makeText(this, "Service onDestroy", Toast.LENGTH_SHORT).show(); |
35 | super.onDestroy(); |
36 | } |
37 | |
38 | @Override |
39 | public int onStartCommand(Intent intent, int flags, int startId) { |
40 | Toast.makeText(this, Integer.toString(mCount), Toast.LENGTH_SHORT).show(); |
41 | return super.onStartCommand(intent, flags, startId); |
42 | } |
43 | |
44 | public int getCount() { |
45 | return mCount; |
46 | } |
47 | |
48 | /** |
49 | * 服务被绑定时调用 |
50 | * 返回值用于让调用者和服务通信,传入ServiceConnection的public void onServiceConnected(ComponentName name, IBinder service)函数第二个参数 |
51 | */ |
52 | @Override |
53 | public IBinder onBind(Intent intent) { |
54 | return myBinder; |
55 | } |
56 | } |
AndroidManifest.xml中注册服务:
1 | <service android:name=".MyAIDLService"> |
2 | <intent-filter> |
3 | <action android:name="com.trinea.android.demo.remote.MyAIDLServiceAction" /> |
4 | </intent-filter> |
5 | </service> |
其中的action name用于后面客户端调用时匹配。
客户端(代码见AndroidAidlClient@GoogleCode):
(1) 拷贝aidl文件
将服务器端的aidl文件拷贝到客户端,客户端也会在gen目录下自动生成同名的java文件。服务器端和客户端文件目录如下:
服务器端目录结构:
客户端目录结构:
PS:在运行过程中发现一个小问题,即服务器端服务已经启动,而客户端始终无法BindService成功,报错
ERROR/AndroidRuntime(2104): java.lang.SecurityException: Binder invocation to an incorrect interface。后发现是因为服务器端和客户端aidl文件的包名不同导致生成的java文件不同。
(2) 服务绑定
跟一般的服务绑定类似,只是获取服务的接口不同而已,这里我们通过Stub.asInterface函数获得,代码如下:
1 | private MyAIDLInterface binder = null; |
2 | private Intent myAIDLServiceIntent; |
3 | private ServiceConnection con = new ServiceConnection() { |
4 | |
5 | @Override |
6 | public void onServiceDisconnected(ComponentName name) { |
7 | |
8 | } |
9 | |
10 | @Override |
11 | public void onServiceConnected(ComponentName name, IBinder service) { |
12 | binder = MyAIDLInterface.Stub.asInterface(service); |
13 | } |
14 | }; |
15 | |
16 | @Override |
17 | public void onCreate(Bundle savedInstanceState) { |
18 | super.onCreate(savedInstanceState); |
19 | setContentView(R.layout.client); |
20 | |
21 | boundAIDLServiceBtn = (Button)findViewById(R.id.boundAIDLService); |
22 | myAIDLServiceIntent = new Intent("com.trinea.android.demo.remote.MyAIDLServiceAction"); |
23 | boundAIDLServiceBtn.setOnClickListener(new OnClickListener() { |
24 | |
25 | @Override |
26 | public void onClick(View v) { |
27 | boolean result = bindService(myAIDLServiceIntent, con, Context.BIND_AUTO_CREATE); |
28 | if (!result) { |
29 | binder = null; |
30 | Toast.makeText(getApplicationContext(), "服务绑定失败。", Toast.LENGTH_SHORT).show(); |
31 | } |
32 | } |
33 | }); |
34 | |
35 | getBoundAIDLServiceProBtn = (Button)findViewById(R.id.getBoundAIDLServicePro); |
36 | getBoundAIDLServiceProBtn.setOnClickListener(new OnClickListener() { |
37 | |
38 | @Override |
39 | public void onClick(View v) { |
40 | try { |
41 | if (binder != null) { |
42 | Toast.makeText(getApplicationContext(), "Service count:" + binder.getCount(), |
43 | Toast.LENGTH_SHORT).show(); |
44 | } else { |
45 | Toast.makeText(getApplicationContext(), "请先绑定服务。", Toast.LENGTH_SHORT).show(); |
46 | } |
47 | } catch (RemoteException e) { |
48 | e.printStackTrace(); |
49 | } |
50 | } |
51 | }); |
52 | |
53 | unboundAIDLServiceBtn = (Button)findViewById(R.id.unboundAIDLService); |
54 | unboundAIDLServiceBtn.setOnClickListener(new OnClickListener() { |
55 | |
56 | @Override |
57 | public void onClick(View v) { |
58 | if (binder != null) { |
59 | unbindService(con); |
60 | binder = null; |
61 | } |
62 | } |
63 | }); |
64 | } |
上面的三个按钮boundAIDLServiceBtn、getBoundAIDLServiceProBtn、unboundAIDLServiceBtn分别用于绑定服务、得到服务中属性值、解除绑定服务。
从上面可以看出我们先定义ServiceConnection对象用于和服务器通信,在onServiceConnected函数中通过MyAIDLInterface.Stub.asInterface(service)返回用户定义的接口对象,通过此对象调用远程接口api。在boundAIDLServiceBtn的onClick函数中绑定服务,在getBoundAIDLServiceProBtn的onClick函数中调用服务的api,在boundAIDLServiceBtn的onClick函数中取消绑定服务,现在我们运行两个项目即可,在客户端绑定服务再获取服务属性显示。
3、生命周期
通过bindService绑定服务,若服务未启动,会先执行Service的onCreate函数,再执行onBind函数,最后执行ServiceConnection对象的onServiceConnected函数,客户端可以自动启动服务。若服务已启动但尚未绑定,先执行onBind函数,再执行ServiceConnection对象的onServiceConnected函数。若服务已绑定成功,则直接返回。这里不会自动调用onStartCommand函数。
通过unbindService解除绑定服务,若已绑定成功,会先执行Service的onUnbind函数,再执行onDestroy函数,注意这里不会执行ServiceConnection对象的onServiceDisconnected函数,因为该函数是在服务所在进程被kill或是crash时被调用。