深入 Android 底层服务(service)

news2024/9/22 3:58:52

前言

我们都知道,字节最近发布了PICO4VR眼镜,我买了一个,体验还行。因为我也是做VR眼镜的Android应用层开发的,所以想把自己项目中遇到的一些Android技术分享给读者。近些年随着VR眼镜的兴起,Android的的服务(Service)和广播(Broadcast)以及内容提供者(Content Provider)越来越被重用,相反Activity这个曾经很吃香的组件在VR眼镜的开发过程中却用的不多。本节我会介绍Android的服务在VR眼镜中的使用,服务(Service)是Android的四大组件之一,很多Android的萌新在开发的时候肯定知道这个组件但是用得不是很多,毕竟刚入行更多的时候是写界面。很少会有机会去接触服务这种没有用户界面的组件。所以,让我们一起看看服务的使用吧。


一、服务是什么?

服务(Service)是一种可以在后台长时间执行而没有用户界面的组件。需要注意的是由于它运行在UI线程,因此不能做耗时操作,否则可能会引起ANR(Application Not Response: 应用无响应)。

注意:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。通过使用单独的线程,您可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。

简单说就是服务运行在主线程,不要在服务中做耗时操作,否则可能会引起ANR

二、服务(Service)的生命周期

服务的生命周期有两种形式,。因为服务可以和Activity绑定,也可以不绑定。当我们的activity需要和服务通信的时候,是需要把服务和Activity进行绑定的,因此服务的生命周期分为未绑定Activity的和绑定Activity的。我们可以用以下两张图来描述这两种绑定方式的生命周期:

(1)未绑定Activity的服务生命周期图

(1)onCreate() 当我们使用Intent和startService()方法启动了一个服务时,就会执行onCreate()方法,它会在onStartCommand()或者时onBind()方法之前被系统调用,作用是做一些初始化相关的操作。如果服务已经在运行,则onCreate()方法不会被调用 (2)onStartCommand() 当一个组件(如Activity)通过startService()请求启动服务时,系统会调用onStartCommand()方法,这个方法一旦执行,就会在后台无限期执行。如果实现了这个方法,那么就需要我们调用stopSelf()或者时stopService()来停止服务。

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

我们可以看到这个方法有个返回值,这个返回值代表什么意思呢?我们一起来看看。这个返回值是指用于描述系统应如何在系统终止服务的情况下继续运行服务。从 onStartCommand() 返回的值必须是以下常量之一:

START_NOT_STICKY

如果系统在 onStartCommand() 返回后终止服务,则除非有待传递的挂起 Intent,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

START_STICKY

如果系统在 onStartCommand() 返回后终止服务,则其会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会调用包含空 Intent 的 onStartCommand()。在此情况下,系统会传递这些 Intent。此常量适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。

START_REDELIVER_INTENT

如果系统在 onStartCommand() 返回后终止服务,则其会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。所有挂起 Intent 均依次传递。此常量适用于主动执行应立即恢复的作业(例如下载文件)的服务。

其实简单说就是系统应该以哪种方式启动服务,我在开发中经常使用的是START_STICKY这个值,它的意思是启动了服务后,当服务被杀死的时候,系统会尽最大了的努力重启服务。其他的值读者可以去看API说明,这里不一一列举了。

(3)onDestroy() 当某个操作导致服务停止,比如执行了stopService(),那么服务接下来会执行onDestroy()方法。服务一般应在这个方法中执行资源的释放操作

注意:用户可以查看其设备上正在运行的服务。如果他们发现自己无法识别或信任的服务,则可以停止该服务。为避免用户意外停止您的服务,您需要在应用清单的 <service> 元素中添加 android:description。请在描述中用一个短句解释服务的作用及其提供的好处。

(2)绑定Activity的服务生命周期图

上图中的onCreate()和onDestroy方法都和前面讲的一样,这里我们只讲onBind()和onUnbind()方法

(1)onBind()

当一个组件想通过调用bindService()与服务绑定,比如我们使用AIDL做进程间通信时,系统会调用这个方法,在这个方法的实现中,我们必须通过返回IBinder提供一个接口,来供客户端和服务器端用来与服务进行通信。

(2)onUnbind 当某个操作导致服务器解除绑定,比如执行了unbindService(),那么服务接下来会解除与当前Activity的绑定,接下来服务将面临销毁


三、服务的相关操作

(1)启动服务

我们可以通过将 Intent 传递给 startService() 或 startForegroundService(),从 Activity 或其他应用组件启动服务。Android 系统会调用服务的 onStartCommand() 方法,并向其传递 Intent,从而指定要启动的服务

注意:如果您的应用面向 API 级别 26 或更高版本,除非应用本身在前台运行,否则系统不会对使用或创建后台服务施加限制。如果应用需要创建前台服务,则其应调用 startForegroundService()。此方法会创建后台服务,但它会向系统发出信号,表明服务会将自行提升至前台。创建服务后,该服务必须在五秒内调用自己的 startForeground() 方法。

(2)停止服务

启动服务必须管理自己的生命周期。换言之,除非必须回收内存资源,否则系统不会停止或销毁服务,并且服务在 onStartCommand() 返回后仍会继续运行。服务必须通过调用 stopSelf() 自行停止运行,或由另一个组件通过调用 stopService() 来停止它。

一旦请求使用 stopSelf() 或 stopService() 来停止服务,系统便会尽快销毁服务。如果服务同时处理多个对 onStartCommand() 的请求,则不应在处理完一个启动请求之后停止服务,因为可能已收到新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为避免此问题,我们可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。

换言之,在调用 stopSelf(int) 时,我们需传递与停止请求 ID 相对应的启动请求 ID(传递给 onStartCommand() 的 startId)。此外,如果服务在您能够调用 stopSelf(int) 之前收到新启动请求,则 ID 不匹配,服务也不会停止。

注意:为避免浪费系统资源和消耗电池电量,请确保应用在工作完成之后停止其服务。如有必要,其他组件可通过调用 stopService() 来停止服务。即使为服务启用绑定,如果服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务

(3)创建绑定服务

绑定服务允许应用组件通过调用 bindService() 与其绑定,从而创建长期连接。此服务通常不允许组件通过调用 startService() 来启动它。

如需与 Activity 和其他应用组件中的服务进行交互,或需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。

如要创建绑定服务,需通过实现 onBind() 回调方法返回 IBinder,从而定义与服务进行通信的接口。然后,其他应用组件可通过调用 bindService() 来检索该接口,并开始调用与服务相关的方法。服务只用于与其绑定的应用组件,因此若没有组件与该服务绑定,则系统会销毁该服务。我们不必像通过 onStartCommand() 启动的服务那样,以相同方式停止绑定服务。

如要创建绑定服务,我们必须定义指定客户端如何与服务进行通信的接口。服务与客户端之间的这个接口必须是 IBinder 的实现,并且服务必须从 onBind() 回调方法返回该接口。收到 IBinder 后,客户端便可开始通过该接口与服务进行交互。

多个客户端可以同时绑定到服务。完成与服务的交互后,客户端会通过调用 unbindService() 来取消绑定。如果没有绑定到服务的客户端,则系统会销毁该服务。

(4)在前台运行服务

前台服务是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。

注意:如果应用面向 Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE 权限。这是一种普通权限,因此,系统会自动为请求权限的应用授予此权限。如果面向 API 级别 28 或更高版本的应用试图创建前台服务但未请求 FOREGROUND_SERVICE,则系统会抛出 SecurityException。

如要请求让服务在前台运行,请调用 startForeground()。此方法采用两个参数:唯一标识通知的整型数和用于状态栏的 Notification。此通知必须拥有 PRIORITY_LOW 或更高的优先级。下面是官网示例:

val pendingIntent: PendingIntent =
        Intent(this, ExampleActivity::class.java).let { notificationIntent ->
            PendingIntent.getActivity(this, 0, notificationIntent, 0)
        }

val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
        .setContentTitle(getText(R.string.notification_title))
        .setContentText(getText(R.string.notification_message))
        .setSmallIcon(R.drawable.icon)
        .setContentIntent(pendingIntent)
        .setTicker(getText(R.string.ticker_text))
        .build()

startForeground(ONGOING_NOTIFICATION_ID, notification)

注意:提供给 startForeground() 的整型 ID 不得为 0。

如要从前台移除服务,则调用 stopForeground()。此方法采用布尔值,指示是否需同时移除状态栏通知。此方法不会停止服务。但是,如果您在服务仍运行于前台时将其停止,则通知也会随之移除。

(5)服务的容错处理

这里的容错处理主要是用在我们使用服务来做一些IPC通信的时候,比如有这样一个例子,我们需要将一个投屏的SDK接入到项目中,这时我们可以将投屏的SDK做二次封装,然后再给项目用,但是我们又怕这个投屏的SDK写的BUG影响我们的APP,所以为了防止投屏SDK崩溃而导致我们的应用崩溃,我们选择将投屏的SDK再开一个进程去实现,也就是将投屏的SDK做成一个服务端,我们的项目做客户端。客户端和服务端可以通过IPC来通信。这样投屏SDK就算崩溃也是在它自己的进程崩溃,不会影响我们的APP进程:

当我们将投屏SDK放到服务端的时候,如果服务端因为异常重启了,我们需要做一些容错处理让客户端可以不受服务端异常的影响继续使用投屏服务。一般情况下,假如我们客户端因为异常重启了,是可以继续正常使用服务端的投屏服务的,因为我们的服务端一直都活着。但是假如服务端因为异常重启了,我们的客户端是不知道的,因为C-S架构就是客户端请求,服务端响应。

这种情况下我们的容错方案就是:当服务端发生异常重启的时候,通过广播的方式,像所有连接它的客户端发出自己重启的广播,让所有的客户端重新连接一下它,然后就可以正常使用投屏服务了。是不是很简单,原理如上图所示。


总结

以上就是今天要讲的内容,本文介绍了服务的生命周期,使用方法以及注意事项,并且结合实际的应用场景解析服务。包括服务的创建和停止,容错处理。


针对Android 底层Framework 这块,收集了大量的相关知识点进行了整理,形成了一下《Android Framework学习手册》。

每个知识点都有左侧导航书签页,看的时候十分方便,由于内容较多,这里就截取一部分图吧。需要的读者朋友们可以 通过此处 进行参考:https://0a.fit/acnLL

《Android Framework学习手册》:

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/141597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目经理跨部门沟通,如何避免踢皮球?

软件项目干系人越多&#xff0c;沟通成本也就越高&#xff0c;非常容易出现相互提皮球的现象。那么如何高效沟通&#xff1f; 1、划分干系人 不同干系人不同策略 软件项目涉及干系人众多&#xff0c;项目不经理不可能对每一个人进行详细沟通&#xff0c;那么我们需要将干系人的…

如何把优化Docker镜像大小

什么是Docker Docker是一个开源的虚拟化平台&#xff0c;可以让开发人员将应用程序和依赖项打包在轻量级容器中&#xff0c;然后可以轻松地在任何环境中运行。这样&#xff0c;开发人员可以将容器作为独立的可移植单元在不同的环境中部署和运行应用程序&#xff0c;而不用担心环…

系统学习ElasticSearch

1.1 、ElasticSearch&#xff08;简称ES&#xff09; Elasticsearch是用Java开发并且是当前最流行的开源的企业级搜索引擎。 能够达到实时搜索&#xff0c;稳定&#xff0c;可靠&#xff0c;快速&#xff0c;安装使用方便。 客户端支持Java、.NET&#xff08;C#&#xff09;、…

SAP FICO财务月结-外币评估

月末操作-外币评估 —文章整理自高林旭老师的《由浅入深学习SAP财务》一书&#xff0c;SAP相关从业人员值得一读。 企业的外币业务在记账的时候一般都是使用期初的汇率或者即时汇率&#xff0c;但是在月末&#xff0c;需要按照月末汇率对外币的余额或者未清项进行重估&#xf…

c语言入门-3-打印复杂类型

打印复杂类型前言上代码字符整形浮点型打印超长小数向内存中申请空间局部变量&#xff0c;全局变量使用输入函数 scanf作用域生命周期深度解析1 c语言中数据类型2 这些字段类型的大小又是多少呢3 计算机的大小单位4 scanf 报错下一篇前言 语言本身的学习&#xff0c;有两点比较…

【前端】Vue项目:旅游App-(7)city:搜索框search和标签页Tabs

文章目录目标过程与代码搜索框初步自己实现取消功能样式修改标签页效果总代码修改的文件common.csscity.vuemain.js目标 过程与代码 搜索框 初步 在Vant文档中找到搜索框&#xff1a;Search搜索 按照文档要求引入&#xff08;如果以插件的形式安装vant就不用这样引入&#x…

c语言 动态内存分配 柔性数组

常见的动态内存错误 对null指针的解引用操作 int main()//错误1 因为没有判断 {int* p (int*)malloc(10000);int i 0;for (i 0; i < 10; i){*(p i) i;}return 0; }对动态开辟空间的越界访问 int main() {int* p malloc(10 * sizeof(int));if (p NULL){return 1;}…

5G NR标准: 第16章 初始接入

第16章 初始接入 在 NR 中&#xff0c;初始接入功能包括&#xff1a; • 设备在进入系统覆盖区域时最初找到小区的功能和过程。 • 处于空闲/非活动状态的设备访问网络的功能和程序&#xff0c;通常是请求建立连接&#xff0c;通常称为随机访问。 在相当大的程度上&#xff0…

ORA-27102:out of memory Linux-x86_64 Error: 12: Cannot allocate memory

一 问题描述 无法启动数据库&#xff0c;报错&#xff1a; 二 排查思路 1.确保sga_target,sga_max_size比可用物理内存小 2.检查kernel.shmmax&#xff0c;kernel.shmall是否设置得太小 一般出现ORA-27102&#xff1a;out of memory&#xff0c;是/etc/sysctl.conf种内核参…

Apache Tomcat 存在 JsonErrorReportValve 注入漏洞(CVE-2022-45143)

漏洞描述 Apache Zeppelin是一款基于 Web 可实现交互式数据分析的notebook产品。 在Apache Zeppelin 0.10.1及以前的版本中“Move folder to Trash”功能存在路径遍历漏洞&#xff0c;由于未对InterpreterSettingManager类remove方法中id参数进行正确校验&#xff0c;攻击者可…

看完这篇文章,我再也不用担心线上出现 CPU 性能问题了(上)

目录CPU 使用率平均负载进程上下文切换总结生产环境上出现 CPU 性能问题是非常典型的一类问题&#xff0c;往往这个时候就比较考验相关人员排查问题的能力我相信不少小伙伴在工作当中多多少少都会碰到 CPU 出现性能瓶颈 不知道小伙伴们有没有跟我一样的感受——当 CPU 出现性能…

小红书排行榜 | 粉丝增量500w+,探寻爆款种草内容密码

随着兔年来临&#xff0c;回首上个月小红书母婴动态&#xff0c;行业热度依旧高涨&#xff0c;越来越多的达人和新品牌都涌入了小红书&#xff0c;母婴品牌要如何巧用小红书数据分析工具抢占用户心智&#xff1f;增量500w&#xff0b;的母婴博主有何亮点之处&#xff1f;和我一…

【甘特图软件部件】上海道宁与​DlhSoft助力您的Windows与移动应用程序开发

DlhSoft支持 使用可自定义的甘特图 时间轴、项目调度、资源负载图表、 看板、PERT图表和网络图 为您的WPF、ASP .NET、JavaScript 或macOS和iOS应用程序 设计更智能的应用程序 DlhSoft提供了 真正强大的甘特图软件部件 Apple平台开发人员可以 轻松地将其集成到 他们自…

equals和 == 的区别

1 问题equals和 都可以对于值类型&#xff0c;然后比较代数值是否相等。那么equals和 的具体区别是什么&#xff1f;2 方法 是运算符&#xff0c;可以使用在基本数据类型变量和引用数据类型变量中&#xff0c;如果比较的是基本数据类型&#xff1a;比较的是两个变量保存额数据…

嵌入式实时操作系统的设计与开发(二)

加载应用程序与创建程序运行环境 将应用程序从Flash加载到RAM的实现代码是一定在启动代码中的。 计算机系统的运行其实是CPU到相应的内存地址去取回指令&#xff0c;然后译码并执行指令&#xff0c;再依次从下一个地址取指、执行&#xff0c;而程序就是指令与数据的集合。 程序…

JAVAWeb开发(基于分片的网络状态查询方法、装置及存储介质)

本文基于分片的网络状态查询方法、装置及存储介质已是申请的专利。本人为第一发明人&#xff0c;这里给出来是提供一种写专利的范本&#xff0c;仅供参考。专利申请号&#xff1a;CN202110346967.5正文部分&#xff1a;技术领域本申请涉及但不限于计算机网络数据传输一致性领域…

opencv--颜色物体追踪 图片的形态学处理函数

目录 一、主要函数介绍 1. cv2.erode() 2. cv2.dilate() 3. cv2.findContours() 4. cv2.circle() 5. cv2.line() 二、代码 这里首先确定是否安装imutils库&#xff0c;这个库能让调整大小或者翻转屏幕等基本任务更加容易实现。这一次主要应用的是对于图片的形态学处理函…

【Android春招】Android基础day1

一、填空题 1&#xff0e;Android是基于__ 的移动端开源操作系统。 Linux 2&#xff0e;Android系统是由__公司推出的。 谷歌 3&#xff0e;Android 11对应的API编号是__。 30 4&#xff0e;App除了在手机上运行&#xff0c;还能在电脑的__上运行。 模拟器&#xff08;AVD&…

测试之概念篇【需求、测试用例、Bug描述、产品的生命周期、开发模型、测试模型】

文章目录1. 什么是需求2. 测试用例是什么3. Bug 是描述4. 产品的生命周期5. 软件测试贯穿于软件的整个生命&#xff0c;如何贯穿&#xff1f;6. 开发模型&#xff08;瀑布模型、螺旋模型、增量模型和迭代模型、敏捷模型&#xff09;7. 测试模型&#xff08;V模型、W模型&#x…

【Java寒假打卡】Java基础-BigDecimal

【Java寒假打卡】Java基础-BigDecimal构造方法四则运算BigDecimal的特殊方法基本数据类型包装类自动装箱与自动拆箱Integer的类型转换将数字字符串进行拆分成整数数组构造方法 package com.hfut.edu.test1;import java.math.BigDecimal;public class test3 {public static void…