Service 作为四大组件之一, 在 Android 开发中的地位举足轻重. Service 主要用于完成后台任务, 根据使用场景的不同, Service 有着不同的特性, 在本篇文章中将着重介绍.
一, Service 简介
Service 是 Android 程序中四大基础组件之一, 它和 Activity 一样都是 Context 的子类, 只不过它没有 UI 界面, 是在后台运行的组件.
Service 是 Android 中实现程序后台运行的解决方案, 它非常适用于去执行那些不需要和用户交互而且还要求长期运行的任务. Service 默认并不会运行在子线程中, 它也不运行在一个独立的进程中, 它同样执行在 UI 线程中, 因此, 不要在 Service 中执行耗时的操作, 除非你在 Service 中创建了子线程来完成耗时操作.
二, Service 种类
按运行地点分类:
按运行类型分类:
按使用方式分类:
三, Service 生命周期
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 系统也可能结束服务, 还有一种方法可以关闭服务, 在设置中, 通过应用 ->找到自己应用 ->停止.
注意点:
1第一次 startService 会触发 onCreate 和 onStartCommand, 以后在服务运行过程中, 每次 startService 都只会触发 onStartCommand
2不论 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?
1如果你只是想要启动一个后台服务长期进行某项任务那么使用 startService 便可以了.
2如果你想要与正在运行的 Service 取得联系, 那么有两种方法, 一种是使用 broadcast , 另外是使用 bindService , 前者的缺点是如果交流较为频繁, 容易造成性能上的问题, 并且 BroadcastReceiver 本身执行代码的时间是很短的(也许执行到一半, 后面的代码便不会执行), 而后者则没有这些问题, 因此我们肯定选择使用 bindService(这个时候你便同时在使用 startService 和 bindService 了, 这在 Activity 中更新 Service 的某些运行状态是相当有用的).
3如果你的服务只是公开一个远程接口, 供连接上的客服端 (Android 的 Service 是 C/S 架构) 远程调用执行方法. 这个时候你可以不让服务一开始就运行, 而只用 bindService , 这样在第一次 bindService 的时候才会创建服务的实例运行它, 这会节约很多系统资源, 特别是如果你的服务是 Remote Service, 那么该效果会越明显(当然在 Service 创建的时候会花去一定时间, 你应当注意到这点). 这部分主要应用是 AIDL
四, 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"
配置好 Service 类, 只需要在前台, 调用 startService()方法, 就会启动耗时操作.
注意:
1不运行在一个独立的进程中, 它同样执行在 UI 线程中, 因此, 在 Service 中创建了子线程来完成耗时操作.
2当 Service 关闭后, 如果在 onDestory()方法中不关闭线程, 你会发现我们的子线程进行的耗时操作是一直存在的, 此时关闭该子线程的方法需要直接关闭该应用程序. 因此, 在 onDestory()方法中要进行必要的清理工作.
2. 可交互的后台服务
可交互的后台服务是指前台页面可以调用后台服务的方法, 通过 bindService()方式开启. Service 的生命周期很简单, 分别为 onCreate,onBind,onUnBind,onDestroy 这四个.
可交互的后台服务实现步骤是和不可交互的后台服务实现步骤是一样的, 区别在于启动的方式和获得 Service 的代理对象.
创建服务类
和普通 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. 前台服务
所谓前台服务只不是通过一定的方式将服务所在的进程级别提升了. 前台服务会一直有一个正在运行的图标在系统的状态栏显示, 非常类似于通知的效果.
由于后台服务优先级相对比较低, 当系统出现内存不足的情况下, 它就有可能会被回收掉, 所以前台服务就是来弥补这个缺点的, 它可以一直保持运行状态而不被系统回收.
创建服务类
前台服务创建很简单, 其实就在 Service 的基础上创建一个 Notification, 然后使用 Service 的 startForeground()方法即可启动为前台服务.
- 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("2017-2-27")
- .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(this, ForeService.class));
关于 TaskStackBuilder 这一段, 可能不是看的很明白, 下面详细介绍.
TaskStackBuilder 在 Notification 通知栏中的使用
首先是用一般的 PendingIntent 来进行跳转
- mBuilder = new NotificationCompat.Builder(this).setContent(view)
- .setSmallIcon(R.drawable.icon).setTicker("新资讯")
- .setWhen(System.currentTimeMillis())
- .setOngoing(false)
- .setAutoCancel(true);
- Intent intent = new Intent(this, NotificationShow.class);
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
- intent, PendingIntent.FLAG_UPDATE_CURRENT);
- mBuilder.setContentIntent(pendingIntent);
这里是直接用 PendingIntent 来跳转到 NotificationShow.
在运行效果上来看, 首先发送了一条 Notification 到通知栏上, 然后这时, 退出程序, 即 MainActivity 已经不存在了, 回到 home 主菜单, 看到 Notification 仍然存在, 当然, 我们还没有点击或者 cancel 它, 现在去点击 Notification, 跳转到 NotificationShow 界面, 然后我们按下 Back 键, 发现直接回到 home 菜单了. 现在大多数 Android 应用都是在通知栏中如果有 Notification 通知的话, 点击它, 然后会直接跳转到对应的应用程序的某个界面, 这时如果回退, 即按下 Back 键, 会返回到该应用程序的主界面, 而不是系统的 home 菜单. 所以用上面这种 PendingIntent 的做法达不到目的. 这里我们使用 TaskStackBuilder 来做.
- mBuilder = new NotificationCompat.Builder(this).setContent(view)
- .setSmallIcon(R.drawable.icon).setTicker("新资讯")
- .setWhen(System.currentTimeMillis())
- .setOngoing(false)
- .setAutoCancel(true);
- Intent intent = new Intent(this, NotificationShow.class);
- TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
- stackBuilder.addParentStack(NotificationShow.class);
- stackBuilder.addNextIntent(intent);
- PendingIntent pendingIntent = stackBuilder.getPendingIntent(0,
- PendingIntent.FLAG_UPDATE_CURRENT);
- // PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
- // intent, PendingIntent.FLAG_UPDATE_CURRENT);
- mBuilder.setContentIntent(pendingIntent);
显示用 TaskStackBuilder.create(this)创建一个 stackBuilder 实例, 接下来 addParentStack();
关于这个方法, 我们查一下官方 API 文档:
Add the activity parent chain as specified by the parentActivityName attribute of the activity (or activity-alias) element in the application's manifest to the task stack builder
这句话意思是: 为跳转后的 activity 添加一个父 activity, 在 activity 中的 manifest 中添加 parentActivityName 即可.
那么我们就在 manifest 文件中添加这个属性
- <activity
- Android:name="com.lvr.service.NotificationShow"
- Android:parentActivityName=".MainActivity">
- </activity>
这里我让它的 parentActivity 为 MainActivity, 也就是说在 NotificationShow 这个界面点击回退时, 会跳转到 MainActivity 这个界面, 而不是像上面一样直接回到了 home 菜单.
注意: 通过 stopForeground()方法可以取消通知, 即将前台服务降为后台服务. 此时服务依然没有停止. 通过 stopService()可以把前台服务停止.
以上是关于 Service 的内容.
自己是从事了七年开发的 Android 工程师, 不少人私下问我, 2019 年 Android 进阶该怎么学, 方法有没有?
没错, 年初我花了一个多月的时间整理出来的学习资料, 希望能帮助那些想进阶提升 Android 开发, 却又不知道怎么进阶学习的朋友.[包括高级 UI, 性能优化, 架构师课程, NDK,Kotlin, 混合式开发(ReactNative+Weex),Flutter 等架构技术资料] , 希望能帮助到您面试前的复习且找到一个好的工作, 也节省大家在网上搜索资料的时间来学习.
来源: http://www.jianshu.com/p/d289d2924029