Service作为Android中的四大组件之一,它重要性不言而喻。它可以分为本地服务和远程服务:区分这两种服务就是看客户端和服务端是否在同一个进程中,本地服务是在同一进程中的,远程服务是在两个不同的应用中或者一个应用的不同进程。前面的文章中我们讲过怎样实现应用内多进程,看这里:http://blog.csdn.net/goodlixueyong/article/details/49853079 。
开启服务也有两种方式,一种是startService(),他对应的结束服务的方法是stopService(),另一种是bindService(),结束服务的是unBindService()。这两种方式的区别是:当Client使用startService方法开启服务时,这个服务和Client之间就没有联系了,Service的运行和Client是相互独立的。而当客户端Client使用bindService方法开始服务的时候,这个服务和Client是一种关联关系,他们之间使用Binder的代理对象进行交互,要是结束服务的话,需要在Client中和服务断开,调用unBindService方法。
很多情况下,为了实现业务需求,我们都希望Activity和Service能够交互,而不是相互独立的运行。尤其是我们使用Service在后台做一些任务时,数据变化可能希望Activity能在UI的变化中体现出来。当Activity和Service运行在同一个进程中的时候,交互相对容易。有哪些实现方案呢?通过bindService可以拿到Service的Binder对象,然后通过这个Binder对象可以调用Service中的方法,反过来,可以在Service中定义一个回调接口作为参数通过Binder传递给Activity,Activity通过Binder设置回调,Service拿到回调之后,就可以通过它更新UI。
如果Activity和Service是运行在不同的进程中,Service再想通知UI更新就没有那么简单了。容易实现的方法是通过广播,在Activity中注册一个广播接收器,在Service中有数据变化时发送广播来通知UI更新。不过如果数据的更新特别频繁,就要频繁的发送广播,再使用广播这种方式就显得不合时宜了。那么用什么方式比较好呢?
我们来介绍两种方式:AIDL和Messenger。这两种都是在跨进程通信中常用的方式,它们的实质也是借助了Binder。本文只讲解AIDL方式实现Service更新UI的功能,Messenger方式在下一篇文章中介绍。
AIDL(Android Interface Definition Language)是一种接口定义语言,用于生成可以在Android设备上两个进程之间通信的代码。客户端使用服务端的代理对象实现与服务端的通信。如果在一个进程中要调用另一个进程中对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL接口和普通的java接口没有什么区别,只是扩展名为.aidl,通过AIDL进行通信的应用需要创建同样的AIDL文件。
实现AIDL时需要注意以下几点:
1)AIDL只支持接口方法,不能公开static变量。
2)AIDL定义的接口名必须和文件名一致。
3)AIDL传递非基本可变长度变量,需要实现parcelable接口。
4)对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。
5)AIDL的方法还提供了oneway关键字,可以用关键字oneway来标明远程调用的行为属性,使用了该关键字的远程调用将仅仅是调用所需的数据传输过来并立即返回,而不会等待结果的返回,也即是说不会阻塞远程线程的运行。
一般实现两个进程之间的AIDL通信需要实现下面几个步骤:
(1)在android工程目录下面创建.aidl扩展名的文件,如果需要在AIDL中使用其他AIDL接口类型,即使是在相同包下也需要手动import。
(2)在Eclipse上,如果aidl文件符合规范,编译器会在gen目录下生成相对应的.java文件。但是在Android Studio不会自动生成java文件,需要在app/src/main目录下建立一个aidl目录,在其中创建.aidl文件,才会在app/build/generated/source/aidl/debug目录下生成对应的java文件。
(3)需要继承一个服务类,在onBind()方法中返回Binder对象。
(4)在Service端实现AIDL接口,将Binder对象返回给Client端。
(5)在Activity中实现更新UI的回调,注册回调。
根据上面的实现步骤,我们通过AIDL一步步实现不同进程的Service通知Activity更新的功能。
1、定义AIDL文件。
我们定义两个aidl文件,IClientCallback.aidl和IRegisterCallback.aidl,第一个用于Service调用更新UI,另外一个用于Activity将更新UI的回调注册到Service以及Activity通知Service停止更新UI。两个文件中的所有方法都不需要等待调用返回,所以使用oneway关键字修饰。
// IClientCallback.aidl
package com.viclee.aidltest.aidl;
interface IClientCallback {
oneway void updateData(int num);
}
// IRegisterCallback.aidl
package com.viclee.aidltest.aidl;
import com.viclee.aidltest.aidl.IClientCallback;
interface IRegisterCallback {
oneway void registerCallback(IClientCallback callback);
oneway void unregisterCallback(IClientCallback callback);
oneway void start();
}
2、实现Service类
Service类用到了一个RemoteCallbackList类,它用来管理一个Client注册回调接口的列表,方便对注册的接口进行管理。它提供了register()和unregister()方法来注册和注销接口。
public class AIDLService extends Service {
private int count = 0;
private RemoteCallbackList<IClientCallback> callbackList = new RemoteCallbackList<IClientCallback>();
public AIDLService() {
}
@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}
private IBinder serviceBinder = new IRegisterCallback.Stub() {
public boolean isRunning;
@Override
public void registerCallback(IClientCallback callback) throws RemoteException {
callbackList.register(callback);
isRunning = true;
}
@Override
public void unregisterCallback(IClientCallback callback) throws RemoteException {
callbackList.unregister(callback);
isRunning = false;
}
@Override
public void start() {
new Thread(new Runnable() {
@Override
public void run() {
while(isRunning) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count ++;
int num = callbackList.beginBroadcast();
for (int i = 0; i < num; i++) {
try {
callbackList.getBroadcastItem(i).updateData(count);
} catch (RemoteException e) {
e.printStackTrace();
}
}
callbackList.finishBroadcast();
}
}
}).start();
}
};
}
代码中实现了一个IRegisterCallback类型的Binder对象通过onBind方法返回给Client中的Activity,这个Binder对象中,实现了三个方法,前两个为注册和注销回调接口,start()方法用于开始更新UI,可以看到实现中每隔一秒调用一次回调接口中的updateData()方法更新UI。
3、Activity中实现callback接口,注册callback接口
public class ServiceUpdateActivity extends Activity implements View.OnClickListener {
private Button btn, btnStop;
private TextSwitcher switcher;
private int mCount = 0;
Intent intent;
private boolean isAIDLServiceConnected = false;
IRegisterCallback registerCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service_update_main);
btn = (Button) findViewById(R.id.btn);
btnStop = (Button) findViewById(R.id.method_stop);
btn.setOnClickListener(this);
btnStop.setOnClickListener(this);
switcher = (TextSwitcher) findViewById(R.id.number_switcher);
switcher.setFactory(new ViewSwitcher.ViewFactory() {
@Override
public View makeView() {
TextView textView = new TextView(ServiceUpdateActivity.this);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(30);
return textView;
}
});
switcher.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.switcher_text_in));
switcher.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.switcher_text_out));
updateCount();
intent = new Intent(this, AIDLService.class);
}
@Override
protected void onDestroy() {
stopAll();
super.onDestroy();
}
private void startAIDLMethod() {
bindService(intent, AIDLServiceConnection, Service.BIND_AUTO_CREATE);
}
/**
* 刷新数字
*/
private void updateCount() {
//由于从binder调用回来是在子线程里,需要post到主线程调用
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
switcher.setText(Integer.toString(mCount));
}
});
}
private void stopAll() {
if (isAIDLServiceConnected) {
try {
registerCallback.unregisterCallback(clientCallback);
} catch (RemoteException e) {
e.printStackTrace();
}
unbindService(AIDLServiceConnection);
isAIDLServiceConnected = false;
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
startAIDLMethod();
break;
case R.id.method_stop:
stopAll();
break;
}
}
private ServiceConnection AIDLServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isAIDLServiceConnected = true;
registerCallback = IRegisterCallback.Stub.asInterface(service);
try {
registerCallback.registerCallback(clientCallback);
registerCallback.start();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private IClientCallback clientCallback = new IClientCallback.Stub() {
@Override
public void updateData(int num) throws RemoteException {
mCount = num;
updateCount();
}
};
}
在回调接口中调用updateData()方法更新UI,并通过注册接口的方法将回调接口注册到Service中,然后调用start()方法启动远程Service中的线程。可以看到,Activity中的IClientCallback是Binder对象的Server端,而Service中却是Binder的Client端。
通过AIDL方式实现不同进程中的Service更新UI的方法就讲解完了,下一节会讲解另外一种方式:Messenger。
欢迎关注我的公众号一起交流学习