Android 9 이상에서는 플랫폼이 기기의 배터리 수명에 부정적인 영향을 미치는 앱 동작을 모니터링할 수 있습니다. 플랫폼은 설정 규칙을 사용하고 평가하여 규칙을 위반하는 앱을 사용자가 제한할 수 있게 해주는 UX 흐름을 제공합니다.
Android 8.0 및 이전에는 잠자기, 앱 대기, 백그라운드 제한, 백그라운드 위치 제한과 같은 기능을 통한 제한이 있었습니다. 하지만 일부 앱은 계속해서 동작 불량을 보였습니다. 이러한 동작 중 일부는 Android vitals에 설명되어 있습니다. Android 9에는 점차적으로 업데이트 가능한 설정 규칙에 따라 앱을 감지하고 제한할 수 있는 OS 인프라가 도입되었습니다.
백그라운드 제한
원하는 경우 사용자가 앱을 제한하거나 시스템에서 감지한 앱이 기기 상태에 부정적인 영향을 미칠 경우 이를 표시할 수 있습니다.
제한된 앱:
- 사용자가 계속해서 실행할 수 있습니다.
- 작업/알람을 실행하거나 백그라운드에서 네트워크를 사용할 수 없습니다.
- 포그라운드 서비스를 실행할 수 없습니다.
- 사용자에 의해 제한되지 않는 앱으로 변경될 수 있습니다.
기기 구현자는 앱에 추가 제한을 더하여 다음을 수행할 수 있습니다.
- 앱을 자체 재시작으로부터 제한합니다.
- 서비스가 귀속되는 것으로부터 제한합니다(위험성 높음).
백그라운드의 제한된 앱은 메모리, CPU, 배터리 같은 기기 리소스를 소비할 것으로 기대되지 않습니다. 백그라운드 제한 앱은 사용자가 앱을 자주 사용하지 않을 때 기기 상태에 영향을 미치면 안 됩니다. 하지만 사용자가 앱을 실행하면 같은 앱이 온전히 작동해야 합니다.
맞춤 구현 사용
기기 구현자는 계속해서 맞춤 메서드를 사용하여 앱에 제한을 적용할 수 있습니다.
앱 제한 통합
다음 섹션에는 기기에 대한 앱 제한 및 통합을 정의하는 방법이 개요로 나와 있습니다. Android 8.x 이하의 앱 제한 메서드를 사용하는 경우 다음 섹션에서 Android 9 이상에서의 변경사항을 자세히 검토하세요.
AppOpsManager 플래그 설정
앱이 제한되면 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 이상에서 앱 제한 동작을 테스트하려면 다음 명령어 중 하나를 사용합니다.
- 앱을 제한 지정:
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) 앱을 설치하지 않은 경우 플랫폼에서 앱이 비활성 상태임을 감지합니다. }특정 양의 시청 시간과 함께 특정 화면의 화면 시간을 전달합니다. (비간접 실행은 포그라운드 앱이 두 번째 앱에서 서비스에 액세스하면 발생함)
앱 대기 중 플랫폼은 플랫폼이 하루에 한 번 이상 네트워크에 액세스하지 못하도록 하므로 앱 동기화 및 기타 작업을 지연시킵니다.
다음과 같은 경우 플랫폼이 앱 대기 모드에서 앱을 종료합니다.
- 앱이 활성화됩니다.
- 기기가 연결되었고 충전 중인 경우
활성 애플리케이션은 앱 대기의 영향을 받지 않습니다. 애플리케이션은 다음과 같은 경우 활성입니다.
- 프로세스가 현재 포그라운드에 있는 경우(활동 또는 포그라운드 서비스로 있거나 다른 활동 또는 포그라운드 서비스에 사용되고 있음)(예: 알림 리스너, 접근성 서비스, 라이브 배경화면 등)
- 사용자가 잠금 화면이나 알림 목록 등에서 알림을 본 경우
- 사용자가 명시적으로 시작함
특정 시간 동안 위의 활동이 하나도 발생하지 않으면 애플리케이션은 비활성입니다.
앱 대기 테스트
다음과 같은 adb
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