Android的前台服务

概述

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

在 Android 8.0(API 级别 26)及更高版本中,系统对后台服务进行了限制,如果想要一直保持服务的运行就需要将服务设置为前台服务。前台服务与普通服务的区别在于它会有个通知在状态栏显示。当然有时可能也不仅仅是为了防止服务被回收才使用前台服务,有些项目的需要要求必须使用前台服务。如音乐播放、小说听书、天气等软件,这些都需要设置为前台服务,跟随进程的销毁而销毁。有些不仅仅是依赖应用进程,也可设置为系统白名单,保持一直运行的状态。

需要注意的是:应尽量限制应用使用前台服务。只有当应用执行的任务需供用户查看(即使该任务未直接与应用交互)时,您才应使用前台服务。因此,前台服务必须显示优先级为 PRIORITY_LOW 或更高的状态栏通知,这有助于确保用户知道应用正在执行的任务。如果某操作不是特别重要,您希望使用最低优先级通知,则可能不适合使用服务;此时,您可以考虑使用WorkManager、JobScheduler

每个运行服务的应用都会给系统带来额外负担,从而消耗系统资源。如果应用尝试使用低优先级通知隐藏其服务,则可能会降低用户正在主动交互的应用的性能。因此,如果某个应用尝试运行拥有最低优先级通知的服务,则系统会在抽屉式通知栏的底部调用出该应用的行为。

例如,应将服务播放音乐的音乐播放器设置为在前台运行,因为用户会明确意识到其操作。状态栏中的通知可能表示正在播放的歌曲,并且其允许用户通过启动 Activity 与音乐播放器进行交互。同样,如果应用允许用户追踪其运行,则需通过前台服务来追踪用户的位置。

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

有关前台服务的通知:如果您的应用正在运行“前台服务”(一种长时间在后台运行且用户可以察觉到的 Service,如媒体播放器),则需要发出通知。不能像关闭其他通知那样关闭这种通知。要移除此类通知,必须停止运行服务或者将其从“前台”状态中移除。如要从前台移除服务,请调用 stopForeground()。此方法接受布尔值参数,指示是否需同时移除状态栏通知。

应用场景

前台服务执行用户可以注意到的操作。前台服务显示一个状态栏通知,让用户知道你的应用程序正在前台执行任务,正在消耗系统资源。

应用程序使用前台服务的例子包括:

  • 一个在前台服务中播放音乐的音乐播放器应用程序。通知可能会显示当前正在播放的歌曲。
  • 一种健身应用程序,在获得用户的许可后,在前台服务中记录用户的跑步情况。该通知可能会显示用户在当前健身会话中走过的距离。

只有当你的应用需要执行用户可以注意到的任务时,才使用前台服务,即使他们没有直接与应用程序交互。如果操作的重要性足够低,你想使用最低优先级通知,那么创建一个后台任务。

本文介绍了使用前台服务所需的权限,以及如何启动前台服务并将其从后台移除。它还描述了如何将某些用例与前台服务类型相关联,以及当您从正在后台运行的应用程序启动前台服务时生效的访问限制。

前台服务的特点

默认情况下用户可取消前台服务

从Android 13 (API级别33)开始,默认情况下用户可以取消与前台服务相关的通知。此时只需要用户在通知上执行滑动手势即可。通常情况下,除非前台服务停止或从前台删除,否则通知不会被取消。

如果您希望通知不被用户划掉,则在使用notification.builder创建通知时将true传递给setOngoing() 方法。

立即显示通知的服务

如果前台服务有以下特征之一,系统在服务启动后立即显示相关的通知,即使在运行Android 12或更高版本的设备上:

  • 该服务与包含操作按钮的通知相关联。
  • 该服务的前台服务类型为mediaPlayback、mediaProjection或phoneCall。
  • 该服务提供了与电话呼叫、导航或媒体播放相关的用例,这些用例在通知的category属性中定义。
  • 在设置通知时,服务通过将FOREGROUND_SERVICE_IMMEDIATE传递给setForegroundServiceBehavior()来选择退出行为。
<manifest ...><uses-permission android:name="android.permission.FOREGROUND_SERVICE" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /><application ...><serviceandroid:name=".MyMediaPlaybackService"android:foregroundServiceType="mediaPlayback"android:exported="false"></service></application>
</manifest>

在Android 13 (API级别33)或更高版本上,如果用户拒绝通知权限,他们仍然会在任务管理器中看到与前台服务相关的通知,但在通知抽屉中看不到它们。

申请前台服务权限

针对Android 9 (API级别28)或更高版本并使用前台服务的应用程序需要请求FOREGROUND_SERVICE权限,如下面的代码片段所示。这是一个正常的权限,所以系统会自动将其授予请求应用程序。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...><uses-permission android:name="android.permission.FOREGROUND_SERVICE"/><application ...>...</application>
</manifest>

注意:如果目标API级别为28或更高的应用程序试图创建前台服务而没有请求FOREGROUND_SERVICE权限,系统将抛出一个SecurityException。

启动一个前台服务

  • 紧接上文的案例,这里首先添加前台服务的权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  • 在请求系统将某个服务作为前台服务运行之前,请先启动该服务本身: 在MainActivity中修改启动服务的方式如下:
//启动一个普通后台服务
//startService(startIntent);//启动一个前台服务 在api大于26才可使用startForegroundService此方法if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startForegroundService(startIntent);}else{startService(startIntent);}
  • 在服务内部,通常是在onStartCommand() 中,你可以请求你的服务在前台运行。为此,调用startForeground()。此方法接受两个参数:一个在状态栏中唯一标识通知的正整数和notification对象本身。接着修改服务内部的代码:

首先创建一个通知,在MyService的oncreate方法和onStartCommand()方法中做如下修改:

public class MyService extends Service {public MyService() {}private Looper serviceLooper;private ServiceHandler serviceHandler;private NotificationManager manager;// 从当前线程接收消息的处理程序private final class ServiceHandler extends Handler {public ServiceHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {try {Thread.sleep(5000);Log.i("Myservice","当前进程编号"+ Thread.currentThread().getName()+" ·····正在处理任务");} catch (InterruptedException e) {Thread.currentThread().interrupt();}//服务处理完成后,使用startId停止服务,这样我们就不会在停止处理另一个作业的服务
//            stopSelf(msg.arg1);}}@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.throw new UnsupportedOperationException("Not yet implemented");}@Overridepublic void onCreate() {manager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);Log.i("Myservice","onCreate");//启动运行该服务的线程。因为默认情况下服务通常运行在进程的主线程中,我们不希望阻塞主线程。所以创建了一个单独的线程,// 我们还将其设置为后台优先级,这样cpu密集型工作就不会破坏我们的UI。HandlerThread thread = new HandlerThread("ServiceStartArguments",Process.THREAD_PRIORITY_BACKGROUND);thread.start();// Get the HandlerThread's Looper and use it for our HandlerserviceLooper = thread.getLooper();serviceHandler = new ServiceHandler(serviceLooper);}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.i("Myservice","onStartCommand");// If the notification supports a direct reply action, use
// PendingIntent.FLAG_MUTABLE instead.Intent notificationIntent = new Intent(this, MainActivity.class);PendingIntent pendingIntent =PendingIntent.getActivity(this, 0, notificationIntent,PendingIntent.FLAG_IMMUTABLE);Notification notification =null;if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {//只在Android O之上需要渠道NotificationChannel notificationChannel = new NotificationChannel("channelid1","channelname",NotificationManager.IMPORTANCE_HIGH);//如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,通知才能正常弹出manager.createNotificationChannel(notificationChannel);notification = new Notification.Builder(this, "channelid1").setContentTitle(getText(R.string.notification_title)).setContentText(getText(R.string.notification_message)).setSmallIcon(R.drawable.ic_launcher_background).setContentIntent(pendingIntent).setTicker(getText(R.string.ticker_text)).build();}// Notification ID cannot be 0.startForeground(1, notification);Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();//对于每个开始请求,发送一个消息来开始一个作业,并传递启动ID,这样我们就知道当我们完成作业时我们正在停止哪个请求Message msg = serviceHandler.obtainMessage();msg.arg1 = startId;//在当前线程下执行服务的任务。serviceHandler.sendMessage(msg);// If we get killed, after returning from here, restartreturn START_STICKY;}@Overridepublic void onDestroy() {super.onDestroy();Log.i("Myservice","onDestroy");}
}

经过上述三步,我们来测试一下效果:

这会在前台开启一个通知,日志可见并没有进行销毁。服务列表中也可查看当前前台服务运行的时间:

这样一个前台任务就创建好了。

注意:状态栏通知必须使用优先级为PRIORITY_LOW或更高。如果你的应用程序试图使用一个优先级较低的通知,系统会在通知抽屉中添加一条消息,提醒用户应用程序使用了前台服务。

后台启动限制

针对Android 12 (API级别31)或更高版本的应用程序不能在后台运行时启动前台服务,除非有一些特殊情况。如果应用程序在后台运行时试图启动前台服务,而前台服务不满足前文中的情况,系统将抛出ForegroundServiceStartNotAllowedException异常。

注意:如果一个应用程序调用Context.startForegroundService()来启动另一个应用程序拥有的前台服务,这里限制只适用于两个应用程序都针对Android 12或更高版本。

检查应用程序是否执行后台启动

为了更好地验证当你的应用程序试图在后台运行时启动前台服务,可以在连接到测试设备或模拟器的开发机器上执行以下ADB命令,每当这种行为发生时就会出现通知:

adb shell device_config put activity_manager \ default_fgs_starts_restriction_notification_enabled true
免除后台启动限制

在以下情况下,即使你的应用程序在后台运行,应用程序也可以启动前台服务:

  • 你的应用从一个用户可见的状态转换,比如一个活动。
  • 当应用程序在一个现有任务的后台堆栈中有一个活动时,服务可以从后台启动一个活动。
  • 您的应用程序使用云消息接收高优先级消息。

注意:如果应用程序没有使用高优先级消息向用户显示时间敏感的内容,系统可以将高优先级消息降级为正常优先级。如果消息的优先级被降级,你的应用程序不能启动前台服务,并且试图启动前台服务会导致ForegroundServiceStartNotAllowedException异常。

因此,在尝试启动前台服务之前,建议检查RemoteMessage.getPriority() 的结果,并确认它是PRIORITY_HIGH。

  • 用户在与应用程序相关的UI元素上执行操作。例如,他们可能与气泡、通知、小部件或活动交互。
  • 应用程序调用精确的Alarm来完成用户请求的操作。
  • 应用程序是设备当前的输入法。
  • 应用程序接收到一个与地理围栏或活动识别转换相关的事件。
  • 在设备重启并在广播接收器中接收到ACTION_BOOT_COMPLETED、ACTION_LOCKED_BOOT_COMPLETED或action_my_package_replace意图动作后。
  • 应用程序在广播接收器中接收ACTION_TIMEZONE_CHANGED、ACTION_TIME_CHANGED或ACTION_LOCALE_CHANGED意图动作。
  • 具有特定系统角色或权限的应用程序,例如设备所有者和配置文件所有者。
  • ........

从前台删除一个服务

要从前台删除服务,请调用stopForeground()。此方法接受一个布尔值,该值指示是否也删除状态栏通知。如果在前台运行时停止服务,则会删除其通知。

    @Overridepublic void onDestroy() {super.onDestroy();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {stopForeground(Service.STOP_FOREGROUND_REMOVE);}Log.i("Myservice","onDestroy");}

此时通知也会被移除。

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

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

相关文章

一对一聊天

1.创建包 .服务界面 package yiduiy;import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.uti…

1.2 C语言简介

一、为什么要讲C语言 C语言是编程界的长青藤&#xff0c;可以查看语言排名发现&#xff0c;虽然现在语言很多&#xff0c;但是C语言一直占有一定地址 来源网站&#xff1a;https://www.tiobe.com/tiobe-index/ 在系统、嵌入式、底层驱动等领域存在一定的唯一性&#xff08;C语…

Python Opencv实践 - 简单的AR项目

这个简单的AR项目效果是&#xff0c;通过给定一张静态图片作为要视频中要替换的目标物品&#xff0c;当在视频中检测到图片中的物体时&#xff0c;通过单应矩阵做投影&#xff0c;将视频中的物体替换成一段视频播放。这个项目的所有素材来自自己的手机拍的视频。 静态图片&…

Java注册并监听全局快捷键

背景 之前在博客中分享了SWT托盘功能, 随之带来一个问题, 当程序最小化后无法快速唤醒, 按照平时使用软件的思路, 自然想到了注册全局快捷键, 本文介绍使用java方式实现全局快捷键的注册. 方案 通过google,搜到一个现成的库: jintellitype, 使用maven可以直接引用, 非常方便…

istio为什么能代替传统的SpringCloud 服务网格Istio概述

服务网格Istio概述 什么是服务网格(Service Mesh)&#xff1f;istio简介边车模式&#xff08;Sidecar&#xff09;为什么istio能代替传统SpringCloud&#xff1f;整体架构 首先奉上 istio官网 什么是服务网格(Service Mesh)&#xff1f; 服务网格详解 服务网格&#xff08;Se…

SpringMVC修炼之旅(2)基础入门

一、第一个程序 1.1环境配置 略 1.2代码实现 package com.itheima.controller;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;//定义…

【JaveSE】:认识异常

异常 一.异常的概念二.异常的体系结构1.基础结构2.异常分类 三.异常的处理1.防御式编程2.异常的抛出3.异常捕获4.try-catch捕获并处理5.finally 四.异常处理流程五.自定义异常 一.异常的概念 在Java中&#xff0c;将程序执行过程中发生的不正常行为称为异常。 算术异常 数组越界…

推荐一款手持式ECU刷写设备,国产软件配合使用

同星智能开发的一款手持式ECU刷写工具——TF1011&#xff0c;在TSMaster中导入诊断流程即可离线一键刷写。在 PC 端完成配置后&#xff0c;在设备可以在手持式离线场景下实现&#xff1a;基于 UDS 协议的诊断和基于 UDS 协议的 Flash Bootloader 程序更新功能。 产品简介—TF10…

ansible常用模块介绍

ansible运行模块的两种方式 Ad - Hoc 利用 ansible 命令直接完成管理 &#xff0c; 主要用于临时命令使用场景 ansible westos -m shell -a ls /mnt playbook ansible 脚本 &#xff0c; 主要用于大型项目场景 &#xff0c; 需要前期的规划 vim test.yml - hosts: all task…

【栈】车队

题目&#xff1a; /** 单调栈&#xff1a;存储到终点的时间&#xff0c;需要考虑浮点数* 思路&#xff1a;首先按照距离进行排序&#xff08;目的&#xff1a;如果离终点远的车辆用时比前面的车辆用时短&#xff0c;则是一个车队)* 排序后计算每一辆车辆的时间如果用…

web:[SUCTF 2019]CheckIn(一句话木马,.user.ini)

题目 页面显示 上传文件&#xff0c;随便上传一个文件试试 上传了一个文本&#xff0c;显示失败&#xff0c;不是图片 那就换图片马上传试试 不能包含<?,换一种写法&#xff0c;需要加上GIF89a&#xff0c;进行exif_imagetype绕过 上传成功 这里用.user.ini或者用post传参…

甘草书店:#8 2023年11月22日 星期三「“说一套做一套”的甘草与麦田」

最近与甘草书店的投资方和意向投资方沟通&#xff0c;听取了来自不同领域不同人群的观点。他们讲的都有道理&#xff0c;但他们说的都不是甘草。就像“麦田”成立之前&#xff0c;世间没有“麦田”一样&#xff1b;“甘草”出现之前&#xff0c;世间没有也没有“甘草”。 故事…

力扣116. 填充每个节点的下一个右侧节点指针(详细讲解root根节点的理解)

题目&#xff1a; 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针&#xff0c;让这个指针指向其下一个右…

D. In Love

贪心&#xff0c;维护最靠左的右端点以及最靠右的左端点 // Problem: D. In Love // Contest: Codeforces - Codeforces Round 905 (Div. 3) // URL: https://codeforces.com/contest/1883/problem/D // Memory Limit: 256 MB // Time Limit: 2000 ms // // Powered by CP Edi…

【从0配置JAVA项目相关环境1】jdk + VSCode运行java + mysql + Navicat + 数据库本地化 + 启动java项目

从0配置JAVA项目相关环境 写在最前面一、安装Java的jdk环境1. 下载jdk2. 配置jdk3. 配置环境变量 二、在vscode中配置java运行环境1. 下载VSCode2. 下载并运行「Java Extension Pack」 三、安装mysql1.官网下载MySQL2.开始安装如果没有跳过安装成功 3.配置MySQL Server4.环境变…

数据结构与算法编程题44

有向无权图邻接矩阵表示 //参考博客&#xff1a;https://blog.csdn.net/qq_54162207/article/details/117414707#include <iostream> using namespace std;#define Maxsize 100 #define VertexmMaxNum 20 #define ERROR 0 #define OK 1 typedef string VertexType; …

微信玩具小程序商城开发技巧

小程序已成为许多企业和个人开展业务的重要工具之一。如果你想在玩具行业中打造一个小程序商城&#xff0c;但又没有相关的编程经验&#xff0c;不用担心&#xff01;本文将通过乔拓云平台提供的简单操作步骤&#xff0c;分享给你玩具行业小程序平台搭建的教程。 首先&#xff…

DriveWorks——参数化设计非标定制利器

DriveWorks基本介绍 DriveWorks是一套被 SOLIDWORKS 认可为金牌合作伙伴产品的设计自动化软件。DriveWorks 可自动创建特定于订单的销售文档和 SOLIDWORKS 制造数据。减少重复性任务&#xff0c;消除错误&#xff0c;增加销售额&#xff0c;并在创纪录的时间内交付定制产品。 为…

python的异常处理批量执行网络设备的巡检命令

前言 在网络设备数量超过千台甚至上万台的大型企业网中&#xff0c;难免会遇到某些设备的管理IP地址不通&#xff0c;SSH连接失败的情况&#xff0c;设备数量越多&#xff0c;这种情况发生的概率越高。 这个时候如果你想用python批量配置所有的设备&#xff0c;就一定要注意这…

top K问题(C语言)

目录 前言 top K问题 模拟数据 建堆 验证&#xff08;简单了解即可&#xff09; 最终代码 调试部分 前言 在大小堆的实现&#xff08;C语言&#xff09;中我们讨论了堆的实际意义&#xff0c;在看了就会的堆排序&#xff08;C语言&#xff09;中我们完成了堆排序&#…