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 ...>
      <service
          android: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);
        }
        @Override
        public void handleMessage(Message msg) {
            try {
                Thread.sleep(5000);
                Log.i("Myservice","当前进程编号"+ Thread.currentThread().getName()+" ·····正在处理任务");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
          //服务处理完成后,使用startId停止服务,这样我们就不会在停止处理另一个作业的服务
//            stopSelf(msg.arg1);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public 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 Handler
        serviceLooper = thread.getLooper();
        serviceHandler = new ServiceHandler(serviceLooper);
    }

    @Override
    public 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, restart
        return START_STICKY;
    }

    @Override
    public 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()。此方法接受一个布尔值,该值指示是否也删除状态栏通知。如果在前台运行时停止服务,则会删除其通知。

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

此时通知也会被移除。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【使用高德开放平台API和js的Ajax代码实现定位并获得城市的天气情况】

使用高德开放平台API和js的Ajax代码实现定位并获得城市的天气情况 1、注册高德开放平台账号&#xff0c;免费获得Web服务API应用key 高德开放平台Web服务API 按照API点击申请KEY 登录后进入应用管理 新建应用&#xff08;随意起名&#xff09; 然后添加key提交即可 然后就可…

MySQL生僻字修改编码utf8mb4

1、查看你编码 SHOW VARIABLES WHERE Variable_name LIKE character_set_% OR Variable_name LIKE collation%;&#xff08;如果不是下图则继续&#xff09; 2、修改默认参数 /etc/my.cnf [mysqld] datadir/usr/local/mysql/data basedir/usr/local/mysql socket/usr/local/my…

zookeeper集群 +kafka集群

1.zookeeper kafka3.0之前依赖于zookeeper zookeeper是一个开源&#xff0c;分布式的架构&#xff0c;提供协调服务&#xff08;Apache项目&#xff09; 基于观察者模式涉及的分布式服务管理架构 存储和管理数据&#xff0c;分布式节点上的服务接受观察者的注册&#xff0c…

【Linux】冯诺依曼体系结构(硬件)、操作系统(软件)、系统调用和库函数 --- 概念篇

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和Linux还有算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 …

Vcenter 6.7 VCSA证书过期问题处理

1. 故障现象 2022年10月25日&#xff0c;登陆VC报错。 按照报错信息&#xff0c;结合官方文档&#xff0c;判断为STS证书过期导致。 vCenter Server Appliance (VCSA) 6.5.x, 6.7.x or vCenter Server 7.0.x 在/var/log/vmware/vpxd-svcs/vpxd-svcs.log看到类似报错: ERRO…

深度探索 Python Pyramid 框架

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Pyramid是一个灵活且强大的Python web框架&#xff0c;广泛用于构建各种规模的Web应用程序。本文将深度探索Pyramid框架&#xff0c;介绍其核心概念、应用场景以及一些高级特性。 安装与基础用法 首先&#xf…

Python if else条件语句详解

if 分支使用布尔表达式或布尔值作为分支条件来进行分支控制。Python 的 if 分支既可作为语句使用&#xff0c;也可作为表达式使用。下面先介绍 if 分支作为语句使用的情形。 if 语句可使用任意表达式作为分支条件来进行分支控制。Python 的 if 语句有如下三种形式&#xff1a;…

通过仿真理解信道化接收机分析过程

概要 信道化从子信道带宽划分上可分为临界抽取和非临界抽取两种&#xff0c;从各子信道中心频率布局上可分为偶型排列和奇型排列&#xff0c;从处理流程上可分为信道化分析与信道化综合过程。本文主要通过仿真来理解偶型排列/临界抽取/信道化分析过程。 基本原理 常规的数字…

maven生命周期回顾

目录 文章目录 **目录**两种最常用打包方法&#xff1a;生命周期&#xff1a; 两种最常用打包方法&#xff1a; 1.先 clean&#xff0c;然后 package2.先 clean&#xff0c;然后install 生命周期&#xff1a; 根据maven生命周期&#xff0c;当你执行mvn install时&#xff0c…

Python中字符串拼接及其应用场景

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 字符串拼接是Python中常见而重要的操作&#xff0c;它涉及到将多个字符串连接成一个字符串。本文将深入探讨Python中字符串拼接的不同方式、性能比较、以及在实际应用中的场景和最佳实践。 常见的字符串拼接方法…

【Pytorch使用自制数据集,Dataloader】

数据集结构 话不多说&#xff0c;直接上核心代码 myDataset.py from collections import Counter from torch.utils.data import Dataset import os from PIL import Imageclass MyDataset(Dataset):"""读取自制的数据集args:- image_dir: 图片的地址- labe…

腾讯云轻量应用服务器怎么安装BT宝塔面板?

腾讯云轻量应用服务器宝塔面板怎么用&#xff1f;轻量应用服务器如何安装宝塔面板&#xff1f;在镜像中选择宝塔Linux面板腾讯云专享版&#xff0c;在轻量服务器防火墙中开启8888端口号&#xff0c;然后远程连接到轻量服务器执行宝塔面板账号密码查询命令&#xff0c;最后登录和…

数据结构 | 查漏补缺之哈希表、最短路径、二叉树与森林的转换

哈希表是什么&#xff1f; 或者说 设图采用邻接表的存储结构&#xff0c;写对图的删除顶点和删除边的算法步骤 删除边 删除点 最短路径问题 判断一个有向图是否有环&#xff08;回路&#xff09;可以用下列哪些办法&#xff08;&#xff09; 存在拓扑序列&#xff0c;就有回路…

JVM之基本概念(一)

(1) 基本概念&#xff1a; JVM 是可运行 Java 代码的假想计算机 &#xff0c;包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收&#xff0c;堆 和 一个存储方法域。JVM 是运行在操作系统之上的&#xff0c;它与硬件没有直接的交互。 (2) 运行过程&#xff1a; 我们都…

关于PDE频率的问题

讨论&#xff1a;关于PDE频率的问题 关于PDE频率的问题问题复现讨论解决方法 关于PDE频率的问题 问题 在这篇文章下有人提出一个问题&#xff1a; 在使用物理信息神经网络对固定求解区域内进行物理场的预测&#xff0c;具体来说是在求解传热问题。在定义域为1或者大于0.4的正…

Python Opencv实践 - 简单的AR项目

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

手机电脑同步的时间管理工具

有不少上班族会发现自己有太多的工作要完成&#xff0c;并且在工作中往往会浪费很多时间在无关紧要的事情上&#xff0c;而不是专注于真正重要的任务&#xff0c;因此没有足够的时间来完成所有任务。在这种情况下&#xff0c;我们可以使用时间管理软件来帮助自己优先考虑重要的…

Flink Flink数据写入Kafka

一、环境准备 flink 1.14写入Kafka&#xff0c;首先在pom.xml文件中导入相关依赖 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><flink.version>1.14.6</flink.version><spark.version>2.4.3</spa…

鸿蒙一出,android开发处境再受重创

华为宣布其自研操作系统鸿蒙HarmonyOSNEXT开发者预览版将不再兼容安卓系统&#xff0c;这一消息引起了广泛关注和热议。这一决策标志着华为正式告别安卓&#xff0c;摆脱了外部的制约&#xff0c;开始着手打造一个全新的生态系统。 鸿蒙系统4发布一个月&#xff0c;截至目前&a…

Java LeetCode篇-深入了解二叉树经典解法(三种方式实现:获取二叉树的最大深度)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 对称二叉树 1.1 判断对称二叉树实现思路 1.2 代码实现&#xff1a;判断对称二叉树 2.0 二叉树的最大深度 2.1 使用递归实现获取二叉树的最大深度思路 2.2 代码实…