文章目录
Android Service服务的相关介绍
- 服务概览 | Android 开发者 | Android Developers
- android service是一种运行在后台,不需要与用户交互的任务
- service不是运行在单独的进程中的(当然,如果你想的话也是可以在单独的进程中的),本身还是运行在主线程中,因此如果后台服务是个耗时任务,那么必须得开启线程去执行(因此出现了IntentService)
- 当进程被杀死,依赖该进程的服务也会被杀死,也就是说当app应用进程被杀死时,服务也停止了,因此不存在说app挂了而它的服务还在跑的情况
- 实际上service可以理解为一个没有界面的activity,它可以默默地在后台处理一些任务
创建方式
- 在android中使用service其实很简单,就是继承Service即可,然后在相应的生命周期函数里实现一些功能
- 当然,作为四大组件之一的service,别完了在清单文件中配置一下servic,不然服务是跑不起来的
- 生命周期里有个onBind方法,这个onBind是干嘛的呢
- 一般情况下,如果我们的服务启动后就只是默默地做它自己的任务,不想与外界有任何交流,那么就可以在onBind这个方法里返回null
- 但假如我们的服务可以对外开放自己的一些能力(在代码里就是开放一堆方法供外界调用),那么就可以把这些要开放的东西写在一个类里,而这个类要实现IBinder接口,然后我们可以在Service类里实例这个IBinder对象,在onBind这个方法里返回这个实例对象
- 这样客户端连接到这个服务时,就可以拿到这个实例对象,然后就可以愉快地调用服务开放的能力了
启动方式
- 当服务都写好了,那么该怎么启动这个服务呢
- service有两种启动方式,startService和bindService
- 这两种方式的区别是bindService方式可以被多个Activity绑定,当所有绑定的都解绑后服务才有可能会销毁,之所以说可能,是因为假如服务在bindService之前已经startService了,那么即使后面都解绑了,服务依然会存在
- startService一旦跑起来,除非调用stopService或stopSelf或者应用被杀死,否则服务会一直存在,即使中间有人通过unbindService解绑,服务也不会销毁
- 这里再强调一次,startService启动后服务销毁的条件只能是调用stopService或在服务里stopSelf或者服务所在进程被杀死
- 既然有了startService,那么bindService是干嘛的呢
- bindService主要有两个作用,一个是临时与服务建立本地通信,另一个就是跨进程通信(IPC)了
- bindService的生命周期比较短,比如在activity里bindService的话,那么activity销毁时服务也停止了(你在logcat里应该能看到一个异常has leaked ServiceConnection/was originally bound here,这个异常不会导致应用奔溃,但其实就是在提醒你要在适当时机调用unbindService),当然,为了消除这个异常,你完全可以在Activity的onDestroy里调用unbindService,如果你没手动调用unbindService,activity销毁时也会帮你解绑,解绑后如果发现服务不是startService启动的,而且已经没有其他人与之绑定了,那么就会销毁这个服务,只要还有其他人与之绑定,那么服务还是继续存在
生命周期
- 服务的生命周期与启动方式有一定的联系
- startService启动服务的话一般会经历 onCreate --> onStartCommand -> onStart(已经标记过时) -> onDestroy
- bindService分为两种,
- 一种是bindService之前服务还不存在: onCreate --> onBind --> onUnbind --> onDestroy
- 一种是bindService之前服务已经存在: onBind --> onUnbind --> onDestroy
- 不管怎么样,反正服务的onCreate只会执行一次,onBind也只会执行一次,这一点牢记即可
- 当你多次调用startService的话,你会发现onStartCommand和onStart就会被多次调用
- 当你多次调用bindService,onBind只会执行一次
- stopService调用多少次都无所谓,而unbindService与bindService要成对的,否则会引发异常,即调用unbindService时发现之前根本就没有绑定,那么就会抛出异常
- 为了解决unbindService这个异常,你可以在bindService的ServiceConnection的onServiceConnected的时候设置一个标记,然后根据这个标记来决定是否调用unbindService
onStartCommand参数及返回值的理解
- 以下是android的默认实现
public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
- onStartCommand有三个参数,第一个就是启动这个服务的Intent,这个似乎没什么好说的,需要注意的是这个intent有可能为空,因为服务可能被异常终止,然后被系统重新启动,此时intent为不为空得看之前onStartCommand的返回值
- 第二个参数flags总共有三个值,0和START_FLAG_REDELIVERY和START_FLAG_RETRY;其中0就是我们正常startService时这个flags就是0,如果我们的服务由于某些异常由android系统重启,那么flags才会是另外两个值;START_FLAG_RETRY表示onStartCommand方法没正常返回时系统进行的重试,START_FLAG_REDELIVERY就是onStartCommand能正常返回,但后边服务遇到异常被系统重启
- 第三个参数就是每次startService都会进行自增的一个id,这个id的唯一性由系统保证,为啥要有这个id呢,主要是为了防止服务在需要的时候被终止了,试想一下,如果你通过stopSelf()去终止服务的时候,可能有其他人正在执行onStartCommand,说明他正需要这个服务,但却被你终止了
- 如果你使用stopSelfResult(int starId),那么由于其他人执行到onStartCommand已经产生了新的startId,那么你的这个终止服务的操作就会失效,这样其他人就可以正常使用服务了
- 看到这里, 推荐你使用stopSelfResult(int starId)而不是stopSelf()
- 终止服务有stopSelfResult和stopSelf两个方法,如下
- 参数了解完了,让我们看看返回值,其实返回值就是告诉系统在服务被异常终止时该如何重新启动此服务,一般情况下直接用super即可,如果想知道详细的,可以看下这篇文章: Service的onStartCommand方法的返回值和参数详解_风行天下-CSDN博客_startcommand
ServiceConnection
- 通过bindService这种方式需要一个ServiceConnection对象,这个对象主要有两个回调
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
-
在绑定成功后onServiceConnected就会被回调,而且只会回调一次,也就是说你即使多次调用bindService,onServiceConnected也只会执行一次,只有当unbindService后再次bindService,onServiceConnected才会又被调用一次
-
onServiceDisconnected这个只有在服务崩溃或被终止时才会被调用,unbindService是不会调用到onServiceDisconnected的
-
onServiceConnected方法里的第二个参数就是我们在Service类里onBind方法返回的对象
场景说明
- 下载任务的场景非常适合服务,因为你不可能在一个activity里启动下载,然后activity销毁就停止下载了吧,或者你启动下载后去干别的事了,干别的事的过程中又想知道下载的进度等等,那么结合使用startService和bindService就非常有用
- 假如你有一个任务,这个任务没有界面,也不与任何activity有关系,那么你就可以使用startService,即使是在某个activity里调用startService的,但当activity销毁时service也不会停止,像下载任务这类就比较合适
- 假如你已经运行到某个activity了,而且你知道后台有个服务在跑,你在某个activity里想知道这个服务的一些信息,那么就可以使用bindService,如果这个服务不存在,那么bindService会创建一个,但activity销毁时这个服务会销毁(如果有多个activity bind的话,那么当最后一个activity销毁时服务才会停止)
- 假如你知道另外一个进程的服务,想与之交互,那么也可以通过bindService进行跨进程通信
前台服务
- 服务几乎都是在后台运行的,但是服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收正在后台运行的服务
- 如果你希望可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务
- 前台服务和普通服务最大的区别就在于,他会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果
- 前台服务最重要的几个api就是
//这表明将要开启的是一个前台服务,用户必须自己在5秒内调用startForeground方法,否则就会产生奔溃
startForegroundService(...)
//启动前台服务,前面是一个id,只要不与其他人相同即可,后面的参数是一个Notification 对象,就是会在通知栏显示的那个
startForeground(NOTIFICATION_DOWNLOAD_PROGRESS_ID,notification);
//停止前台服务,参数表示是否需同时移除状态栏通知
stopForeground(true);
- 另外,如果api在28(android9)及以上,则需要声明android.permission.FOREGROUND_SERVICE权限,这是个普通权限,在xml里声明一下即可,否则会抛SecurityException
- 还有你可能想知道Service是否是前台服务(即是否调用过startForeground),则可以使用android.app.Service#getForegroundServiceType接口,不过此接口需要api29及以上才能调用
问答
startService和bindService顺序的影响
- startService启动服务的优先级比较高,就是说当你bindService后再startService,那么当你unbindService后服务还是存在的
如果你的服务也想让其他应用启动那该怎么办
- 可以使用隐式启动方式在清单文件中给服务一个过滤器,如
<service
android:name="com.example.myjavaexample.MyService"
android:enabled="true" >
<intent-filter android:priority="1000" >
<action android:name="com.abc.xxx.myservice" />
</intent-filter>
</service>
- 然后其他应用就可以如下启动你的服务了
Intent intent=new Intent();
intent.setAction("com.abc.xxx.myservice");
intent.setPackage("com.example.myjavaexample");//设置应用的包名
startService(intent);
service被系统杀死后会执行到Service.onDestroy吗
- 在不同的手机设备上这个行为不确定,经测试,通过开发者选项杀死正在运行的服务,有的能执行到onDestroy,有的并没有
其他
- 当在activity里bindService而在activity销毁时没有手动调用unbindService时,会抛出类似异常
com.example.myjavaexample.MainActivity has leaked ServiceConnection com.example.myjavaexample.MainActivity$1@d26642d that was originally bound here
- 这个异常只会打印在日志中,并不会导致你app挂掉
- 当你bindService成功后在后台任务里把app杀掉,是不会调用onUnbind和onDestroy的
- 有时发现调用unbindService时并没执行到onUnbind,概率性会这样,不知为何
- 关于IntentService可以查看这篇文章:Android开发–IntentService的用法,你错过了什么 - 中国人醒来了 - 博客园