我要做 Android 弹弹之 Service
谢谢 minmin小姐姐的总结:
Service (服务) 是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求能够长期运行的任务。Service 主要负责与 UI 无关的工作,比如耗时操作。
一:Service 概要
(1)开启子线程
(2)异步消息处理机制
二:Service 周期
三:Service 的基本用法
(1)普通 Service
(2)前台 Service
(3)系统 Service
demo:后台定时任务复制代码
(4)IntentService
四:Service 和 Activity 之间的通信
1 Service 概要
Service 的运行不依赖于任何用户界面,因此即便程序被切换到后台或者用户打开了另一个应用程序,Service 任然能够保持正常运行。但当某个应用程序进程被 kill 的时候,所有依赖于该进程的服务也都会停止运行。
同时注意注意,Service 是四大组件之一,它不是运行在线程或者独立进程中的,它执行在 UI 线程。所以说,就不能够在 Service 中执行耗时操作,除非手动打开一个子线程,否则有可能会出现主线程被阻塞的情况。即 ANR。
(1)相信大家对于开启一个线程应该不会感觉到困难,最常用的方法应该就是直接 new Thread 然后实现 Runnable 接口,在调用它的 start() 方法,就可以在重写的 run 方法中执行耗时操作。example:
(2)异步消息处理机制
还要注意一点:Android不允许在子线程中进行UI操作。但有时候,在子线程里执行一些耗时任务之后需要根据任务的执行结果来更新相应的UI控件,在这里Android提供了一套异步消息处理机制,它可以很好地解决在子线程中更新UI的问题。主要用到两个类:Handler(处理者,主要用于发送和处理消息)和Message(信息,可携带少量信息用于在不同线程之间交换)。下图展示了如何用它们实现从子线程到主线程的转换:
可以看到,只要在需要转换到主线程进行UI操作的子线程中实例化一个Message对象并携带相关数据,再由Handle的sendMessage()将它发送出去,之后这个数据就会被在主线程中实例化的Handle对象的重写方法handleMessage()收到并处理。现在在子线程中更新UI就很容易了。
2.Service生命周期
官方文档提供的Sevice生命周期图如下:
先来看看这几种回掉方法的含义:
onCreate():服务第一次被创建时调用
onStartCommand() : 服务启动时调用
onBind(): 服务被绑定的时候调用
onUnBind(): 服务被解绑的时候调用
onDestroy(): 服务停止时调用
从上面的途中可以看出有两种方法启动 Service,下面来分别介绍:
第一种:其他组件调用 Context 的 StartService() 方法就可以启动一个 Service,并回掉服务中的 onStartCommand()。如果该服务之前还没有创建,那么回掉的顺序是 onCreate() -> onStartCommand()。服务启动之后会一直保持运行状态,直到 stopService() 或 stopSelf() 方法被调用,服务停止并回掉 onDestroy()。另外,无论你启动调用多少次 startService() 方法,只需要调用一次 stopService() 或者 stopService() 方法,服务就会停止了。
第二种:其他组件调用 Context 的 bindService() 可以绑定一个 Service,并回掉服务中的 onBind() 方法。类似,如果在这之前服务还没有创建,那么回掉顺序是 onCreate() -> onBind() 。之后,调用方法可以获取到 onBind() 方法里返回的 IBinder 对象的实例,从而实现和服务进行通信。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态,一直等到调用了 unbindService() 方法之后服务就会停止,回掉顺序是 onUnBind() -> onDestroy()。
tip:这两种启动方法并不冲突,当你先使用 startService() 启动了 Service 之后,还可以在使用 bindService() 绑定,只不过需要同时调用 stopService() 和 unbindService() 方法才能让服务销毁。
3 Service的基本用法
介绍完 Service 生命周期和启动方法之后,下面再来看看具体的 example
(1)普通 Service
第一:新建类并继承 Service 且必须重写 onBind() 方法,有选择的重写 onCreate(),onStartCommand() 以及 onDestroy() 方法。
第二:在配置文件中进行注册。这里提示大家 Android 的四大组件除了 广播 可以使用动态注册,其他定义好的组件都需要完成在配置文件中注册这一步才阔以。
第三:在活动中利用 Intent 可以实现 Service 的启动。(Intent 给我的感觉好像是什么都能启动,,,)
停止Service方法:
Intent intent = new Intent(this, MyService.class);stopService(intent);复制代码
好,下面让我们来写一个实际的例子,定义一个 MyService,重写以下四种方法并且都打印一行日志。
看 MainActivity
不要忘了注册
看看点击开始的时候运行结果
点击stop的时候结果
2 前台Service
前台服务和普通服务最大的区别是,前者会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。使用前台服务或者为了防止服务被回收掉,比如听歌,或者由于特殊的需求,比如实时天气状况。
想要实现一个前台服务非常简单,它和之前学过的发送一个通知非常类似,只不过在构建好一个Notification之后,不需要NotificationManager将通知显示出来,而是调用了startForeground()方法。
修改 MyService 的 onCreate() 方法
这里我在测试的时候出现了一个问题。就是在 Android 8.1 上的系统会报错,直接导致 APP 重启,我又在自己的真机上测试(8.0),虽然没有报错,但是没有像想象中那样出现前台通知,我看了日志,onCreate 和 onStartCommand 也是正常打印的。具体原因不清楚,我想 Google 一下,看到 StackOverflow 好像也有人说了这个错误。说是需要创建自己的通知通道,还有一个描述和提示的一样就是需要创建一个 notification channel 才可以。
这是 StackOverflow 上的回答解决方法。我在 7.0 上运行正常显示
这是运行结果。下面来看代码部分
在 Service 中修改 onCreate 方法即可。当然你在 onStartCommand 方法中使用也是 OK 的。我还特地网上搜了一下,好像更多的人是使用在 onStartCommand 上。
除了我们自己开启的 Service 之外,还有哪些 系统 Service 呢?
通过 getSystemService() 方法并传入一个 Name 就可以得到相应的服务对象。
我们现在来再看一个系统服务 AlarmManager,来实现一个后台定时任务。调用 AlarmManager 的 set() 方法可以设置一个定时任务,并提供三个参数(工作类型,定时任务触发的时间,PendingIntent 对象)。下面一一解释以上三个参数。
(1)工作类型:有四个值可以选择
(2)定时任务触发的时间:以毫秒为单位,传入值和第一个参数的对应关系是:
(3)PendingIntent 对象:一般会调用它的 getBroadcast() 方法来获取一个能够执行广播的 PendingIntent。这样当定时任务被触发的时候,广播接收器的 onReceive() 方法就可以得到执行。
接着来看代码,修改 MyService,将前台服务代码都删掉,重写 onStartCommand() 方法,这里先获取 AlarmManager 的实例,然后定义任务的触发时间为 10s 后,在使用 PendingIntent 指定处理定时任务的广播接收器为 MyReceiver,最后调用 set() 方法来完成设定。
在这里需要定义一个广播接收器为 MyBroadcast() ,在 onReceiver 方法中通过 Intent 对象去启动 MyService 这个服务。这样做之后,10s 后 广播的 onReceive 方法就会执行,接着启动 MyService,反复循环。这样就可以有一个能长期在后台进行定时任务的服务就完成了。
别忘了注册广播
另外,从Android 4.4版本开始,由于系统在耗电性方面进行了优化使得Alarm任务的触发时间会变得不准确。如果一定要求Alarm任务的执行时间精确,把AlarmManager的setExact()方法替代 set()方法就可以了。
说实话,感觉 minmin 小姐姐确实厉害。我从大二开始接触 Android,大三开始比较详细的学习 Android 和做项目,但是对于基础基本的使用还不够好,这次对照着小姐姐的总结材料来复习一遍确实补充了很多之前没有注意到的知识点。
(4)IntentServie
为了可以简单的创建一个异步的,会自动停止 的服务,Android 专门提供了一个 IntentService 类。它和普通 Service 的使用非常像。
第一:新建一个类继承 IntentService,在这里需要提供一个无参的构造函数且必须在其内部调用父类的有参构造函数,然后具体实现 onHandleIntent() 方法,在这里可以处理一些耗时操作而不用担心 ANR 的问题,因为这个方法本身就是运行在子线程中。
第二:同样需要在配置中进行注册。
第三:在活动中利用 Intent 实现 IntentService 的启动,和 Service 用的方法完全一样。
别忘记注册,注册
MainActivity 中的代码
运行结果如下
还有一个主线程
4.Service与Activity的通信
这个是 Service 的最后一部分的内容了。如果想和 Activity 通信的话,需要借助服务的 onBind() 方法。比如如果想在 MyService 中提供一个下载功能,在活动中可以决定何时开始下载以及随时查看下载进度。
第一:在 MyService 里自定义一个类 MyBinder 并继承 Binder ,在他的内部提供了开始下载以及查看下载进度的方法,在这里就模拟打印日志。
第二:在 MyService 的 onBind() 方法里返回刚刚定义好的 MyBinder 类
第三:在活动中实例化一个 ServiceConnection 类,重写它的 onServiceConnection() 方法和 onServiceDisconnertion() 方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。在 onServiceConnection() 方法中,又通过向下转型得到了 MyBinder 的实例。之后就可以相互通信调用 MyBinder的方法了。
第四:在活动布局里再准备两个按钮用于绑定和解绑服务,在它们的点击事件里利用 Intent 对象实现活动和服务的绑定和解绑。方法是:bindService()实现绑定,它接收三个参数(Intent对象,ServiceConnection对象,标志位),这里传入BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务。unbindService() 实现解绑,传入 ServiceConnection 对象就可以了。
下面看运行结果
可以看到两个方法都已经成功调用。这里再次歇息 minmin 小姐姐的知识总结分享。
愿我们成为真实的自己