应用电耗管理

在 Android 9 及更高版本中,平台可以对应用进行监控,以了解应用是否存在对设备的电池续航时间造成负面影响的行为。平台会使用和评估设置规则以提供用户体验流程,让用户可以选择限制违反规则的应用。

在 Android 8.0 及更低版本中,系统通过一些功能(例如低电耗模式、应用待机模式、后台限制和后台位置限制)对这类应用进行了限制。不过,一些应用仍存在不良行为(Android Vitals 中对部分这类行为进行了说明)。Android 9 引入了一个操作系统基础架构,此基础架构可以根据设置规则(随时间不断更新)检测和限制应用。

后台限制

用户可以选择限制应用,或者系统可能会提示用户其检测到对设备运行状况有不利影响的应用。

受限应用:

  • 仍然可以由用户启动。
  • 无法在后台运行作业/闹铃或使用网络。
  • 无法运行前台服务。
  • 可以由用户更改为不受限应用。

设备实现者可以向应用添加额外限制以:

  • 限制应用自动重启。
  • 限制绑定服务(风险极高)。

在后台运行的受限应用不应该消耗任何设备资源,例如内存、CPU 和电量。如果用户未主动使用后台受限应用,则这些应用不应该影响设备的运行状况。不过,如果用户启动后台受限应用,则这类应用应全功能运行。

使用自定义实现

设备实现者可以继续使用他们的自定义方法对应用设置限制。

集成应用限制

下面的部分概述了如何针对您的设备定义和集成应用限制。如果您使用的是 Android 8.x 或更低版本中的应用限制方法,请仔细阅读下面的部分,了解 Android 9 中的变更。

设置 AppOpsManager 标记

限制应用时,应在 AppOpsManager 中设置适当的标记。下面是 packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryUtils.java 中的一个示例代码段:

   public void setForceAppStandby(int uid, String packageName,
            int mode) {
        final boolean isPreOApp = isPreOApp(packageName);
        if (isPreOApp) {
       // Control whether app could run in the background if it is pre O app
            mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName, mode);
        }
       // Control whether app could run jobs in the background
        mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode);
    }

确保 isBackgroundRestricted 返回 true

限制应用时,应确保 ActivityManager.isBackgroundRestricted() 返回 true

记录限制原因

限制应用时,应记录限制原因。下面是 packages/apps/Settings/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppAction.java 中日志记录的一个示例代码段:

mBatteryUtils.setForceAppStandby(mBatteryUtils.getPackageUid(packageName), packageName,AppOpsManager.MODE_IGNORED);
if (CollectionUtils.isEmpty(appInfo.anomalyTypes)) {
  // Only log context if there is no anomaly type
  mMetricsFeatureProvider.action(mContext,
    MetricsProto.MetricsEvent.ACTION_TIP_RESTRICT_APP, packageName,
    Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT,metricsKey));
            } else {
  // Log ALL the anomaly types
  for (int type : appInfo.anomalyTypes) {
    mMetricsFeatureProvider.action(mContext,
      MetricsProto.MetricsEvent.ACTION_TIP_RESTRICT_APP, packageName,
      Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, metricsKey),
      Pair.create(MetricsProto.MetricsEvent.FIELD_ANOMALY_TYPE, type));
  }

应将 type 替换成 AnomalyType 中的值。

设备实现者可以使用 src/com/android/settings/fuelgauge/batterytip/StatsManagerConfig.java 中定义的常量:

public @interface AnomalyType {
        // This represents an error condition in the anomaly detection.
        int NULL = -1;
         // The anomaly type does not match any other defined type.
        int UNKNOWN_REASON = 0;
         // The application held a partial (screen off) wake lock for a period of time that
         // exceeded the threshold with the screen off when not charging.
        int EXCESSIVE_WAKELOCK_ALL_SCREEN_OFF = 1;
         // The application exceeded the maximum number of wakeups while in the background
         // when not charging.
        int EXCESSIVE_WAKEUPS_IN_BACKGROUND = 2;
         // The application did unoptimized Bluetooth scans too frequently when not charging.
        int EXCESSIVE_UNOPTIMIZED_BLE_SCAN = 3;
         // The application ran in the background for a period of time that exceeded the
         // threshold.
        int EXCESSIVE_BACKGROUND_SERVICE = 4;
         // The application exceeded the maximum number of wifi scans when not charging.
        int EXCESSIVE_WIFI_SCAN = 5;
         // The application exceed the maximum number of flash writes
        int EXCESSIVE_FLASH_WRITES = 6;
         // The application used more than the maximum memory, while not spending any time
         // in the foreground.
        int EXCESSIVE_MEMORY_IN_BACKGROUND = 7;
         // The application exceeded the maximum percentage of frames with a render rate of
         // greater than 700ms.
        int EXCESSIVE_DAVEY_RATE = 8;
         // The application exceeded the maximum percentage of frames with a render rate
         // greater than 16ms.
        int EXCESSIVE_JANKY_FRAMES = 9;
         // The application exceeded the maximum cold start time - the app has not been
         // launched since last system start, died or was killed.
        int SLOW_COLD_START_TIME = 10;
         // The application exceeded the maximum hot start time - the app and activity are
         // already in memory.
        int SLOW_HOT_START_TIME = 11;
         // The application exceeded the maximum warm start time - the app was already in
         // memory but the activity wasn't created yet or was removed from memory.
        int SLOW_WARM_START_TIME = 12;
         // The application exceeded the maximum number of syncs while in the background.
        int EXCESSIVE_BACKGROUND_SYNCS = 13;
         // The application exceeded the maximum number of gps scans while in the background.
        int EXCESSIVE_GPS_SCANS_IN_BACKGROUND = 14;
         // The application scheduled more than the maximum number of jobs while not charging.
        int EXCESSIVE_JOB_SCHEDULING = 15;
         // The application exceeded the maximum amount of mobile network traffic while in
         // the background.
        int EXCESSIVE_MOBILE_NETWORK_IN_BACKGROUND = 16;
         // The application held the WiFi lock for more than the maximum amount of time while
         // not charging.
        int EXCESSIVE_WIFI_LOCK_TIME = 17;
         // The application scheduled a job that ran longer than the maximum amount of time.
        int JOB_TIMED_OUT = 18;
         // The application did an unoptimized Bluetooth scan that exceeded the maximum
         // time while in the background.
        int LONG_UNOPTIMIZED_BLE_SCAN = 19;
         // The application exceeded the maximum ANR rate while in the background.
        int BACKGROUND_ANR = 20;
         // The application exceeded the maximum crash rate while in the background.
        int BACKGROUND_CRASH_RATE = 21;
         // The application exceeded the maximum ANR-looping rate.
        int EXCESSIVE_ANR_LOOPING = 22;
         // The application exceeded the maximum ANR rate.
        int EXCESSIVE_ANRS = 23;
         // The application exceeded the maximum crash rate.
        int EXCESSIVE_CRASH_RATE = 24;
         // The application exceeded the maximum crash-looping rate.
        int EXCESSIVE_CRASH_LOOPING = 25;
         // The application crashed because no more file descriptors were available.
        int NUMBER_OF_OPEN_FILES = 26;
    }

如果用户或系统移除了应用的限制,则您必须记录移除限制的原因。下面是 packages/apps/Settings/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java 中日志记录的一个示例代码段:

   public void handlePositiveAction(int metricsKey) {
        final AppInfo appInfo = mUnRestrictAppTip.getUnrestrictAppInfo();
        // Clear force app standby, then app can run in the background
        mBatteryUtils.setForceAppStandby(appInfo.uid, appInfo.packageName,
                AppOpsManager.MODE_ALLOWED);
        mMetricsFeatureProvider.action(mContext,
                MetricsProto.MetricsEvent.ACTION_TIP_UNRESTRICT_APP, appInfo.packageName,
                Pair.create(MetricsProto.MetricsEvent.FIELD_CONTEXT, metricsKey));
    }

测试应用限制

要在 Android 9.x 或更高版本中测试应用限制的行为,请使用下列命令之一:

  • 对应用设置应用限制:
    appops set package-name RUN_ANY_IN_BACKGROUND ignore
  • 移除应用限制并恢复默认行为:
    appops set package-name RUN_ANY_IN_BACKGROUND allow
  • 让后台应用立即进入闲置状态:
    am make-uid-idle [--user user-id | all | current] package-name
  • 在短期内将软件包添加到 tempwhitelist 中:
    cmd deviceidle tempwhitelist [-u user] [-d duration] [package package-name]
  • 向用户白名单中添加软件包或从中移除软件包:
    cmd deviceidle whitelist [+/-]package-name
  • 查看 jobscheduler 和闹铃管理器的内部状态:
    dumpsys jobscheduler
    dumpsys alarm

应用待机模式

对于用户未主动使用的应用,应用待机模式会延迟其后台网络活动和作业,从而延长电池续航时间。

应用待机模式生命周期

平台检测到未活动的应用,并使其进入应用待机模式,直到用户开始主动与相应应用互动为止。

检测 应用待机模式期间 退出

当设备未充电用户在特定的时钟时间内以及特定的屏幕开启时间内未直接或间接地启动某个应用时,平台会检测到该应用处于未活动状态(当前台应用访问另一个应用中的服务时,便会间接启动该应用)。

平台会阻止应用访问网络(一天多次),从而延迟应用同步和其他作业。

当出现以下情况时,平台会使应用退出应用待机模式:

  • 应用变成活动状态。
  • 设备接通电源并充电。

处于活动状态的应用不受应用待机模式影响。如果应用具有以下特点,则表示其处于活动状态:

  • 有目前在前台运行的进程(作为活动或前台服务,或者正在被其他活动或前台服务使用),例如通知侦听器、无障碍服务、动态壁纸等等。
  • 有用户要查看的通知,例如锁定屏幕或通知栏中的通知。
  • 明确由用户启动。

如果某个应用在一段时间内未发生上述活动,则表示该应用处于非活动状态。

测试应用待机模式

您可以使用以下 adb 命令手动测试应用待机模式:

adb shell dumpsys battery unplug
adb shell am set-idle package-name true
adb shell am set-idle package-name false
adb shell am get-idle package-name