启动和移除

启动

通常在Service的onStartCommand()方法中回调startForeground()方法,避免10s超时发生ANR。

Context context = getApplicationContext();
Intent intent = new Intent(...); // Build the intent for the service
context.startForegroundService(intent);

startForeground()方法中需要传入唯一标识通知的正整数id,跟通知本身

Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =PendingIntent.getActivity(this, 0, notificationIntent, 0);Notification notification =new 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();// Notification ID cannot be 0.
startForeground(ONGOING_NOTIFICATION_ID, notification);

移除

调用stopForeground()将Service从前台状态移除,Service本身仍然继续运行;int值参数表明是否移除与该前台服务有关的通知。
如果在前台服务运行时停止该服务,则与之相关的通知会被移除。

通知

android 12+通知延迟10s显示

https://developer.android.com/guide/components/foreground-services#notification-immediate
android 12+系统会等待10s才显示跟前台Service有关的通知(为短期运行的FGS提供优化体验),以下情况可以豁免(即立刻显示通知):

  1. The service is associated with a notification that includes action buttons.
  2. The service has a foregroundServiceType of mediaPlayback, mediaProjection, or phoneCall.
  3. The service provides a use case related to phone calls, navigation, or media playback, as defined in the notification’s category attribute.
  4. Service在配置通知时调用setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
 // 对应上面2    // Foreground service types that always get immediate notification display,// expressed in the same bitmask format that ServiceRecord.foregroundServiceType// uses.static final int FGS_IMMEDIATE_DISPLAY_MASK =ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK| ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL| ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE| ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
    public boolean shouldShowForegroundImmediately() {// Has the app demanded immediate display?// 对应上面4if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) {return true;}// Has the app demanded deferred display?if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) {return false;}// 对应上面3// We show these sorts of notifications immediately in the absence of// any explicit app declarationif (isMediaNotification() || hasMediaSession()|| CATEGORY_CALL.equals(category)|| CATEGORY_NAVIGATION.equals(category)// 对应上面1|| (actions != null && actions.length > 0)) {return true;}// No extenuating circumstances: defer visibilityreturn false;}

通知入队时确定是否延迟显示,延迟显示再更新下Service的通知

android 13+通知运行时权限

https://developer.android.com/about/versions/13/changes/notification-permission
Android 13 中引入了新的运行时权限,用于从应用发送非豁免通知:POST_NOTIFICATIONS。此更改有助于用户专注于最重要的通知。

权限

android 9+前台Service普通权限

https://developer.android.com/guide/components/foreground-services#request-foreground-service-permissions
未在功能请单声明权限的话,启动时会抛出安全异常:

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

android 13+通知运行时权限

https://developer.android.com/about/versions/13/changes/notification-permission

<manifest ...><uses-permission android:name="android.permission.POST_NOTIFICATIONS"/><application ...>...</application>
</manifest>

限制

android 11+ while-in-use权限

https://developer.android.com/guide/components/foreground-services#bg-access-restrictions
为了保护用户隐私,当前台Service在后台运行时,有以下限制:

  • 除非用户授予ACCESS_BACKGROUND_LOCATION权限,否则FGS不能访问location
  • FGS不能访问microphone 和 camera.
    // allow while-in-use permissions in foreground service or not.// while-in-use permissions in FGS started from background might be restricted.boolean mAllowWhileInUsePermissionInFgs;

不满足豁免条件下,系统会有如下log打印:

Foreground service started from background can not have location/camera/microphone access: service SERVICE_NAME

豁免:

  1. The service is started by a system component.
  2. The service is started by interacting with app widgets.
  3. The service is started by interacting with a notification.
  4. The service is started as a PendingIntent that is sent from a different, visible app.
  5. The service is started by an app that is a device policy controller that is running in device owner mode.
  6. The service is started by an app which provides the VoiceInteractionService.
  7. The service is started by an app that has the START_ACTIVITIES_FROM_BACKGROUND privileged permission.

https://www.yuque.com/amytan-l2rhk/qstog7/ryb5tq/

    /*** Should allow while-in-use permissions in FGS or not.* A typical BG started FGS is not allowed to have while-in-use permissions.* @param callingPackage caller app's package name.* @param callingUid caller app's uid.* @param targetService the service to start.* @return {@link ReasonCode}*/private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,int callingPid, int callingUid, @Nullable ServiceRecord targetService,boolean allowBackgroundActivityStarts) {int ret = REASON_DENIED;// 非后台,进程优先级较高final int uidState = mAm.getUidStateLocked(callingUid);if (ret == REASON_DENIED) {// Is the calling UID at PROCESS_STATE_TOP or above?if (uidState <= PROCESS_STATE_TOP) {ret = getReasonCodeFromProcState(uidState);}}// 有可见的Activity或windowif (ret == REASON_DENIED) {// Does the calling UID have any visible activity?final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);if (isCallingUidVisible) {ret = REASON_UID_VISIBLE;}}// 对应上面4, 仅在PendingIntentRecord中才有可能为trueif (ret == REASON_DENIED) {// Is the allow activity background start flag on?if (allowBackgroundActivityStarts) {ret = REASON_START_ACTIVITY_FLAG;}}if (ret == REASON_DENIED) {boolean isCallerSystem = false;final int callingAppId = UserHandle.getAppId(callingUid);switch (callingAppId) {case ROOT_UID:case SYSTEM_UID:case NFC_UID:case SHELL_UID:isCallerSystem = true;break;default:isCallerSystem = false;break;}// 对应上面1if (isCallerSystem) {ret = REASON_SYSTEM_UID;}}// 涉及到上面4if (ret == REASON_DENIED) {final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {if (pr.uid == callingUid) {if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {return REASON_ACTIVITY_STARTER;}}return null;});if (allowedType != null) {ret = allowedType;}}if (ret == REASON_DENIED) {if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) {return REASON_TEMP_ALLOWED_WHILE_IN_USE;}}if (ret == REASON_DENIED) {if (targetService != null && targetService.app != null) {ActiveInstrumentation instr = targetService.app.getActiveInstrumentation();if (instr != null && instr.mHasBackgroundActivityStartsPermission) {ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;}}}// 对应上面7if (ret == REASON_DENIED) {if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)== PERMISSION_GRANTED) {ret = REASON_BACKGROUND_ACTIVITY_PERMISSION;}}if (ret == REASON_DENIED) {final boolean isAllowedPackage =mAllowListWhileInUsePermissionInFgs.contains(callingPackage);if (isAllowedPackage) {ret = REASON_ALLOWLISTED_PACKAGE;}}// 对应上面5if (ret == REASON_DENIED) {// Is the calling UID a device owner app?final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid);if (isDeviceOwner) {ret = REASON_DEVICE_OWNER;}}return ret;}

来自不同的处于可见app发送的PendingingIntent启动的Service,有效期为10s
4.The service is started as a PendingIntent that is sent from a different, visible app

android 12+后台启动限制

https://developer.android.com/guide/components/foreground-services#background-start-restrictions
android 12+ app不能在后台启动前台Service,否则系统会抛出ForegroundServiceStartNotAllowedException. 但是如果一个app启动另一个app的FGS,这个限制只有在两个app的target sdk同时为android 12+时才生效。
检查应用是否有后台启动:

adb shell device_config put activity_manager \default_fgs_starts_restriction_notification_enabled true

如果App有后台启动前台Service的行为,建议App更换使用WorkManager来代替FGS。

                // Apps that are TOP or effectively similar may call startForeground() on// their services even if they are restricted from doing that while in bg.if (!ignoreForeground&& !appIsTopLocked(r.appInfo.uid)&& appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {Slog.w(TAG,"Service.startForeground() not allowed due to bg restriction: service "+ r.shortInstanceName);// Back off of any foreground expectations around this service, since we've// just turned down its fg request.updateServiceForegroundLocked(psr, false);ignoreForeground = true;}

豁免:

  1. Your app transitions from a user-visible state, such as an activity.
  2. Your app can start an activity from the background, except for the case where the app has an activity in the back stack of an existing task.
  3. Your app receives a high-priority message using Firebase Cloud Messaging.

Note: When your app is in the frequent bucket or a more restrictive bucket, your high-priority FCM messages might be downgraded to normal priority. If the message’s priority is downgraded, your app can’t start a foreground service. To check the priority of an FCM message that your app receives, call getPriority().

  1. The user performs an action on a UI element related to your app. For example, they might interact with a bubble, notification, widget, or activity.
  2. Your app invokes an exact alarm to complete an action that the user requests.
  3. Your app is the device’s current input method.
  4. Your app receives an event that’s related to geofencing or activity recognition transition.
  5. After the device reboots and receives the ACTION_BOOT_COMPLETED, ACTION_LOCKED_BOOT_COMPLETED, or ACTION_MY_PACKAGE_REPLACED intent action in a broadcast receiver.
  6. Your app receives the ACTION_TIMEZONE_CHANGED, ACTION_TIME_CHANGED, or ACTION_LOCALE_CHANGED intent action in a broadcast receiver.
  7. Your app receives a Bluetooth broadcast that requires the BLUETOOTH_CONNECT or BLUETOOTH_SCAN permissions.
  8. Apps with certain system roles or permission, such as device owners and profile owners.
  9. Your app uses the Companion Device Manager and declares the REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND permission or the REQUEST_COMPANION_RUN_IN_BACKGROUND permission. Whenever possible, use REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND.
  10. The user turns off battery optimizations for your app. You can help users find this option by sending them to your app’s App info page in system settings. To do so, invoke an intent that contains the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent action.
    private @ReasonCode int shouldAllowFgsStartForegroundLocked(@ReasonCode int allowWhileInUse,int callingPid, int callingUid, String callingPackage,@Nullable ServiceRecord targetService) {int ret = allowWhileInUse;// caller在前台if (ret == REASON_DENIED) {final int uidState = mAm.getUidStateLocked(callingUid);// Is the calling UID at PROCESS_STATE_TOP or above?if (uidState <= PROCESS_STATE_TOP) {ret = getReasonCodeFromProcState(uidState);}}if (ret == REASON_DENIED) {final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {if (app.uid == callingUid) {final ProcessStateRecord state = app.mState;//TOP之下 BFGS、FGS、BTOP的procState也可以if (state.isAllowedStartFgsState()) {return getReasonCodeFromProcState(state.getAllowStartFgsState());} else {final ActiveInstrumentation instr = app.getActiveInstrumentation();if (instr != null&& instr.mHasBackgroundForegroundServiceStartsPermission) {return REASON_INSTR_BACKGROUND_FGS_PERMISSION;}// caller在5s内可见,对应上面1final long lastInvisibleTime = app.mState.getLastInvisibleTime();if (lastInvisibleTime > 0 && lastInvisibleTime < Long.MAX_VALUE) {final long sinceLastInvisible = SystemClock.elapsedRealtime()- lastInvisibleTime;if (sinceLastInvisible < mAm.mConstants.mFgToBgFgsGraceDuration) {return REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;}}}}return null;});if (allowedType != null) {ret = allowedType;}}// 此权限不适用于三方Appif (ret == REASON_DENIED) {if (mAm.checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid,callingUid) == PERMISSION_GRANTED) {ret = REASON_BACKGROUND_FGS_PERMISSION;}}// 拥有REASON_SYSTEM_ALERT_WINDOW_PERMISSION 权限,跟2有关系if (ret == REASON_DENIED) {if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,callingPackage)) {ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;}}// Check for CDM apps with either REQUEST_COMPANION_RUN_IN_BACKGROUND or// REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND.// Note: When a CDM app has REQUEST_COMPANION_RUN_IN_BACKGROUND, the app is also put// in the user-allowlist. However, in this case, we want to use the reason code// REASON_COMPANION_DEVICE_MANAGER, so this check needs to be before the// isAllowlistedForFgsStartLOSP check.// 对应上面的12if (ret == REASON_DENIED) {final boolean isCompanionApp = mAm.mInternal.isAssociatedCompanionApp(UserHandle.getUserId(callingUid), callingUid);if (isCompanionApp) {if (isPermissionGranted(REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND,callingPid, callingUid)|| isPermissionGranted(REQUEST_COMPANION_RUN_IN_BACKGROUND,callingPid, callingUid)) {ret = REASON_COMPANION_DEVICE_MANAGER;}}}// if (ret == REASON_DENIED) {ActivityManagerService.FgsTempAllowListItem item =mAm.isAllowlistedForFgsStartLOSP(callingUid);if (item != null) {if (item == ActivityManagerService.FAKE_TEMP_ALLOW_LIST_ITEM) {ret = REASON_SYSTEM_ALLOW_LISTED;} else {ret = item.mReasonCode;}}}// 下面两个对应上面的11if (ret == REASON_DENIED) {if (UserManager.isDeviceInDemoMode(mAm.mContext)) {ret = REASON_DEVICE_DEMO_MODE;}}if (ret == REASON_DENIED) {// Is the calling UID a profile owner app?final boolean isProfileOwner = mAm.mInternal.isProfileOwner(callingUid);if (isProfileOwner) {ret = REASON_PROFILE_OWNER;}}if (ret == REASON_DENIED) {final AppOpsManager appOpsManager = mAm.getAppOpsManager();if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, callingUid,callingPackage) == AppOpsManager.MODE_ALLOWED) {ret = REASON_OP_ACTIVATE_VPN;} else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) {ret = REASON_OP_ACTIVATE_PLATFORM_VPN;}}// 对应上面的6if (ret == REASON_DENIED) {final String inputMethod =Settings.Secure.getStringForUser(mAm.mContext.getContentResolver(),Settings.Secure.DEFAULT_INPUT_METHOD,UserHandle.getUserId(callingUid));if (inputMethod != null) {final ComponentName cn = ComponentName.unflattenFromString(inputMethod);if (cn != null && cn.getPackageName().equals(callingPackage)) {ret = REASON_CURRENT_INPUT_METHOD;}}}// if (ret == REASON_DENIED) {if (mAm.mConstants.mFgsAllowOptOut&& targetService != null&& targetService.appInfo.hasRequestForegroundServiceExemption()) {ret = REASON_OPT_OUT_REQUESTED;}}return ret;}

声明前台Service类型

https://developer.android.com/guide/components/foreground-services#types

声明Service类型

android 10+新增location type
android 11+新增camera 和 microphone

<manifest>...<service ...android:foregroundServiceType="location|camera|microphone" />
</manifest>
Notification notification = ...;
Service.startForeground(notification,FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_CAMERA);

声明WorkerManager类型

https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running#foreground-service-type
如果app有长时间运行的worker请求location, camera, or microphone,按照上面链接指定worker的类型。

<serviceandroid:name="androidx.work.impl.foreground.SystemForegroundService"android:foregroundServiceType="location|microphone"tools:node="merge" />
@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String progress) {// Build a notification...Notification notification = ...;return new ForegroundInfo(NOTIFICATION_ID, notification,FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_MICROPHONE);
}

前台Service发展历史相关推荐

  1. service mesh(一)架构发展历史

    service mesh(一)架构发展历史 文章目录 service mesh(一)架构发展历史 数据面和控制面 service mesh架构演变 istio sidecar模式 流量治理 熔断 限流 ...

  2. CentOS以及Oracle数据库发展历史及各版本新功能介绍, 便于构造环境时有个对应关系...

    CentOS版本历史 版本 CentOS版本号有两个部分,一个主要版本和一个次要版本,主要和次要版本号分别对应于RHEL的主要版本与更新包,CentOS采取从RHEL的源代码包来构建.例如CentOS ...

  3. 4 年 46 个版本,一文读懂 Spring Cloud 发展历史

    作者 | 方剑 责编 | 唐小引 头图 | CSDN 下载自东方 IC 出品 | CSDN(ID:CSDNnews) Spring Cloud 自 2016 年 1 月发布第一个 Angel.SR5 ...

  4. [转]OpenGL基础技术讲座--发展历史

    OpenGL基础技术讲座--发展历史 第一讲 OpenGL 的发展历史 人们对三维图形技术的研究已经经历了一个很长的历程,而且涌现了许多三维图形开发工具,其中SGI公司推出的GL(Graphics L ...

  5. 惊了!原来 Web 发展历史是这样的

    点击上方蓝色字体,选择"标星公众号" 优质文章,第一时间送达 其实今天文章的主题是前后端分离接口设计,但是在谈前后端分离.接口设计之前,不得不谈谈 Web 的发展史.在 Web 元 ...

  6. .NET框架和发展历史介绍

    [.NET框架知识] .NET 框架是由微软开发的软件开发平台,其最主要的两个组成部分是公共语言运行时 (CLR) 和框架类库 (FCL),基础类库 (BCL)是框架类库的一个子集,.NET框架的主要 ...

  7. java的发展历史 现状

    java是什么 面向对象设计的语言 特别适用于internet应用程序开发 一次编译,到处运行 可以在各种操作系统上使用 windows linux都可以 发展历史 Java 的历史要追溯到 1991 ...

  8. 一文读懂 5G——发展历史、应用场景

    1.前言: 第五代移动通信技术(英语:5th generation mobile networks或5th generation wireless systems.5th-Generation,简称5 ...

  9. Android发展历史

    Android发展历史 第1页:机器人的开端 Android是一种以Linux为基础的开放源码操作系统,主要应用于便携设备.Android股份有限公司于2003年在美国加州成立,在2005年被Goog ...

最新文章

  1. 阶段1 语言基础+高级_1-3-Java语言高级_04-集合_05 List集合_1_List集合_介绍常用方法...
  2. Linux软件包命令
  3. 4 信道编码之循环码
  4. sizeof _countof _tcslen的比较
  5. 【风控场景】互利网上数字金融典型场景: 消费金融
  6. 驱动精灵2007_畅玩Steam的Win10游戏掌机发布! |莱莎2新战斗动画,肉腿致命驱动~...
  7. FastJson/spring boot: json输出
  8. MapReduce程序之数据排序
  9. 电动汽车真的省钱吗?
  10. mina的HTTP性能测试
  11. jsp分页带条件查询时,点击上一页或下一页,没有按条件查询数据。
  12. 如何成为一个合格的数据分析师
  13. 1口百兆光纤收发器工业导轨式发送机接收机1百兆光1百兆电工业以太网光纤收发器
  14. macos复制粘贴快捷键 快速_苹果MAC系统复制粘贴快捷键是什么?
  15. excel合并两列内容_一起来学习Excel表格两列合并一列的两种方法
  16. 大数据智能营销笔记本人工智能下引入新的交互方式
  17. 带你快速入门AXI4总线--AXI4-Full篇(2)----XILINX AXI4-Full接口IP源码仿真分析(Slave接口)
  18. SpringBoot整合grpc
  19. android仿IT之家、炫酷水波纹、Kotlin MVP项目、后台模拟点击、蜂巢迷宫小游戏等源码
  20. 计算机科学内容基础,计算机科学的基础pdf

热门文章

  1. 梦话西游显示服务器,梦幻西游:梦幻公测后上线的第一批服务器,现在只剩下五个了...
  2. mac下载EasyConnect
  3. t4b代码生成_T4+VSIX 打造Visual Studio 2010 中的Entity代码生成自定义工具
  4. 完美破解C# DLL
  5. 使用svn删除Xcode工程中的文件
  6. 怎么调用函数求ln x?
  7. 2021-11-04图形学 鼠标传点,连线,填充。(有人看就更新细节,其实创建类复制进去代码就能运行)
  8. C语言 使用zlib压缩和解压数据 API
  9. ISE软件的基本操作
  10. AI驱动的游戏资产制作流程【生成式AI】