AIDL整体流程
使用AIDL实现安卓跨进程通信的时候,主要分为3步骤:
- 声明AIDL接口文件,android sdk会根据aidl文件生成接口形式的java文件,服务端实现接口中的抽象类Stub,其中包含了我们声明的方法。并在onBind回调中作为Binder返回给客户端
- 客户端与服务端绑定,在回调函数onServiceConnected中获取Binder
- 利用Stub的asInterface方法转换为我们声明的接口,通过接口调用服务端逻辑。
在使用AIDL时要注意,服务端可能同时和多个客户端进行绑定,所以要设计为线程安全的模式。
除此之外,Service依托于主线程而不依托于创建它的组件,这句话的意思是比如在某个Acitivity中创建Service,Activity关闭后Service仍可运行,然后那个应用关闭后,Service失去依托的主线程所以无法运行。基于此,我们也要注意在Server中的CPU密集和耗时操作,同样要放在新的线程中运行。
示例
1、声明AIDL接口文件
// ITimeManager.aidl
package com.example.zxc.aidlserver;
// Declare any non-default types here with import statements
interface ITimeManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
String getTime();
}
声明一个方法,获取服务端的时间。
2、在服务端实现Stub抽象类,实现其中的方法,并通过onBInd回调接口,在Server被绑定调用此回调接口时,将Binder暴露给客户端:
public class MyService extends Service {
public MyService() {
}
private final ITimeManager.Stub binder = new ITimeManager.Stub() {
@Override
public String getTime() throws RemoteException {
return String.valueOf(System.currentTimeMillis());
}
};
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return binder;
}
}
3、在客户端绑定Service,实现连接回调接口,连接成功时将收到的Binder,通过Stub中的asInterface方法转换为可以使用的接口
public class MainActivity extends AppCompatActivity {
private ITimeManager iTimeManager;
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iTimeManager = ITimeManager.Stub.asInterface(service);
try {
Log.d("TAG", iTimeManager.getTime());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iTimeManager = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(MainActivity.this, MyService.class), mConn, Context.BIND_AUTO_CREATE);
}
}
将客户端和服务端放在不同应用中
当然,这里客户端和服务端还是同一个应用,如果两个应用的话,首先要将Myservice放在服务端,MainActivity中设置连接回调和绑定服务的操作放在客户端。然后aidl下整个包拷贝至服务端应用和客户端应用,注意保持两者包文件名、文件结构相同。最后在服务端应用的Manifest文件设置Service的IntentFilter,这样我们就可以隐式调用了:
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.zxc.aidlserver.MyService"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
需要提到一个地方:客户端在绑定时隐式调用intent,由于安卓5.0以上不允许隐式调用的方式,会抛出异常
Service Intent must be explicit
解决方法是在隐式调用时,声明intent的时候同时调用setAction和setPackage方法,会被认为是显示调用,最后,客户端绑定服务:
//注意,Intent的action客户端和服务端保持相同即可,packageName要是aidl文件的包名
bindService(new Intent("com.example.zxc.aidlserver.MyService").setPackage("com.example.zxc.aidlserver"), mConn, Context.BIND_AUTO_CREATE);
操作时先打开服务端应用,再打开客户端应用绑定服务即可。可以看到客户端的应用打出了日志
08-22 21:45:31.543 31624-31624/com.example.zxc.myapplication:remote D/TAG: bind
08-22 21:45:31.553 31624-31624/com.example.zxc.myapplication:remote D/TAG: 1471873531564