本篇主要讲述service的绑定状态及如何设计前台service。
一、综述
绑定服务允许本APP内的activity与service进行绑定从而去执行具体的任务,也允许其他的应用程序APP绑定并且与之交互的service类实现。
当应用程序通过bindService()方法绑定到service时,service处于绑定状态。绑定服务可以为client-server提供接口,允许组件与service进行交互,发送请求,获得结果等操作,甚至还可以进行IPC(进程间通信)。
在activity采用bindService()与service进行绑定,会调用到service中onBind()函数,此函数返回一个bind实例。但是由于绑定是异步的,bindService()方法立即返回并且不返回Ibinder到客户端,因此需要在activity或者fragment中创建一个ServiceConnection的匿名类,并在这个类中重写onServiceConnected()和onServiceDisconnected(),用来监视客户端与服务端的连接。ServiceConnection中包含系统调用发送Ibinder的回调方法。
注意:有多个client可以连接到service,但是只有第一个客户端绑定后,系统会调用service的onBind()函数,来获取一个binder对象。然后系统会将获取的对象发送给其他的client,但是不会再次调用onBind(),简单滴说,就是只调用一次onBind(),多次使用binder对象。
二、绑定的几种方法
2.1 继承Binder类
这种方法最常见,仅应用于私有APP所创建的service。此时service和APP运行于相同的进程。继承Binder类实现的步骤:
- 在服务中创建继承于Binder的类,在这个类中包含activity或者fragment可以调用的公共方法;
- 从onBind()返回可以在其他activity中使用到的Binder类实例;
- 客户端的Activity或者Fragment创建一个ServiceConnection的匿名类来监视client-service的连接;
如在MyService.class中建立继承Binder的类MyBinder,并且实例化。
private MyBinder myBinder = new MyBinder();
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return myBinder;
}
public class MyBinder extends Binder {
public void startDownLoad(){
Log.e(TAG,"开始下载");
}
}
然后在MainActivity.class中,就可以通过ServiceConnection来监听service和client的连接。
private MyService.MyBinder myBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//连接成功后需要执行的任务
myBinder = (MyService.MyBinder) service;
myBinder.startDownLoad();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e("MyService","解除链接");
}
};
2.2 使用Messenger
当需要跨进程工作时,可以使用Messenger来为服务创建接口。Messenger,可被称作“信使”。service可以定义不同的Handler对象来响应不同类型的message。而Handler是Messenger的基础,可以与client分享binder对象,允许client使用Message对象向service发送命令。因此,通过Messenger返回的binder对象可以不同考虑client-service是否属于同一个进程的问题。此外,Messenger将所有的请求队列化到单独的线程,因此也不必考虑线程的安全问题。在使用Messenger需要注意:
- Handler用于创建Messenger,以及通过Handler的handleMessage()方法来接收message来决定如何操作;
- Messenger创建IBinder,并且service从onBind()返回这个实例给客户端;
- 客户端使用IBinder来实例化Messenger,然后在通过Messenger将message发送给service端;
例,新建一个MyMessengerService.class
public class MyMessengerService extends Service {
public static final int TEST = 1;
private final Messenger messenger = new Messenger(new MyHandler());
public MyMessengerService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
//通过messenger创建binder对象
return messenger.getBinder();
}
/**
* MyHanler用于创建messenger对象,并且通过handleMessage()接收来自客户端的message来觉得如何操作
*/
public class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch(msg.what)
{
case TEST:
ToastMgr.show("这是一个测试的service");
break;
default:
break;
}
}
};
}
客户端的主要操作代码:
private Messenger messenger = null;
private boolean bound = false;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messenger = new Messenger(service);
bound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
messenger = null;
bound = false;
}
};
执行绑定按钮:
Intent bindIntent = new Intent(mActivity,MyMessengerService.class);
mActivity.bindService(bindIntent,connection,Context.BIND_AUTO_CREATE);
if (!bound){
return;
}else {
Message msg = Message.obtain(null, MyMessengerService.TEST,0,0);
try {
messenger.send(msg);
}catch (RemoteException e){
Log.e(TAG, "onClick: ", e);
}
}
执行解除绑定按钮:
if (bound) {
mActivity.unbindService(connection);
Log.e(TAG, "onClick: 解除绑定");
bound = false;
}
在以上实例代码中,服务端主要完成的工作是通过Messenger实例化一个Ibinder对象,创建一个Handler用于创建Messenger,并且接收来自客户端的message。
客户端的全部工作是根据绑定服务所返回的binder对象创建一个Messenger对象,并且通过Messenger的send()方法将message发送给服务端。
2.3 使用AIDL
绝大多数的APP几乎都不采用这种方法来执行绑定服务,主要是因为需要多线程的能力而导致更加复杂的设计,本篇就不做介绍了。
注意:
在android的四大组件中,只有Activity、ContentProvider、Service能够绑定到服务,而BroadcastReceiver(简称BR)是不可以绑定到服务的。因为BR的生命周期很短,在执行完onReceive()函数后,BR的生命周期就结束了,而service本身的生命周期与client本身的生命周期有关,因此可以使用startService()来代替。
三、设置前台service
service几乎都被设定运行于后台,但是由于其优先级较低,在系统资源不足的情况下,有可能会kill后台运行的service。如果想让该service可以一直运行,而不用担心系统资源不足带来的问题,就可以针对此设计前台service。其特点在于会有一个一直运行的UI显示在系统的状态栏里,当用户下拉时,会看到更加详细的信息。
实例如下:
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
private MyBinder myBinder = new MyBinder();
public MyService(){}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"on Create excused");
Log.e(TAG,"Service的线程id = " + Thread.currentThread().getId());
Intent resultIntent = new Intent(this, SecondFragment.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,resultIntent,0);
NotificationCompat.Builder nfBuilder = new NotificationCompat.Builder(this);
nfBuilder.setSmallIcon(R.mipmap.ic_launcher);//设置通知栏中的图标
nfBuilder.setContentTitle("测试title");//设置通知栏中的标题
nfBuilder.setContentText("这是一个测试的通知栏UI");//设置通知栏中的内容
nfBuilder.setContentIntent(pendingIntent);//设定点击的意图
startForeground(1,nfBuilder.build());
// nfBuilder.setAutoCancel(true);//只有在通知的时候会自动取消该通知
/*如何设置通知*/
/*
int nfID = 1;
Notification notification = nfBuilder.build();
NotificationManager manager = (NotificationManager) getSystemService(this.NOTIFICATION_SERVICE);
manager.notify(nfID, notification);
*/
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand excused");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy excused");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return myBinder;
}
public class MyBinder extends Binder {
public void startDownLoad(){
Log.e(TAG,"开始下载");
}
}
}
测试结果如下图示:
在以上代码中给出了通过NotificationCopmat.Builder的方式建立一个通知栏来显示前台service。当然也可以使用Notification.Builder建立,效果一样,只不过前者在API上的兼容性更好。
BTW
可以通过上述的NotificationCopmat.Builder方法建立通知,然后根据通知信息的更新,只需要发送同一个nfID即可进行更新通知的操作。
此外,在上述示例代码中,有用到PendingIntent和Intent,简单的提及下这二者之间的区别。
Intent是及时启动,并且随activity的消失而消失,而PendingIntent(未执行的,即将发生的),顾名思义,不是马上就启动,用于即将发生的事情。
PendingIntent作为对Intent的包装,通常通过getActivity,getBroadCast等方法获得实例,通常用在Notification,延迟执行的intent。例如在Notification中,在下拉状态栏中点击某notification的时候才会发生activity的跳转,此时用到的就是如上示例中的pendingintent。