앱 전원 관리

Android 9 이상에서는 플랫폼이 기기의 배터리 수명에 부정적인 영향을 미치는 앱 동작을 모니터링할 수 있습니다. 플랫폼은 설정 규칙을 사용하고 평가하여 규칙을 위반하는 앱을 사용자가 제한할 수 있게 해주는 UX 흐름을 제공합니다.

Android 8.0 및 이전에는 잠자기, 앱 대기, 백그라운드 제한, 백그라운드 위치 제한과 같은 기능을 통한 제한이 있었습니다. 하지만 일부 앱은 계속해서 동작 불량을 보였습니다. 이러한 동작 중 일부는 Android vitals에 설명되어 있습니다. Android 9에는 점차적으로 업데이트 가능한 설정 규칙에 따라 앱을 감지하고 제한할 수 있는 OS 인프라가 도입되었습니다.

백그라운드 제한

원하는 경우 사용자가 앱을 제한하거나 시스템에서 감지한 앱이 기기 상태에 부정적인 영향을 미칠 경우 이를 표시할 수 있습니다.

제한된 앱:

  • 사용자가 계속해서 실행할 수 있습니다.
  • 작업/알람을 실행하거나 백그라운드에서 네트워크를 사용할 수 없습니다.
  • 포그라운드 서비스를 실행할 수 없습니다.
  • 사용자에 의해 제한되지 않는 앱으로 변경될 수 있습니다.

기기 구현자는 앱에 추가 제한을 더하여 다음을 수행할 수 있습니다.

  • 앱을 자체 재시작으로부터 제한합니다.
  • 서비스가 귀속되는 것으로부터 제한합니다(위험성 높음).

백그라운드의 제한된 앱은 메모리, CPU, 배터리 같은 기기 리소스를 소비할 것으로 기대되지 않습니다. 백그라운드 제한 앱은 사용자가 앱을 자주 사용하지 않을 때 기기 상태에 영향을 미치면 안 됩니다. 하지만 사용자가 앱을 실행하면 같은 앱이 온전히 작동해야 합니다.

맞춤 구현 사용

기기 구현자는 계속해서 맞춤 메서드를 사용하여 앱에 제한을 적용할 수 있습니다.

앱 제한 통합

다음 섹션에는 기기에 대한 앱 제한 및 통합을 정의하는 방법이 개요로 나와 있습니다. Android 8.x 이하의 앱 제한 메서드를 사용하는 경우 다음 섹션에서 Android 9 이상에서의 변경사항을 자세히 검토하세요.

AppOpsManager 플래그 설정

앱이 제한되면 AppOpsManagerAppOpsManager에 적절한 플래그를 설정합니다. 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));
  }

typeAnomalyType의 값으로 바꿉니다.

기기 구현자는 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 이상에서 앱 제한 동작을 테스트하려면 다음 명령어 중 하나를 사용합니다.

  • 앱을 제한 지정:
    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

앱 대기 모드

앱 대기는 사용자가 활발하게 사용하지 않는 앱의 백그라운드 네트워크 활동 및 작업을 지연시켜 배터리 수명을 연장합니다.

앱 대기 수명 주기

플랫폼은 비활성 애플리케이션을 감지한 후 사용자가 애플리케이션을 적극적으로 사용하기 시작할 때까지 앱 대기 모드로 전환합니다.

감지 단계 중에 기기가 충전 중이 아니며 또한 사용자가 앱을 직접 실행하지 않았거나 {101) 앱을 설치하지 않은 경우 플랫폼에서 앱이 비활성 상태임을 감지합니다. }특정 양의 시청 시간과 함께 특정 화면의 화면 시간을 전달합니다. (비간접 실행은 포그라운드 앱이 두 번째 앱에서 서비스에 액세스하면 발생함)

앱 대기 중 플랫폼은 플랫폼이 하루에 한 번 이상 네트워크에 액세스하지 못하도록 하므로 앱 동기화 및 기타 작업을 지연시킵니다.

다음과 같은 경우 플랫폼이 앱 대기 모드에서 앱을 종료합니다.

  • 앱이 활성화됩니다.
  • 기기가 연결되었고 충전 중인 경우

활성 애플리케이션은 앱 대기의 영향을 받지 않습니다. 애플리케이션은 다음과 같은 경우 활성입니다.

  • 프로세스가 현재 포그라운드에 있는 경우(활동 또는 포그라운드 서비스로 있거나 다른 활동 또는 포그라운드 서비스에 사용되고 있음)(예: 알림 리스너, 접근성 서비스, 라이브 배경화면 등)
  • 사용자가 잠금 화면이나 알림 목록 등에서 알림을 본 경우
  • 사용자가 명시적으로 시작함

특정 시간 동안 위의 활동이 하나도 발생하지 않으면 애플리케이션은 비활성입니다.

앱 대기 테스트

다음과 같은 adbadb 명령어를 사용하여 앱 대기를 수동으로 테스트할 수 있습니다.

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