一、Service简介
Service是Android中实现程序后台运行的解决方案,它非常适用于去执行那些不需要和用户交互而且还要求长期运行的任务。Service默认并不会运行在子线程中,它也不运行在一个独立的进程中,它同样执行在UI线程中,因此,不要在Service中执行耗时的操作,除非你在Service中创建了子线程来完成耗时操作。
二、Service种类
按运行地点分类:
类别 | 区别 | 优点 | 缺点 | 应用 |
---|---|---|---|---|
本地服务(Local Service) | 依附于主线程 | 依附在主线程而不是独立的线程在一定程度上节约了资源,另外你Local服务因为是在同一进程不需要IPC,也不需要AIDL。便于bindService。 | 主线程销毁后,服务便会跟着终止 | 依附于主线程如:音乐播放器等不需要 |
远程服务(Remote Service) | 独立的进程 | 对应的进程名称为所在包名加上指定的android:process字符串。由于是独立线程,因此不受所在activity进程影响,有利于多进程提供服务具有较高的灵活性。 | 会占用一定资源,并且AIDL进程IPC相对麻烦 | 一些提供系统服务的Service,属于常驻型的 |
按运行类型分类:
类别 | 区别 | 应用 |
---|---|---|
前台服务 | 会在通知栏显示onGoing的Notification | 服务终止时Notification也会消失,对于用户具有一定的通知作用。如音乐播放器播放服务 |
后台服务 | 默认服务即为后台服务,不会在通知栏显示onGoing的Notification | 服务终止时,用户看不到效果。如:天气更新,日期同步,邮件同步等 |
按使用方式分类:
类别 | 区别 |
---|---|
starService() | 用于启动动一个服务执行后台任务,不进行通信;停止服务用stopService() |
bindService() | 启动的服务用于通信,终止用unBindService() |
starService() + bindService() | 停止服务同时使用stopService()和unBindService() |
三、Service生命周期
starService()
OnCreate()->onStartCommand()->serviceRunning->onDestroy()
bindService()
OnCreate()->onBind()->bound to service->onUnbind()->onDestroy()
OnCreate()
系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作。如果service已经运行,这个方法不会被调用。
onStartCommand()
每次客户端调用startService()方法启动该Service都会回调该方法(多次调用)。一旦这个方法执行,service就启动并且在后台长期运行。通过调用stopSelf()或stopService()来停止服务。
OnBind()
当组件调用bindService()想要绑定到service时(比如想要执行进程间通讯)系统调用此方法(一次调用,一旦绑定后,下次再调用bindService()不会回调该方法)。在你的实现中,你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯,你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null。
OnUnbind()
当前组件调用unbindService(),想要解除与service的绑定时系统调用此方法(一次调用,一旦解除绑定后,下次再调用unbindService()会抛出异常)。
OnDestory()
系统在service不再被使用并要销毁时调用此方法(一次调用)。service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等.这是service收到的最后一个调用。
介绍三种不同情况下Service的生命周期情况。
1.startService / stopService
生命周期顺序:onCreate->onStartCommand->onDestroy
如果一个Service被某个Activity 调用 Context.startService方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务,还有一种方法可以关闭服务,在设置中,通过应用->找到自己应用->停止。
注意点:
- 第一次 startService 会触发 onCreate 和 onStartCommand,以后在服务运行过程中,每次
startService 都只会触发 onStartCommand - 不论 startService 多少次,stopService 一次就会停止服务
2.bindService / unbindService
生命周期顺序:onCreate->onBind->onUnBind->onDestroy
如果一个Service在某个Activity中被调用bindService方法启动,不论bindService被调用几次,Service的onCreate方法只会执行一次,同时onStartCommand方法始终不会调用。
当建立连接后,Service会一直运行,除非调用unbindService来接触绑定、断开连接或调用该Service的Context不存在了(如Activity被Finish——即通过bindService启动的Service的生命周期依附于启动它的Context),系统在这时会自动停止该Service。
注意点:
第一次 bindService 会触发 onCreate 和 onBind,以后在服务运行过程中,每次 bindService 都不会触发任何回调
3.混合型(同时包含上面两种方式的交互)
当一个Service在被启动(startService)的同时又被绑定(bindService),该Service将会一直在后台运行,并且不管调用几次,onCreate方法始终只会调用一次,onStartCommand的调用次数与startService调用的次数一致(使用bindService方法不会调用onStartCommand)。同时,调用unBindService将不会停止Service,必须调用stopService或Service自身的stopSelf来停止服务。
什么情况下使用 startService 或 bindService 或 同时使用startService 和 bindService
- 如果你只是想要启动一个后台服务长期进行某项任务那么使用 startService 便可以了。
- 如果你想要与正在运行的 Service 取得联系,那么有两种方法,一种是使用 broadcast ,另外是使用 bindService,前者的缺点是如果交流较为频繁,容易造成性能上的问题,并且 BroadcastReceiver本身执行代码的时间是很短的(也许执行到一半,后面的代码便不会执行),而后者则没有这些问题,因此我们肯定选择使用 bindService(这个时候你便同时在使用 startService 和 bindService 了,这在 Activity 中更新Service 的某些运行状态是相当有用的)。
- 如果你的服务只是公开一个远程接口,供连接上的客服端(android 的 Service是C/S架构)远程调用执行方法。这个时候你可以不让服务一开始就运行,而只用 bindService ,这样在第一次 bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是Remote Service,那么该效果会越明显(当然在Service 创建的时候会花去一定时间,你应当注意到这点)。
四、Service的几种典型使用实例
1.不可交互的后台服务
不可交互的后台服务即是普通的Service,通过startService()方式开启。Service的生命周期很简单,分别为onCreate、onStartCommand、onDestroy这三个。
public class BackService extends Service {
private Thread mThread;
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
System.out.println("onBind");
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//执行耗时操作
mThread = new Thread() {
@Override
public void run() {
try {
while (true) {
//等待停止线程
if (this.isInterrupted()) {
throw new InterruptedException();
}
//耗时操作。
System.out.println("执行耗时操作");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
mThread.start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
//停止线程
mThread.interrupt();
}
}
配置服务:
<service android:name=".BackService">
</service>
配置成远程服务:
android:process="remote"
配置好之后,只需在前台调用starService()就会启动耗时操作。
注意:
- 不运行在一个独立的进程中,它同样执行在UI线程中,因此,在Service中创建了子线程来完成耗时操作。
- 当Service关闭后,如果在onDestory()方法中不关闭线程,你会发现我们的子线程进行的耗时操作是一直存在的,此时关闭该子线程的方法需要直接关闭该应用程序。因此,在onDestory()方法中要进行必要的清理工作。
2.可交互的后台服务
前台页面可以调用后台服务的方法,通过bindService()方式开启。Service的生命周期很简单,分别为onCreate、onBind、onUnBind、onDestroy这四个。
可交互的后台服务实现步骤是和不可交互的后台服务实现步骤是一样的,区别在于启动的方式和获得Service的代理对象。
public class BackService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//返回MyBinder对象
return new MyBinder();
}
//需要返回给前台的Binder类
class MyBinder extends Binder {
public void showTip(){
System.out.println("我是服务提示");
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
前台调用
bindService(mIntent,con,BIND_AUTO_CREATE);
//第二个参数
private ServiceConnection con = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
BackService.MyBinder myBinder = (BackService.MyBinder) service;
myBinder.showTip();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
当建立绑定后,onServiceConnected中的service便是Service类中onBind的返回值。如此便可以调用后台服务类的方法,实现交互。
当调用unbindService()停止服务,同时要在onDestory()方法中做好清理工作。
注意:通过bindService启动的Service的生命周期依附于启动它的Context。因此当前台调用bindService的Context销毁后,那么服务会自动停止。
3.混合型后台服务
上面两种启动方式结合起来就是混合性交互的后台服务了,即可以单独运行后台服务,也可以运行后台服务中提供的方法,其完整的生命周期是:onCreate->onStartCommand->onBind->onUnBind->onDestroy
4.前台服务
前台服务只不是通过一定的方式将服务所在的进程级别提升了。前台服务会一直有一个正在运行的图标在系统的状态栏显示,非常类似于通知的效果。
由于后台服务优先级相对比较低,当系统出现内存不足的情况下,它就有可能会被回收掉,所以前台服务就是来弥补这个缺点的,它可以一直保持运行状态而不被系统回收。
创建服务类
public class ForeService extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
beginForeService();
}
private void beginForeService() {
//创建通知
Notification.Builder mBuilder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText("2019-3-21")
.setContentText("您有一条未读短信...");
//创建点跳转的Intent(这个跳转是跳转到通知详情页)
Intent intent = new Intent(this,NotificationShow.class);
//创建通知详情页的栈
TaskStackBuilder stackBulider = TaskStackBuilder.create(this);
//为其添加父栈 当从通知详情页回退时,将退到添加的父栈中
stackBulider.addParentStack(NotificationShow.class);
PendingIntent pendingIntent = stackBulider.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
//设置跳转Intent到通知中
mBuilder.setContentIntent(pendingIntent);
//获取通知服务
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//构建通知
Notification notification = mBuilder.build();
//显示通知
nm.notify(0,notification);
//启动前台服务
startForeground(0,notification);
}
}
启动前台服务
startService(new Intent(mContext, ForeService.class));
注意:通过 stopForeground()方法可以取消通知,即将前台服务降为后台服务。此时服务依然没有停止。通过stopService()可以把前台服务停止。