android的service分为本地service和远程service,二者的区别在于:
1. 一个activity调用startService()或bindService(),创建出服务,若该服务与创建服务的activity运行在同一进程中,则把这种服务称为本地服务。并且,本地服务只能在创建该服务的应用程序内部使用,当应用程序终止时,本地服务也一同终止;
2. 与本地服务不同,远程服务并不与创建者运行在同一进程中,它运行在单独的进程中,所以当主应用程序终止时,远程服务还会继续运行。
在本地服务中,服务与使用服务的客户端程序运行在同一进程中,本地服务的绑定实际是指客户端程序获取了待绑定服务的一个引用。当绑定完成后,客户端即获取了服务的一个引用,通过该引用,客户端即可调用该服务提供的各种方法。
就远程服务而言,activity与远程服务运行在不同的进程中,activity若想控制远程服务,必须使用IPC机制。
在我们的项目中,我们需要创建一个service,要求该service能脱离主应用程序而独立存在,有较高的稳定性,该service能独立启动,应用程序崩溃不会影响该service,该service崩溃也不会影响主应用程序,因此选择采用remote service。
区分一个service是本地服务还是远程服务,首先是在AndroidManifest.xml中声明该service,有点不同:
<service
android:name="com.dtri.clientagent.BackendService"
android:enabled="true"
android:exported="true"
android:process="com.dtri.clientagent.BackendService" >
android:process指定该service作为一个独立的进程运行,本地服务不用指定android:process,默认跟主应用程序运行在同一个进程里面。运行后,通过adb shell,ps后会发现有一个com.dtri.clientagent.BackendService进程,这个进程即为我们的远程服务进程。
启动远程服务跟启动本地服务没有区别,都是通过startService()或bindService(),如果需要同service进行交互控制,则用bindService(),不用交互控制只需要启动起来,那么就用startService()。我们在这里采用bindService()。
不同于本地服务,远程服务涉及到进程间的通信,就需要引入IPC机制,即通过AIDL来描述通信接口。
我们创建ISecondary.aidl
package com.dtri.clientagent;
import com.dtri.clientagent.RegisterArg;
interface ISecondary {
int register(in RegisterArg arg);
int getRegisterStatus();
void notify_service();
int updateCommand(String cmdtype, String potocoltype, String tarPath, String uid, String pwd);
void writeLog(int logtype, String log);
int getCAName();
}
该接口描述了远程服务可供外部activity调用的接口。
同时我们创建IBackendService.aidl
package com.dtri.clientagent;
import com.dtri.clientagent.IBackendServiceCallbacK;
interface IBackendService {
void registerCallback(IBackendServiceCallback cb);
void unregisterCallback(IBackendServiceCallback cb);
}
该接口提供了一个注册回调和反注册回调接口,service提供这两个接口用于回调创建者的接口,用来实现双向通信。
IBackendServiceCallback.aidl即描述了回调接口
package com.dtri.clientagent;
oneway interface IBackendServiceCallback {
void updateRegisterStatus(String status, String host, String port, String isAuto);
void reportStatus(int status);
void CANameChanged(String name);
}
光这些aidl文件还是不够的,但android系统会根据这些aidl文件自动生成其它java文件用于完成IPC通信,节省了我们开发时间和难度,因此我们只需要关注aidl接口和实现上面,至于之间怎么通信就不是我们要重点考虑的方面了。
基本流程如下:
1. 在activity中通过bindService()来启动远程service
this.getApplicationContext().bindService(new Intent(IBackendService.class.getName()),
mConnection, Context.BIND_AUTO_CREATE);
this.getApplicationContext().bindService(new Intent(ISecondary.class.getName()),
mSecondaryConnection, Context.BIND_AUTO_CREATE);
因为service提供了两套aidl接口,因此都要bind。
2. 服务启动后,会调用onBind()方法。在onBind()里面,生成用于处理Binder IPC的Binder对象。
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
Log.i("siqun","Backendservice onBind");
if (IBackendService.class.getName().equals(intent.getAction())) {
return mBinder;
}
if (ISecondary.class.getName().equals(intent.getAction())) {
return mSecondaryBinder;
}
return null;
}
private final IBackendService.Stub mBinder = new IBackendService.Stub() {
@Override
public void unregisterCallback(IBackendServiceCallback cb)
throws RemoteException {
// TODO Auto-generated method stub
Log.i("siqun","backendservice unregistercallback");
if (cb != null) {
mCallbacks.unregister(cb);
}
}
@Override
public void registerCallback(IBackendServiceCallback cb)
throws RemoteException {
Log.i("siqun","backendservice registercallback");
// TODO Auto-generated method stub
if (cb != null) {
mCallbacks.register(cb);
}
}
};
binder对象同时实现了各自的aidl接口,比如IBackendService.aidl中定义的接口registerCallback和unregisterCallback就分别实现了,IBackendServiceCallback 是创建服务的activity提供的接口,注册以后,远程服务可以调用这些接口,从而实现跟activity的通信。
3. 在activity端,
private ServiceConnection mConnection = new ServiceConnection() {@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mService = IBackendService.Stub.asInterface(service);
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
}
mCallbackText.setText("Attached.");
}
@Override
public void onServiceDisconnected(ComponentName className) {
mService = null;
mCallbackText.setText("Disconnected.");
}
};
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,IBinder service) {
mSecondaryService = ISecondary.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName className) {
mSecondaryService = null;
}
};
private IBackendServiceCallback mCallback = new IBackendServiceCallback.Stub() {
/**
* This is called by the remote service regularly to tell us about
* new values. Note that IPC calls are dispatched through a thread
* pool running in each process, so the code executing here will
* NOT be running in our main thread like most other things -- so,
* to update the UI, we need to use a Handler to hop over there.
*/
@Override
public void updateRegisterStatus(String status, String host, String port, String isAuto) {
Log.i("siqun","updateRegisterStatus "+"host="+host+"port="+port);
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
// b.putString("ca_name", ca_name);
b.putString("status", status);
b.putString("host", host);
b.putString("port", port);
b.putString("isAuto", isAuto);
msg.what = REGISTER_RSP_MSG;
msg.setData(b);
mHandler.sendMessage(msg);
}
一旦远程服务连接成功,会调用到onServiceConnected(),在这个函数里面得到远程服务的一个引用,通过该引用可以调用远程服务提供的接口,比如调用mService.registerCallback(mCallback);来将自己的aidl接口注册到远程服务中,供远程服务回调。
综上所述,activity如何同远程服务通信:
activity通过bindService()创建并启动远程服务;service端onBind()被调用,生成Binder对象并返回给activity,该Binder对象里面提供服务端aidl的实现,这些aidl是service端开放给activiy的接口(事先通过aidl文件来定义);一旦创建成功,activity端onServiceConnected()会被调用,在该函数里面可以获得远程服务端的引用,通过该引用就可以调用服务端开放的aidl接口,来实现activiy调用服务端的接口。
那么,远程服务端是如何实现反向调用activity的接口呢?
activity同时定义自己的aidl接口,比如上面的IBackendServiceCallback.aidl,同时service要提供registerCallback这个接口,该接口用于activity将自己的aidl注册到远程服务的回调接口列表中;在onServiceConnected中通过registerCallback注册到远程服务的回调列表中。
final RemoteCallbackList<IBackendServiceCallback> mCallbacks
= new RemoteCallbackList<IBackendServiceCallback>();
注册实质上是加入到RemoteCallbackList中,调用mCallbacks.register来完成注册。
final int N = mCallbacks.beginBroadcast();
for(int i=0; i<N; i++) {
try {
mCallbacks.getBroadcastItem(i).updateRegisterStatus(register_status, register_host, register_port,register_isAuto);
} catch (RemoteException e) {
}
}
mCallbacks.finishBroadcast();
来调用activity的接口
这样就实现了activity和service的双向通信