简介

Service(服务)是 Android 四大组件之一,它的主要作用是执行后台操作,Activity 提供了 UI 界面来跟用户交互,而 Service 则没有 UI 界面,所有的操作都是在后台完成。

Service 跟 Activity 一样也可以由其它应用程序启动,即使用户切换到了其它应用,Service 仍然保持在后台运行。

此外,一个组件可以与 Service 进行绑定(bind)来跟 Service 进行交互,甚至是进行进程间通信(IPC)。

通常情况下可以使用 Service 进行网络请求、播放音乐、文件 I/O 等操作。

创建服务

要创建一个 Service 首先需要继承 Service 来实现一个子类。

public class TestService extends Service {@Overridepublic void onCreate() {super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags,int startId) {return super.onStartCommand(intent, flags, startId);}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic boolean onUnbind(Intent intent) {return super.onUnbind(intent);}@Overridepublic void onDestroy() {super.onDestroy();}
}

类似于 Activity,所有的 Service 都要在 Manifest 里面进行声明,如下:

<manifest ... >...<application ... ><service android:name="xxx.xxxs.TestService" />...</application>
</manifest>

通过在 <service> 标签里将 android:exported 设置为 false。可以防止其他的程序来启动你的 Service。

启动服务

通常情况下有两种方式来启动 Service,startService()bindService()

startService()

Intent intent = new Intent(this, TestService.class);
startService(intent); // 开启服务
stopService(intent); // 停止服务

当组件通过调用 startService() 启动 Service 后,Service 就可以在后台无限期的运行,即使启动 Service 的组件被销毁也不受影响。

一般情况下 startService() 是执行单一操作,并且不会将执行结果返回给调用者。例如,它可能是下载文件或者上传文件,通常操作完成后会自动停止。

该方式允许多个组件同时对相同的 Service 进行 startService() 操作,但是如果只要有其中有一个组件调用了 stopSelf()stopService(), 该 Service 就会被销毁。

bindService()

Intent intent = new Intent(this, TestService.class);
ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {}@Overridepublic void onServiceDisconnected(ComponentName name) {}
};
// 绑定服务
bindService(intent, connection, Context.BIND_AUTO_CREATE);
// 解绑服务
unbindService(aidlConnection);

当组件通过调用 bindService() 启动 Service 后,Service 就处于绑定状态了。这种方式提供了 client - service 的接口,可以让调用者与 Service 进行发送请求和返回结果的操作,甚至可以进行进程间的通信(IPC)。

只要有一个组件对该 Service 进行了绑定,那该 Service 就不会销毁。如果多个组件可以同时对一个 Service 进行绑定,只有所有绑定的该 Service 的组件都解绑后,该 Service 才会销毁。

尽管两种方式是分开讨论的,但是并不是互斥的关系,使用 startService() 启动了 Service 后,也是可以进行绑定的。

注意:虽然 Service 是在后台运行的,但其实还是在主线程中进行所有的操作。Service 启动时除非单独进行了定义,否则没有单独开启线程或者进程都是运行在主线程中。

所以任何能阻塞主线程的操作(例如:播放音乐或者网络请求),都应该在 Service 中单独开启新的线程来进行操作,否则很容易出现 ANR。

系统方法

在创建一个 Service 时,必须要去继承 Service,并且需要重写父类的一些方法来实现功能。以下是主要方法的介绍。

onStartCommand()

当另一个组件(如:Activity)通过调用 startService() 来启动 Service 时,系统会调用该方法。一旦执行该方法,Service 就会启动并在后台无限期执行。

如果实现该方法,在 Service 执行完后,需要调用 stopSelf() 或 stopService() 来停结束Service。

如果只是会通过绑定的方式(bind)的方式来启动 Service 则不需要重写该方法。

onBind()

系统会调用这个函数当某个组件(例如:activity,fragment)通过调用 bindService() 绑定的方式来启动 Service 的时候。在实现这个函数的时候,必须要返回一个 IBinder 的继承类,来与 Service 进行通信。

这个函数是默认必须要重写的,但是如果不想通过绑定的方式来启动 Service,则可以直接返回 null

onCreate()

系统会调用此方法在第一次启动 Service 的时候,用于初始化一些一次性的变量。如果 Service 已经启动了,则此方法就不会再别调用。

onDestroy()

系统在 Service 已经不需要准备被销毁的时候会调用此方法。Service 中如有用到 thread、listeners、receivers 等的时候,应该将这些的清理方法写在此方法内。

生命周期

与 Activity 类似,Service 也有生命周期回调方法,可以实现这些方法来监控 Service 状态的变化来执行相关操作。

startService()

onCreate() -> onStartCommand() -> onDestroy()

bindService()

onCreate() -> onBind() -> onUnbind() -> onDestroy()

系统资源回收

当系统内存不足的时候,系统会强制回收一些 Activity 和 Service 来获取更多的资源给那些用户正在交互的程序或页面。当资源充足的时候可以通过 onStartCommand() 的返回值,来实现 Service 自动重启。

public int onStartCommand(Intent intent, int flags, int startId) {return START_NOT_STICKY | START_STICKY | START_REDELIVER_INTENT;
}

START_NOT_STICKY

当系统因回收资源而销毁了 Service,当资源再次充足时不再自动启动 Service,除非有未处理的 Intent 准备发送。

START_STICKY

当系统因回收资源而销毁了 Service,当资源再次充足时自动启动 Service。而且再次调用 onStartCommand() 方法,但是不会传递最后一次的 Intent,相反系统在回调 onStartCommand() 的时候会传一个空 Intent,除非有未处理的 Intent 准备发送。

START_REDELIVER_INTENT

当系统因回收资源而销毁了 Service,当资源再次充足时自动启动 Service,并且再次调用 onStartCommand() 方法,并会把最后一次 Intent 再次传递给 onStartCommand(),相应的在队列里的 Intent 也会按次序一次传递。此模式适用于下载等服务。

IntentService

Service 本身默认是运行在主线程里的,所以如果在 Service 要进行一些会堵塞线程的操作,一定要将这些操作放在一个新的线程里。

为了满足后台运行异步线程的需求,Android 的框架提供了 IntentService。

IntentService 是 Service 的子类,并且所有的请求操作都是在异步线程里。如果不需要 Service 来同时处理多个请求的话,IntentService 将会是最佳的选择。

使用该服务只需要继承并重写 IntentService 中的 onHandleIntent() 方法,就可以对接受到的 Intent 做后台的异步线程操作了。

public class TestIntentService extends IntentService {public TestIntentService() {super("TestIntentService");}public TestIntentService(String name) {super(name);}@Overridepublic void onCreate() {super.onCreate();}@Overrideprotected void onHandleIntent(@Nullable Intent intent) {//TODO: 耗时操作,运行在子线程中}@Overridepublic void onDestroy() {super.onDestroy();}
}

前台服务

什么是前台服务?

前台服务是那些被认为用户知道(用户所认可的),且在系统内存不足的时候不允许系统杀死的服务。

前台服务必须给状态栏提供一个通知,它被放到正在运行(Ongoing)标题之下 —— 这就意味着通知只有在这个服务被终止或从前台主动移除通知后才能被解除。

为什么要使用前台服务?

在一般情况下,Service 几乎都是在后台运行,一直默默地做着辛苦的工作。但这种情况下,后台运行的Service系统优先级相对较低,当系统内存不足时,在后台运行的 Service 就有可能被回收。

那么,如果我们希望 Service 可以一直保持运行状态,且不会在内存不足的情况下被回收时,可以选择将需要保持运行的 Service 设置为前台服务。

例如:App中的音乐播放服务应被设置在前台运行(前台服务)——在 App 后台运行时,便于用户明确知道它的当前操作、在状态栏中指明当前歌曲信息、提供对应操作。

如何创建一个前台服务?

新建一个服务。

public class ForegroundService extends Service {private static final int RESULT_CODE = 0;private static final int ID = 1;public ForegroundService() { }@Overridepublic void onCreate() {super.onCreate();Intent intent = new Intent(this, MainActivity.class);PendingIntent pendingIntent = PendingIntent.getActivity(this, RESULT_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);NotificationCompat.Builder builder;// 兼容 Android 8.0if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {String channelId = "foreground_service";NotificationChannel channel = new NotificationChannel(channelId, "channel_1", NotificationManager.IMPORTANCE_HIGH);channel.enableLights(true);channel.setLightColor(Color.GREEN);channel.setShowBadge(true);NotificationManager notificationManager = getSystemService(NotificationManager.class);notificationManager.createNotificationChannel(channel);builder = new NotificationCompat.Builder(this, channelId);} else {builder = new NotificationCompat.Builder(this);}builder.setContentIntent(pendingIntent).setContentTitle("这是前台通知标题").setContentText("这是内容").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher_round).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).setPriority(NotificationManager.IMPORTANCE_HIGH).setDefaults(Notification.DEFAULT_SOUND);startForeground(ID, builder.build());}@Overridepublic int onStartCommand(Intent intent, int flags,int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic IBinder onBind(Intent intent) {return super.onBind(intent);}
}

启动与停止前台服务

Intent foregroundIntent = new Intent(this, ForegroundService.class);
startService(foregroundIntent); // 启动前台服务
stopService(foregroundIntent); // 停止前台服务

前台服务与普通服务的区别

  • 前台 Service 的系统优先级更高、不易被回收;
  • 前台 Service 会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

服务保活

通过前面的介绍我们了解到 Service 是后台服务来执行一些特定的任务,但是当后台服务在系统资源不足的时候可能会回收销毁掉 Service。

那么如何让后台服务尽量不被杀死呢?基本解决思路如下:

提升 Service 的优先级

为防止 Service 被系统回收,可以尝试通过提高服务的优先级解决。优先级数值最高为 1000,数字越小,优先级越低。

<service android:name=".ui.service.TestService" ><intent-filter android:priority="1000"/>
</service>

persistent 属性

在 Manifest.xml 文件中设置 persistent 属性为 true,则可使该服务免受 out-of-memory killer 的影响。但是这种做法一定要谨慎,系统服务太多将严重影响系统的整体运行效率。

<application android:persistent="true">
</application>

该属性的特点如下:

  • 在系统启动的时候会被系统启动起来。
  • 在该 App 被强制杀掉后系统会重新启动该 App,这种情况只针对系统内置App,第三方安装的 App 不会被重启。

将服务改成前台服务

重写 onStartCommand 方法,使用 startForeground(int, Notification) 方法来启动 Service。利用 Android 的系统广播

利用 Android 的系统广播检查 Service 的运行状态,如果被杀掉就重启。系统广播是 Intent.ACTION_TIME_TICK,这个广播每分钟发送一次。我们可以每分钟检查一次 Service 的运行状态,如果已经被销毁了,就重新启动 Service。

参考资料

  • 官方文档 - 服务
  • Service 前台服务的使用

我的 GitHub

github.com/jeanboydev

我的公众号

欢迎关注我的公众号,分享各种技术干货,各种学习资料,职业发展和行业动态。

技术交流群

欢迎加入技术交流群,来一起交流学习。

如何正确的使用 Service?相关推荐

  1. QXTEND QUERY SERVICE调试成功

    在之前成功通过QXTEND向QAD写入数据后,想实现如何通过QXTEND来查询数据.目的主要有两个: 1.是想通过PYTHON来调用QXTEND发布的WEB SERVICE,来获取数据. 2.后续的s ...

  2. Writing a Winsock 2 Layered Service Provider

    Wei Hua, Jim Ohlund, Barry Butterklee 著 来源:http://greatdong.blog.edu.cn 作者:董岩 译 greatdong_2001@163.c ...

  3. K8s常见问题:Service 不能访问排查流程

    问题1:无法通过 Service 名称访问 如果你是访问的Service名称,需要确保CoreDNS服务已经部署: [root@k8s-master ~]# kubectl get pods -n k ...

  4. Labview发布web service时出现错误 Error LabVIEW: (Hex 0xFFFEFA29)

    这里写自定义目录标题 问题由来 解决方案 问题由来 按照labview的帮助文档,练习web service,在发布的时候提示错误Error LabVIEW: (Hex 0xFFFEFA29). 解决 ...

  5. liunx 下dhcp中继及服务器配置

    dhcp:动态主机配置协议 使用udp协议 端口为67(服务),68(客户) 作用:动态分配地址等参数 工作模式 1. 手工 manual server-地址池 (ip-mac) 2222----1. ...

  6. Apache安装80端口被占用解决方案

    Windows系统 如果80端口被占用,安装Apache的过程中,可能无法成功安装Apache的Servcie.如果不能正确安装Apache Service,那么执行Start或Restart等命令时 ...

  7. Android性能优化典范第四季

    原文链接:http://hukai.me/android-performance-patterns-season-4/ 前言 本季内容大致有:优化网络请求的行为,优化安装包的资源文件,优化数据传输的效 ...

  8. GHOST_XP详细制作过程

    GHOST XP制作基本步骤 1.安装好系统和必要的软件,清理垃圾,确保系统干净无错误 2.关闭系统还原,设置页面文件为0.在设备管理器中,更改"IDE ATA/ATAPI控制器" ...

  9. Linux配置脚本导出运行,linux服务器部署jar包以及shell脚本的书写

    背景:记录在linux环境下部署jar程序的过程 1 部署过程记录 1.1 程序结构 这里的main函数就在DemRest2.java 文件中. 为了部署方便,要做到以下两点: 1 在导出的jar包中 ...

最新文章

  1. /etc/inittab
  2. boost::externally_locked相关的测试程序
  3. Sentinel实现黑白名单控制详细教程来了
  4. php实现目录及目录文件下的遍历
  5. 好玩的Scratch
  6. 设备上专用计算机管理办法,计算机设备管理办法
  7. Linux面试题附答案
  8. 从命令行安装Deb文件的5种方法
  9. 苹果Apple TV+上线了重磅史诗级别科幻作品,这是要挑战Netflix、HBO?
  10. [MapReduce] Counter
  11. 阿里、京东、亚马逊为何如此重视重构“会员”?
  12. 每月与英国签证官网聊?
  13. 批量移动文件夹到对应文件夹
  14. 图片工具GraphicsMagick的下载安装配置使用
  15. SUS2019迎新赛ret2moonWP
  16. PowerQuery操作分类
  17. Linux下的文件管理
  18. 正在连接localhost...无法打开到主机的连接。 在port 8080: 连接失败
  19. 原始GPS经纬度转换为距离(c/c++)
  20. vc_redist 又名VC runtime library,或MSCVRT

热门文章

  1. mysql数据库快捷键_MySQL数据库(YOG软件)快捷键大全
  2. 修改html2canvas生成图片的dpi
  3. AD软件PCB快捷键
  4. 苹果cms怎么采集别人网站的视频
  5. 腾讯文档网页版登录提示服务器,腾讯文档官网地址,腾讯文档电脑版pc端登录入口...
  6. Ubuntu14.04上安装calamari
  7. D3.js绘制竖向组织架构图
  8. k8s iptable升级到ipvs
  9. 用fiddler+chrome搞定在线学习网站
  10. AES与RSA混合加密完整实例