当前位置: 首页 > news >正文

startForegroundService与startService 使用浅析

一. 了解服务(Service)的概念

service是安卓开发中一个很重要组件,意为“服务”。与我们常见的activity不同,“服务”是默默的在背后进行工作的,通常,它用于在后台为我们执行一些耗时,或者需要长时间执行的一些操作的。通常的,我们使用 Intent来启动一个服务(需要在manifest文件中注册,也可以像注册activity一样,给它分配进程)。Service可以不需要UI就在后台运行,不用管开启它的页面是否被销毁,只要进程还在就可以在后台运行。

 - Service生命周期:


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

 二. 问题背景

产品需要在我们的业务启动之后,在状态栏展示一个“xx应用正在进行中”中的一个通知,同时,要求app退后台后也不能被结束,即需要保活。于是,我们将目光投向了service

....// 业务启动流程结束
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (guildInfo != null)) {getApp().applicationContext.startForegroundService(serviceIntent)
} else {getApp().applicationContext.startService(serviceIntent)}
// Service类中
public int onStartCommand(Intent intent, int flags, int startId) {....
mAudioNotification = createAudioNotification(); //创建通知栏展示内容
startForeground(NID, mAudioNotification); // 展示服务通知....
}

 于是我们的业务中出现了这样的代码,乍看之下 好像也没什么问题。可是外发之后,收到的crash反馈却只增不减。

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord

 这类堆栈引起了我们的注意。原来我们使用了新提供的 startForegroundService,而这个API比较特殊,它要求我们在调用之后,收到 onStartCommand 回调后 5s内必须调用 startForeground, 否则会有ANR,而如果在调用 startForeground 之前,调用了 stopService 或者 stopSelf ,则会直接抛出 crash 我们这里问题的原因就是,在startForeground之前,调用了 stopService。问题找到了,是业务自己调用导致的。

但,似乎这个API没有提供给我们一个可以合适取消service的时机呢。既然这个API 限制这么多,我们又为什么要选择它呢?它和之前常用的startService又有什么区别呢?

三. 源代码分析

@Overridepublic ComponentName startService(Intent service) {warnIfCallingFromSystemProcess();return startServiceCommon(service, false, mUser);}@Overridepublic ComponentName startForegroundService(Intent service) {warnIfCallingFromSystemProcess();return startServiceCommon(service, true, mUser);}private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user)

 这两个API最终的都是调用的 startServiceCommon,区别只在于 其中的参数 requireForeground 字段赋值不同。那么这个字段究竟做了哪些处理呢?

在Android8.0的行为变更说明中,我们看到,不在允许随意创建 后台服务,所以改为 调用 startForegroundService的形式。

 

  1.  不满足条件(如:O以上版本后台启动服务)调用startService会抛异常
private ComponentName startServiceCommon(Intent service, boolean requireForeground,UserHandle user) {try {validateServiceIntent(service);service.prepareToLeaveProcess(this);ComponentName cn = ActivityManager.getService().startService(mMainThread.getApplicationThread(), service,service.resolveTypeIfNeeded(getContentResolver()), requireForeground,getOpPackageName(), getAttributionTag(), user.getIdentifier());if (cn != null) {// 异常匹配if (cn.getPackageName().equals("!")) {throw new SecurityException("Not allowed to start service " + service+ " without permission " + cn.getClassName());} else if (cn.getPackageName().equals("!!")) {throw new SecurityException("Unable to start service " + service+ ": " + cn.getClassName());} else if (cn.getPackageName().equals("?")) {throw ServiceStartNotAllowedException.newInstance(requireForeground,"Not allowed to start service " + service + ": " + cn.getClassName());}}....return cn;} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

if (forcedStandby || (!r.startRequested && !fgRequired)) {// 调用startService,(!r.startRequested && !fgRequired) 条件为truefinal int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);if (allowed != ActivityManager.APP_START_MODE_NORMAL) {if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {return null;}if (forcedStandby) {if (fgRequired) {return null;}}UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(r.appInfo.uid);return new ComponentName("?", "app is in background uid " + uidRec);}}

// Unified app-op and target sdk check@GuardedBy(anyOf = {"this", "mProcLock"})int appRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {// 安卓8限制if (packageTargetSdk >= Build.VERSION_CODES.O) {if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");}return ActivityManager.APP_START_MODE_DELAYED_RIGID;}.....}

2.  提高服务优先级

我们知道,在安卓系统内存紧张时,前台应用是有高优先级的,不会被清理掉。所以,我们需要将“不可见”的服务 升级为前台服务,前台服务是被认为用于已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程。

所以 我们在启动service后,收到onStartCommand时,调用 startForeground,同时传入需要展示在前台的notification

3.  为什么调用startForgroundService后,再调用stop或者没有及时调用startForeground会crash/ANR呢?

startServiceCommon

-> AMS.startService

-> ActiveServices.startServiceLocked

-> startServiceInnerLocked

-> bringUpServiceLocked

->realStartServiceLocked

-> sendServiceArgsLocked

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,boolean oomAdjusted) throws TransactionTooLargeException {...ArrayList<ServiceStartArgs> args = new ArrayList<>();while (r.pendingStarts.size() > 0) {ServiceRecord.StartItem si = r.pendingStarts.remove(0);...if (r.fgRequired && !r.fgWaiting) {if (!r.isForeground) {<!--监听是否5S内startForeground-->scheduleServiceForegroundTransitionTimeoutLocked(r);} ...try {r.app.thread.scheduleServiceArgs(r, slice);}
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {return;}Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);msg.obj = r;r.fgWaiting = true;mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs);}
/*** How long the Context.startForegroundService() grace period is to get around to* calling Service.startForeground() before we generate ANR.*/volatile int mServiceStartForegroundTimeoutMs = DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS;

 修改 r.fgWaiting = true启动任务延迟TimeoutMs后发送 SERVICE_FOREGROUND_TIMEOUT_MSG

// handler处理 SERVICE_FOREGROUND_TIMEOUT_MSG
void serviceForegroundTimeout(ServiceRecord r) {ProcessRecord app;synchronized (mAm) {if (!r.fgRequired || !r.fgWaiting || r.destroying) {return;}app = r.app;if (app != null && app.isDebugging()) {// The app's being debugged; let it ridereturn;}if (DEBUG_BACKGROUND_CHECK) {Slog.i(TAG, "Service foreground-required timeout for " + r);}r.fgWaiting = false;stopServiceLocked(r, false);}if (app != null) {
// 就是我们之前遇到的异常final String annotation = "Context.startForegroundService() did not then call "+ "Service.startForeground(): " + r;Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_ANR_MSG);SomeArgs args = SomeArgs.obtain();args.arg1 = app;args.arg2 = annotation;msg.obj = args;mAm.mHandler.sendMessageDelayed(msg,mAm.mConstants.mServiceStartForegroundAnrDelayMs);}}

 如果在没有调用startForegroun前调用了stop,则会抛出 SERVICE_FOREGROUND_CRASH_MSG 的msg

private final void bringDownServiceLocked(ServiceRecord r) {...if (r.fgRequired) {r.fgRequired = false;r.fgWaiting = false;mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);if (r.app != null) {Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);msg.obj = r.app;msg.getData().putCharSequence(ActivityManagerService.SERVICE_RECORD_KEY, r.toString());mAm.mHandler.sendMessage(msg);}}

解决方案 -  startForegroundService后 必须按照要求调用 startForground 😊

四. 总结

笔者的业务场景下 其实不需要用这么严格的API,正常的在 应用处于前台时,启动前台服务 就按照常用的startService -> startForeground 调用即可。如果一定需要使用 startForegroundService 就要注意到以上的几个问题。

相关文章:

startForegroundService与startService 使用浅析

一. 了解服务&#xff08;Service&#xff09;的概念 service是安卓开发中一个很重要组件&#xff0c;意为“服务”。与我们常见的activity不同&#xff0c;“服务”是默默的在背后进行工作的&#xff0c;通常&#xff0c;它用于在后台为我们执行一些耗时&#xff0c;或者需要…...

django项目实战三(django+bootstrap实现增删改查)进阶分页

目录 一、分页 1、修改case_list.html页面 2、修改views.py的case_list方法&#xff08;分页未封装&#xff09; 二、分页封装 1、新建类Pagination 2、修改views.py的case_list方法 三、再优化&#xff0c;实现搜索分页qing情况 四、优化其他查询页面实现分页和查询 五…...

Python 之 Pandas DataFrame 数据类型的简介、创建的列操作

文章目录一、DataFrame 结构简介二、DataFrame 对象创建1. 使用普通列表创建2. 使用嵌套列表创建3 指定数值元素的数据类型为 float4. 字典嵌套列表创建5. 添加自定义的行标签6. 列表嵌套字典创建 DataFrame 对象7. Series 创建 DataFrame 对象三、DataFrame 列操作1. 选取数据…...

华为OD机试真题Python实现【5键键盘的输出】真题+解题思路+代码(20222023)

🔥系列专栏 华为OD机试(Python)真题目录汇总华为OD机试(JAVA)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出描述:示例1:示例2:解题思路代码实现运行结果:版权说明:题目...

IDEA全家桶式讲解 | IDEA安装、使用、断点调试、Git、插件 (第二篇)

目录 一&#xff1a;JavaEE阶段需要掌握的IDEA技能 1. 配置Tomcat 2. 配置Maven 3. IDEA连接数据库 4. 方便的特殊功能 5. 断点调试&#xff08;重点&#xff09; 6. IDEA中常用Git协同开发&#xff08;重点&#xff09; 7. 常用插件安装 一&#xff1a;JavaEE阶段需要…...

音视频基础之封装格式与音视频同步

封装格式的概念 封装格式(也叫容器&#xff09;就是将已经编码压缩好的视频流、音频流及字幕按照一定的方案放到一个文件中&#xff0c;便于播放软件播放。 一般来说&#xff0c;视频文件的后缀名就是它的封装格式。 封装的格式不一样&#xff0c;后缀名也就不一样。 比如&a…...

外籍在读博士|赴新西兰奥克兰大学双院士导师麾下联合培养

N同学来自阿拉伯国家&#xff0c;但本硕博都是在我国某省属高校就读&#xff0c;现为材料学专业一年级博士生。联合培养首选澳洲国家&#xff0c;包括澳大利亚和新西兰&#xff0c;其次是美国&#xff0c;希望在2023年初出国&#xff0c;以完成整个学年的学习计划。在我们的帮助…...

Learning C++ No.11【string类实现】

引言&#xff1a; 北京时间&#xff1a;2023/2/19/8:48&#xff0c;昨天更新了有关进程状态的博客&#xff0c;然后在休息的时候&#xff0c;打开了腾讯视屏&#xff0c;然后看到了了一个电视剧&#xff0c;导致上头&#xff0c;从晚上6点看到了10点&#xff0c;把我宝贵的博客…...

实力见“证”:Tapdata 技术创新与发展潜力广受认可

Tapdata 积极拥抱各种“不确定”&#xff0c;变中求新&#xff0c;只为呈现出更加好用的产品。 而 Tapdata 在专业领域不断深耕&#xff0c;持续打磨产品能力的同时&#xff0c;也收获了诸多来自外界的肯定&#xff0c;从用户到投资人&#xff0c;从生态伙伴到技术媒体以及官方…...

【C++修炼之路】18.map和set

每一个不曾起舞的日子都是对生命的辜负 map和setmap和set一.关联式容器二.set2.1 set的介绍2.2 set的使用1.set的模板参数列表2.set的构造3.set的迭代器4.set修改操作5.bound函数三.multiset四.map3.1 map的介绍3.2 map的使用1.map的模板参数说明2.pair的介绍3.map的[]重载五.m…...

ChatGPT原理与技术演进剖析

—— 要抓住一个风口&#xff0c;你得先了解这个风口的内核究竟是什么。本文作者&#xff1a;黄佳 &#xff08;著有《零基础学机器学习》《数据分析咖哥十话》&#xff09; ChatGPT相关文章已经铺天盖地&#xff0c;剖析&#xff08;现阶段或者只能说揣测&#xff09;其底层原…...

Retrofit+Hilt后端请求小项目1--项目介绍

简介 本项目根据 youtube 对应教程实现而来 将会对对应代码以及依赖&#xff08;如 Hilt、retrofit、coil&#xff09;进行详细的分析与解读&#xff0c;同时缕清项目结构安排 如文章有叙述不清晰的&#xff0c;请直接查看原教程&#xff1a;https://www.youtube.com/watch?…...

实际项目角度优化App性能

前言&#xff1a;前年替公司实现了一个在线检疫App&#xff0c;接下来一年时不时收到该App的需求功能迭代&#xff0c;部分线下问题跟进。随着新冠疫情防控政策放开&#xff0c;该项目也是下线了。 从技术角度来看&#xff0c;有自己的独特技术处理特点。下面我想记录一下该App…...

Structure|Alphafold2在肽结构预测任务上的基准实验

​题目&#xff1a;Benchmarking AlphaFold2 on peptide structureprediction 文献来源&#xff1a;2023, Structure 31, 1–9 代码&#xff1a;基准实验&#xff0c;比较了比较多的模型 1.背景介绍 由2-50个氨基酸构成的聚合物可以称为肽。但是关于肽和蛋白质之间的差异还是…...

Simple XML

简介 官网&#xff1a;https://simple.sourceforge.net/home.php Github&#xff1a;https://github.com/ngallagher/simplexml Simple 是用于 Java 的高性能 XML 序列化和配置框架。它的目标是提供一个 XML 框架&#xff0c;使 XML 配置和通信系统的快速开发成为可能。该框架…...

在代码质量和工作效率的矛盾间如何取舍?

这个问题的答案是&#xff0c;在很短的一段时期&#xff0c;编写高质量代码似乎会拖慢我们的进度。与按照头脑中首先闪现的念头编写代码相比&#xff0c;高质量的代码需要更多的思考和努力。但如果我们编写的不仅仅是运行一次就抛之脑后的小程序&#xff0c;而是更有实质性的软…...

rabbitMq安装(小短文)--未完成

rabbitMq是在activeMq的基础上创造的&#xff0c;有前者的功能&#xff0c;比前者强&#xff0c;属于后来居上。系统环境:windows10首先下载相关软件Erlang&#xff0c;因为他是这个语言写的。https://www.erlang.org/downloads然后安装&#xff0c;并且弄到环境变量里验证是否…...

Python调用MMDetection实现AI抠图去背景

这篇文章的内容是以 《使用MMDetection进行目标检测、实例和全景分割》 为基础&#xff0c;需要安装好 MMDetection 的运行环境&#xff0c;同时完成目标检测、实例分割和全景分割的功能实践&#xff0c;之后再看下面的内容。 想要实现AI抠图去背景的需求&#xff0c;我们需要…...

Java代码使用最小二乘法实现线性回归预测

最小二乘法简介最小二乘法是一种在误差估计、不确定度、系统辨识及预测、预报等数据处理诸多学科领域得到广泛应用的数学工具。它通过最小化误差&#xff08;真实目标对象与拟合目标对象的差&#xff09;的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数…...

linux-rockchip-音频相关

一、查看当前配置声卡状态 cat /proc/asound/cards二、查看当前声卡工作状态 声卡分两种通道&#xff0c;一种是Capture、一种是Playback。Capture是输入通道&#xff0c;Playback是输出通道。例如pcm0p属于声卡输出通道&#xff0c;pcm0c属于声卡输入通道。 ls /proc/asoun…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...