android 8.0+后台Service限制

2023-05-16

后台Service限制

背景

每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响会尤为明显。 为了提升用户体验,Android 8.0(API 级别 26)对应用在后台运行时可以执行的操作施加了限制。
应用在两个方面受到限制:

  • 后台 Service 限制:处于空闲状态时,应用可以使用的后台 Service 存在限制。 这些限制不适用于前台 Service,因为前台 Service 更容易引起用户注意。
  • 广播限制:除了有限的例外情况,应用无法使用清单注册隐式广播。 它们仍然可以在运行时注册这些广播,并且可以使用清单注册专门针对它们的显式广播。

在 Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service。 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 如果应用在此时间限制内_未_调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR。

前台判断

如果满足以下任意条件,应用将被视为处于前台:

  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  • 具有前台 Service。
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service,那么该应用处于前台:
    • IME
    • 壁纸 Service
    • 通知侦听器
    • 语音或文本 Service

空闲状态

如果 UID 现在在后台(不在临时白名单上), 它之前是在前台(或在临时白名单上);那么1min后当前UID将会处于idle状态,见Uid是否处于白名单作用

查找最近不活动的应用程序,并在宽限期后将它们标记为空闲。 如果空闲,停止任何后台服务并通知听众。

  • idle处理逻辑是在system server的main线程中进行的
  • idle时间是在处于后台或不在临时白名单的1min后
  • 遍历active uid时,时间到了就执行doStopUidLocked
  • 时间未到计算更新为最近的一次idle时间并再次发送IDLE_UIDS_MSG消息
    @GuardedBy("mService")
    void idleUidsLocked() {
        ......
        final long nowElapsed = SystemClock.elapsedRealtime();
        final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
        ......
    	// 1min后执行消息
        for (int i = N - 1; i >= 0; i--) {
            final UidRecord uidRec = mActiveUids.valueAt(i);
            final long bgTime = uidRec.getLastBackgroundTime();
            // 距离设置后台时间超过1min且当前uid不是idle则设置idle
            if (bgTime > 0 && !uidRec.isIdle()) {
                if (bgTime <= maxBgTime) {
                    EventLogTags.writeAmUidIdle(uidRec.getUid());
                    synchronized (mProcLock) {
                        uidRec.setIdle(true);
                        uidRec.setSetIdle(true);
                    }
                    // 停止当前uid下的后台Services
                    mService.doStopUidLocked(uidRec.getUid(), uidRec);
                } .......
            }
        }

限制后台运行Service

处于前台时,应用可以自由创建和运行前台与后台 Service。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用 Service。 在该时间窗结束后,应用将被视为处于_空闲_状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法一样。

因为当前uid进入后台1min后处于idle状态,停止与此 uid 关联的所有服务;所以我们会经常在bugreport中看到类似如下log。

08-27 20:08:12.038 1588 5729 I am_uid_active: 10135
08-27 20:09:13.517 1588 2114 I am_uid_idle: 10135
08-27 20:09:13.517 1588 2114 I am_stop_idle_service: [10135,com.android.htmlviewer/com.android.settings.services.MemoryOptimizationService]

03-22 01:49:59.872 3463 3521 W ActivityManager: Stopping service due to app idle: u0a153 -1m7s689ms com.qiyi.video/com.iqiyi.im.service.PPMessageService

void stopInBackgroundLocked(int uid) {
    ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
    ArrayList<ServiceRecord> stopping = null;
    if (services != null) {
        for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
            ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
            if (service.appInfo.uid == uid && service.startRequested) {
                if (mAm.getAppStartModeLOSP(service.appInfo.uid, service.packageName,
                        service.appInfo.targetSdkVersion, -1, false, false, false)
                        service.mRecentCallingPackage)
                        != ActivityManager.APP_START_MODE_NORMAL) {
                    if (stopping == null) {
                        stopping = new ArrayList<>();
                    }
                    String compName = service.shortInstanceName;
                    EventLogTags.writeAmStopIdleService(service.appInfo.uid, compName);
                    StringBuilder sb = new StringBuilder(64);
                    sb.append("Stopping service due to app idle: ");
                    UserHandle.formatUid(sb, service.appInfo.uid);
                    sb.append(" ");
                    TimeUtils.formatDuration(service.createRealTime
                            - SystemClock.elapsedRealtime(), sb);
                    sb.append(" ");
                    sb.append(compName);
                    Slog.w(TAG, sb.toString());
                    stopping.add(service);
                	// 如果应用程序受到后台限制,还要确保取消任何通知
                    if (appRestrictedAnyInBackground(
                            service.appInfo.uid, service.packageName)) {
                        cancelForegroundNotificationLocked(service);
                    }
                }
            }
        }
        if (stopping != null) {
            final int size = stopping.size();
            for (int i = size - 1; i >= 0; i--) {
                ServiceRecord service = stopping.get(i);
                service.delayed = false;
                services.ensureNotStartingBackgroundLocked(service);
                stopServiceLocked(service, true);
            }
            if (size > 0) {
                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
            }
        }
    }
}

注意:stopInBackgroundLocked回调后,service不一定会被stop

    private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
            boolean hasConn) {

        if (isServiceNeededLocked(r, knowConn, hasConn)) {
            return;
        }

        // 如果有新拉起service的需求,本次不会stop该service
        if (mPendingServices.contains(r)) {
            return;
        }

        bringDownServiceLocked(r);
    }

限制后台启动Service

如果Service是间接启动的(例如从 PendingIntent),弄清楚是否正在后台状态下启动一个应用程序。
当Service所在App处于后台(uid为idle)时,会限制启动Service,限制分两种情况:

  1. App处于后台受限模式下,限制启动任何Service(包括FGS);

ActivityManager: Background start not allowed: service Intent { act=geofence_trigered cmp=com.xiaomi.smarthome/.scene.activity.GeoActionService (has extras) } to com.xiaomi.smarthome/.scene.activity.GeoActionService from pid=9233 uid=10270 pkg=com.xiaomi.smarthome startFg?=true

  1. App处于无限制模式下,仅限制启动后台Service;

无限制下android.app.AppOpsManager#OP_RUN_ANY_IN_BACKGROUND 为ALLOWED

09-13 10:30:40.633 1581 2603 W ActivityManager: Background start not allowed: service Intent { cmp=com.android.deskclock/.addition.resource.ResourceLoadService (has extras) } to com.android.deskclock/.addition.resource.ResourceLoadService from pid=14625 uid=10216 pkg=com.android.deskclock startFg?=false

// 当前uid不处于空闲状态
final boolean bgLaunch = !mAm.isUidActiveLOSP(r.appInfo.uid);
boolean forcedStandby = false;
// 如果应用程序有严格的后台限制,我们将任何 bg 服务启动类似于旧版应用程序强制限制情况,
// 无论其目标 SDK 版本如何。
if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
    if (DEBUG_FOREGROUND_SERVICE) {
        Slog.d(TAG, "Forcing bg-only service start only for " + r.shortInstanceName
                + " : bgLaunch=" + bgLaunch + " callerFg=" + callerFg);
    }
    forcedStandby = true;
}
    	.......
if (forcedStandby || (!r.startRequested && !fgRequired)) {
    // 在继续之前——如果这个应用程序不允许在后台启动服务,那么在这一点上我们不会让它运行。    
    final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
            r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
    if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
        Slog.w(TAG, "Background start not allowed: service "
                + service + " to " + r.shortInstanceName
                + " from pid=" + callingPid + " uid=" + callingUid
                + " pkg=" + callingPackage + " startFg?=" + fgRequired);

限制豁免

豁免运行后台Service

避免当前App进入空闲状态,可从以下两个方向:

  • 避免当前App进入后台
  • 让当前app uid处于临时白名单中 (当前Uid是否处于白名单 )

mDeviceIdleTempAllowlist或mPendingTempAllowlist名单中的uid的会设置当前UidRecord的mCurAllowList为true。

豁免后台启动Service

当前app在前台

当前app uid处于临时白名单中

@GuardedBy(anyOf = {"this", "mProcLock"})
// alwaysRestrict 为false,disabledOnly 为false
int getAppStartModeLOSP(int uid, String packageName, int packageTargetSdk,
        int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby)
    // 当前app在前台
    if (mInternal.isPendingTopUid(uid)) {
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
	// 避免当前Uid进入空闲状态
    if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.isIdle()) {
        ......
        } else {
            ......
            // alwaysRestrict为false,走的appServicesRestrictedInBackgroundLOSP
            final int startMode = (alwaysRestrict)
                    ? appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk)
                    : appServicesRestrictedInBackgroundLOSP(uid, packageName,
                            packageTargetSdk);
            // android o 以下,暂不考虑了
            if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
            	// 这是一个旧应用程序,已被迫进入“尽可能兼容”的背景检查模式。 
                // 为了增加兼容性,我们将允许其他前台应用启动其服务。
                if (callingPid >= 0) {
                    ProcessRecord proc;
                    synchronized (mPidsSelfLocked) {
                        proc = mPidsSelfLocked.get(callingPid);
                    }
                    if (proc != null && !ActivityManager.isProcStateBackground(
                            proc.mState.getCurProcState())) {
                        // 启动的caller在前台,所以我们将允许它通过。
                        return ActivityManager.APP_START_MODE_NORMAL;
                    }
                }
            }
        	return startMode;
        }
    }
    return ActivityManager.APP_START_MODE_NORMAL;
}

服务启动适用于具有后台运行豁免的应用程序,但某些其他后台操作则不可用。 如果我们正在检查服务启动策略,请允许那些调用者不受限制地继续。

当前App是persistent 应用

当前UID是蓝牙UID

@GuardedBy(anyOf = {"this", "mProcLock"})
int appServicesRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
    // Persistent app?
    if (mPackageManagerInt.isPackagePersistent(packageName)) {
        return ActivityManager.APP_START_MODE_NORMAL;
    }

    // Non-persistent but background whitelisted? bluetooth uid
    if (uidOnBackgroundAllowlistLOSP(uid)) {
        return ActivityManager.APP_START_MODE_NORMAL;
    }

    // UID 是否在系统、用户或临时休眠许可名单中
    if (isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ false)) {
        return ActivityManager.APP_START_MODE_NORMAL;
    }
    // None of the service-policy criteria apply, so we apply the common criteria
    return appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk);
}

当前UID在系统、用户或临时休眠许可名单中

在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动 Service,并且其后台 Service 也可以运行。 处理对用户可见的任务时,应用将被置于白名单中,例如:

  • 处理一条高优先级 Firebase 云消息传递 (FCM)消息。 见 应用是推送App
  • 接收广播,处理短信/彩信消息。见应用在广播接收器中接收特殊广播 短信/彩信
  • 从通知执行 PendingIntent。 见应用收到系统的 PendingIntent 通知
  • 在 VPN 应用将自己提升为前台进程前开启 VpnService。

豁免的情况下在idle白名单(mDeviceIdleAllowlist)或临时白名单(mDeviceIdleTempAllowlist或mPendingTempAllowlist)中的app可不受 此限制。临时白名单添加情况见临时白名单

     // UID 是否在系统、用户或临时休眠许可名单中
    @GuardedBy(anyOf = {"this", "mProcLock"})
    boolean isOnDeviceIdleAllowlistLOSP(int uid, boolean allowExceptIdleToo) {
        final int appId = UserHandle.getAppId(uid);
    	// allowExceptIdleToo为false
        final int[] allowlist = allowExceptIdleToo
                ? mDeviceIdleExceptIdleAllowlist
                : mDeviceIdleAllowlist;

        return Arrays.binarySearch(allowlist, appId) >= 0
                // 由于高优先级消息而暂时允许逃避后台检查的一组应用程序 ID,短信/彩信
                || Arrays.binarySearch(mDeviceIdleTempAllowlist, appId) >= 0
                //  暂时绕过省电模式的临时白名单,通知等
                || mPendingTempAllowlist.get(uid) != null;
    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android 8.0+后台Service限制 的相关文章

  • 保存用户可以访问的 Flutter Android 应用程序中的文本文件

    我在 Flutter 中开发的 Android 应用程序的用户应该能够将一些数据保存 导出 到文本文件中 用户应该能够使用其他应用程序 包括文件管理器 在其 Android 设备上找到并访问此文件 我想 final directory aw
  • 带阴影的弯曲 Android 工具栏

    我需要为工具栏或卡片视图提供弯曲的底视图 我尝试过的 bg toolbar xml
  • Espresso - 检查使用按钮按下意图打开哪个活动?

    是否可以跟踪按下某个按钮后打开了哪个 Activity 我有一个测试 其中当单击 按下按钮时 it 向服务器发送请求 直到发送请求时 它打开一个活动 验证是否执行成功在测试中 我需要检查打开的 Activity 是什么 我的测试示例 检查
  • android - EditText 打字速度很慢

    我的 EditText 在打字时响应速度很慢 这种滞后现象足以让我找到解决方案 我做了一些研究 发现了一个 SO 线程输入文本时 EditText 滞后 https stackoverflow com questions 6173591 a
  • 如何改变android中menuItem的背景颜色?

    我正在以编程方式将菜单项添加到菜单中 我想在选择特定项目时添加背景颜色 如何为 menuItem 添加背景 您的回答将不胜感激 虽然其他答案提供了更改样式 这会影响all菜单项 据我了解 需要更改一个菜单项 我建议你使用android ac
  • 如何从另一个xml文件动态更新xml文件?

    我想从另一个 xml 文件更新 xml 文件 我使用了一个 xml 文件 如下所示 one xml
  • MI设备中即使应用程序被杀死,如何运行后台服务

    您好 我正在使用 alaram 管理器运行后台服务 它工作正常 但对于某些 mi 设备 后台服务无法工作 我使用了服务 但它无法工作 如何在 mi 中运行我的后台服务 MI UI有自己的安全选项 所以你需要的不仅仅是上面提到的粘性服务 你需
  • Flutter / FireStore:如何在 Flutter 中显示 Firestore 中的图像?

    我想将我在应用程序中使用的一些图像放入 Firestore 并从那里显示它们 而不是将它们作为资产捆绑在我的应用程序中 为了做到这一点 我想出了以下解决方案 对于我想要显示图像的项目 我创建了一个 Firebase 文档 其中有一个字段存储
  • 如何检测 Google Play 上是否有我的应用程序的更新? [复制]

    这个问题在这里已经有答案了 有没有办法以编程方式检查 Google Play 上我的应用程序是否有更新 以便通知用户 我知道 android google play 有自动通知 但我想使用我自己的通知 弹出消息来更新可用性 有点像 Vibe
  • invalidateOptionsMenu 在片段中不起作用

    显示或隐藏项目ActionBar根据文本中是否有文本EditText or not 所以 我做了以下事情 public class NounSearch extends android app Fragment EditText seach
  • 如何在 Android 上的 HttpPost 中发送 unicode 字符

    我试图在我的应用程序中允许多语言支持 这会发出 HTTP post 来上传新消息 我需要做什么才能支持日语和其他非拉丁语语言 我的代码目前看起来像这样 note the msg string is a JSON message by the
  • 当应用程序未运行时如何堆叠 Firebase Cloud Messaging 通知?

    我在用Firebase Cloud Messaging将推送通知从我的服务器发送到我的 Android 应用程序 当应用程序运行时 通知是stacked因为我将它们设置为我的一个组FirebaseMessagingService 这很好 但
  • 如何从SurfaceView绘制到Canvas?

    我正在尝试做简单的画家 问题是Android看起来有三个独立的Canvas并给我它来顺序绘制 我用以下方式制作了用户界面SurfaceView 把霍尔德从中拿走 Override protected void onCreate Bundle
  • Android:如何监控WiFi信号强度

    当信号强度发生变化时我会收到通知 我尝试创建以下方法并在 onCreate 中调用它 private void initializeWiFiListener Log i TAG executing initializeWiFiListene
  • Android开发:未定义方法

    大家好 我是 Android 和 Eclipse 的新手 我刚刚遵循了developer android com 上的教程 现在我在添加操作栏 http developer android com training basics actio
  • 如何在android中录制音频时暂停背景音乐

    我正在 Android 中开发一个音频记录应用程序 因此 如果设备音乐播放器中已播放任何背景音乐 则应在开始录制之前暂停该背景音乐 并且每当录制停止或暂停时 背景音乐都应恢复 播放录制的音频时也应该如此 有人可以帮我解决这个问题吗 提前致谢
  • 无法在 Android 模拟器中安装 apk

    我正在尝试通过 adb shell 在 ICS 模拟器中安装 apk 从一个站点下载 但出现以下错误 失败 INSTALL FAILED UID CHANGED 可能是什么问题 只需 rm r 有问题的数据目录即可 如果您在安装时遇到此错误
  • 如何在android sdk上使用PowerMock

    我想为我的 android 项目编写一些单元测试和仪器测试 然而 我遇到了一个困扰我一段时间的问题 我需要模拟静态方法并伪造返回值来测试项目 经过一些论坛的调查 唯一的方法是使用PowerMock来模拟静态方法 这是我的 gradle 的一
  • Android应用程序kill事件捕获

    我想在我的应用程序被终止时执行一些操作 可以使用哪种方法来实现此目的 我正在开发 Android 5 0 这个问题的关键在于 您必须了解您的申请是否可以收到任何 当您的应用程序在任何情况下被终止时的额外回调 下面的答案是由德文连线 http
  • 在 Android 中更新到 API 26 时,清单合并失败并出现多个错误

    我尝试使用 API 26 更新我的 gradle 安卓工作室2 3 3 但我在编译项目时遇到以下错误 这是我收到的错误的屏幕截图 应用级别build gradle Top level build file where you can add

随机推荐