캐시된 앱 고정기

Android 11 (API 수준 30) 이상은 캐시된 앱 고정기를 지원합니다. 이 기능은 캐시된 프로세스의 실행을 중지하고 캐시된 동안 작동하려고 할 수 있는 비정상 앱의 리소스 사용량을 줄입니다.

캐시된 앱 고정기는 앱을 RAM에 유지하면서 CPU에서 앱을 사용 중지합니다. Android에서 앱이 작업을 실행해서는 안 되지만 나중에 필요할 수 있다고 판단하면 앱 프로세스를 종료하는 대신 고정합니다. 이렇게 하면 앱이 다시 필요할 때 콜드 스타트가 방지됩니다.

Android는 프로세스를 고정된 cgroup으로 이전하여 캐시된 앱을 고정합니다. 이렇게 하면 활성 캐시 앱이 있을 때 활성 및 유휴 CPU 소비가 줄어듭니다. 시스템 구성 플래그나 개발자 옵션을 사용하여 앱 고정기를 사용 설정할 수 있습니다.

Android 14 (API 수준 34) 이상에서 캐시된 앱 고정기에는 다음과 같은 강력한 동작이 포함됩니다.

  • 캐시된 상태의 앱 프로세스는 캐시된 상태로 전환된 후 10초 후에 고정됩니다.
  • 시스템은 수명 주기 이벤트 중에 고정된 앱 프로세스를 즉시 고정 해제합니다. 이러한 이벤트에는 인텐트 수신, 작업 서비스 시작, 사용자가 활동 재개 등이 포함됩니다.

ActivityManagerService는 모든 앱 프로세스를 관리하고 앱 수명 주기 결정을 내립니다. CachedAppOptimizer는 앱 프로세스를 고정하는 역할을 합니다.

앱 프로세스가 고정되면 모든 스레드가 일시 중지되고 고정이 해제될 때까지 CPU 작업을 실행할 수 없습니다. 따라서 앱은 가비지 컬렉션 (GC)을 실행할 수 없고 메모리 트림 이벤트에 응답할 수 없습니다. 자세한 내용은 ComponentCallbacks2.onTrimMemory(int)를 참조하세요. 이를 수용하기 위해 Android 14부터 다음이 적용됩니다.

  • Activity 인스턴스가 표시되는 앱은 백그라운드로 이동하는 즉시 TRIM_MEMORY_UI_HIDDEN에 관한 알림을 받습니다. 포그라운드 서비스가 있는 앱과 같이 UI 없이 수명 주기에 남아 있는 앱은 TRIM_MEMORY_BACKGROUND를 수신할 수 있습니다. 앱이 이러한 이벤트를 수신할 수 있는 경우 앱이 고정될 것으로 예상되므로 다른 트림 이벤트는 전송되지 않습니다.
  • 캐시된 상태로 전환된 직후 시스템은 앱 런타임에 동결될 가능성에 대비하여 GC를 실행하도록 요청할 수 있습니다.
  • 앱 프로세스가 고정되면 더티 페이지를 지원 저장소에 쓰고 익명 페이지를 ZRAM으로 스왑하는 등 추가 메모리 압축 단계가 발생할 수 있습니다.
  • 특정 앱의 모든 프로세스가 고정되면 시스템은 앱에서 유지관리하는 활성 TCP 소켓을 종료합니다. 이렇게 하면 소켓의 서버 측에서 기기 모뎀을 절전 모드에서 해제하는 TCP 연결 유지 핑을 전송하지 않습니다.

캐시된 앱 프로세스는 프로세스 상태가 캐시됨에서 더 높은 중요도 상태로 올라가면 고정 해제됩니다. Android 14 이상에서 고정 해제 이벤트를 줄이기 위해 앱이 캐시된 상태에 있는 동안 시스템은 컨텍스트 등록 브로드캐스트를 대기열에 추가합니다. 컨텍스트 등록 브로드캐스트는 앱이 Context.registerReceiver를 호출하여 동적으로 등록하는 수신기입니다. 시스템은 앱이 고정 해제된 후에만 대기열에 추가된 브로드캐스트를 전송합니다. 반면 시스템은 매니페스트 선언 브로드캐스트를 대기열에 추가하지 않습니다. 매니페스트 선언 브로드캐스트는 <receiver> 요소를 사용하여 AndroidManifest.xml에 정적으로 선언된 수신기입니다. 시스템은 매니페스트 선언 브로드캐스트를 전송하기 위해 캐시된 앱을 즉시 고정 해제합니다.

시스템 상태 영향

캐시된 앱 프로세스가 MAX_CACHED_PROCESSES개를 초과하면 Android는 가장 최근에 사용되지 않은 캐시된 앱 프로세스를 종료합니다. Android 14 이상을 실행하는 지원되는 기기에서 MAX_CACHED_PROCESSES가 크게 증가하여 기기가 RAM에 훨씬 더 많은 캐시된 앱 프로세스를 유지할 수 있습니다.

RAM에 더 많은 앱을 캐시하면 콜드 스타트가 최대 30% 감소하며, 감소량은 총 기기 RAM에 따라 조정됩니다. 동시에 캐시된 앱의 CPU 소비가 최소화되어 배터리가 크게 절약됩니다.

냉동고 예외

특정 조건에서는 앱 프로세스가 캐시된 상태로 전환되지만 고정되지 않은 상태로 유지될 수 있습니다. 이러한 예외는 구현 세부정보이며 향후 Android 버전에서 변경될 수 있습니다.

  • 파일 잠금: 캐시된 프로세스가 캐시되지 않은 다른 프로세스를 차단하는 파일 잠금을 보유하는 경우 잠금을 보유한 프로세스는 고정되지 않습니다.
  • BIND_WAIVE_PRIORITY 바인딩: Context.BIND_WAIVE_PRIORITY를 사용하여 생성된 수신 바인딩이 있는 앱 프로세스는 캐시된 상태로 전환될 수 있지만 연결된 모든 클라이언트 프로세스도 캐시될 때까지 고정되지 않은 상태로 유지됩니다. 이 예외는 맞춤 탭을 사용하는 웹브라우저와 같은 다중 프로세스 앱을 지원합니다.

앱 고정기 구현

캐시된 앱 고정기는 커널 cgroup v2 고정기를 사용합니다. 호환되는 커널과 함께 제공되는 기기는 이를 사용 설정할 수 있습니다. 개발자 옵션 캐시된 앱의 실행 일시중지를 사용 설정하거나 기기 구성 플래그 activity_manager_native_boot use_freezer를 true로 설정합니다. 예를 들면 다음과 같습니다.

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

use_freezer 플래그를 false로 설정하거나 개발자 옵션을 사용 중지하면 프리저가 사용 중지됩니다. 예를 들면 다음과 같습니다.

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

소프트웨어 출시 또는 업데이트에서 기기 구성을 변경하여 이 설정을 전환할 수 있습니다.

예를 들어 테스트를 위해 값을 1024로 설정하는 등 MAX_CACHED_PROCESSES를 재정의하려면 다음을 실행합니다.

adb shell device_config put activity_manager max_cached_processes 1024
adb shell device_config set_sync_disabled_for_tests persistent

MAX_CACHED_PROCESSES 재정의를 되돌리려면 다음을 실행합니다.

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

Android 16 (API 수준 36) 이상에서 Android는 원격 프로세스가 고정되거나 고정 해제되는 시점을 관찰하는 IBinder.FrozenStateChangeCallback 및 IBinder.addFrozenStateChangeCallback과 같은 공식 공개 API를 제공합니다. 캐시될 수 있는 앱과 상호작용하는 구성요소는 이러한 API를 사용하여 원격 프로세스의 고정 상태를 추적할 수 있습니다.

기기 및 커널 요구사항

캐시된 앱 고정기에는 커널 cgroup v2 지원이 필요합니다. 또한 IBinder.FrozenStateChangeCallback를 사용한 고정 상태 변경 알림에는 커널 바인더 드라이버 지원이 필요하며 이는 Android 14 (API 수준 34) 이상부터 Android 공통 커널 (ACK) 및 일반 커널 이미지 (GKI)에서 표준입니다.

다음과 같이 표준 adb 명령어를 사용하여 기기가 이러한 기능을 지원하는지 확인할 수 있습니다.

  • 기기에서 냉장고가 사용 설정되어 있는지 확인합니다 (사용자 또는 디버그 빌드의 경우).

    adb shell device_config get activity_manager_native_boot use_freezer

    또는 시스템에서 프로세스가 적극적으로 고정되고 있는지 확인합니다.

    adb shell dumpsys activity | grep -A 20 "Apps frozen:"
  • cgroup v2 freezer 컨트롤러 지원 확인 (모든 기기):

    freezer이 사용 가능한 cgroup v2 컨트롤러에 나열되어 있는지 확인합니다.

    adb shell cat /sys/fs/cgroup/cgroup.controllers

    또는 루팅된 기기나 userdebug 빌드에서 cgroup v2 냉동기 노드가 하위 cgroup에 마운트되었는지 확인합니다.

    adb root && adb shell ls /sys/fs/cgroup/uid_0/cgroup.freeze

    이 파일이 있으면 커널이 cgroup v2 고정기를 지원합니다.

  • 고정 상태 변경 알림 지원 확인 (모든 기기):

    호환되는 일반 커널 이미지 (GKI) 바인더 드라이버를 사용하여 Android 14 이상을 실행하는 기기에서 IBinder.addFrozenStateChangeCallback는 콜백을 성공적으로 등록합니다. 기본 커널 바인더 드라이버가 고정 알림을 지원하지 않으면 메서드는 UnsupportedOperationException를 던집니다.

맞춤 기능 처리

앱 프로세스는 캐시된 동안 어떠한 작업도 하지 않을 것으로 예상되지만, 일부 앱에는 캐시된 동안 실행될 것으로 예상되는 프로세스가 지원하는 맞춤 기능이 있을 수 있습니다. 이러한 앱을 실행하는 기기에서 앱 냉장고가 사용 설정되면 캐시된 프로세스가 고정되어 맞춤 기능이 작동하지 않을 수 있습니다.

해결 방법으로 프로세스가 작업을 처리해야 하기 전에 프로세스 상태를 캐시되지 않음으로 변경할 수 있습니다. 이 변경사항을 통해 앱은 활성 상태를 유지할 수 있습니다. 활성 상태의 예로는 바인드된 포그라운드 서비스 또는 포그라운드 상태가 있습니다.

일반적인 오류 모드

앱 프로세스가 고정되면 부적절한 프로세스 간 통신 (IPC) 또는 작업 예약으로 인해 앱이 종료되거나 예기치 않은 동작이 발생할 수 있습니다.

고정된 프로세스에 대한 동기 바인더 트랜잭션

클라이언트 앱 프로세스가 고정된 서버 앱 프로세스에 동기 바인더 트랜잭션을 전송하면 시스템은 즉시 서버 앱 프로세스를 종료합니다. 이렇게 하면 클라이언트 스레드가 고정된 서버의 응답을 기다리는 동안 무기한 차단되지 않습니다. 그러면 클라이언트 스레드가 RemoteException를 수신하고 등록된 리스너가 트리거됩니다. 자세한 내용은 IBinder.linkToDeath를 참조하세요.

근본 원인: 이 오류는 일반적으로 클라이언트 앱의 버그로 인해 발생합니다. 클라이언트가 서비스에 바인드되면 서버 프로세스가 클라이언트에 바인드되고 클라이언트가 캐시된 상태로 전환되기 전에 캐시된 상태로 전환되지 않습니다. 자세한 내용은 Context.bindService를 참조하세요. 하지만 클라이언트가 Context.unbindService를 호출하면 서버 프로세스가 캐시되고 고정될 수 있습니다. 클라이언트가 바인딩 해제 후에도 캐시된 IBinder 참조를 계속 사용하면 고정된 프로세스와 통신할 위험이 있습니다.

이 문제를 방지하려면 클라이언트 앱이 Context.unbindService 호출 직후 IBinder 참조를 삭제해야 합니다.

클라이언트 프로세스에 대한 원격 콜백 관리

클라이언트 프로세스에 대한 수명이 긴 바인더 콜백을 유지하는 서비스와 시스템 구성요소는 클라이언트 고정 상태를 추적하여 동기 실패와 비동기 버퍼 오버플로를 방지할 수 있습니다.

  • 상태 변경 리스너 등록: 수신되는 클라이언트 바인더 토큰에서 IBinder.addFrozenStateChangeCallback를 사용하여 클라이언트 프로세스가 냉장고에 들어가거나 나갈 때 알림을 받습니다.
  • 고정된 동안 디스패치 일시중지: 클라이언트가 STATE_FROZEN에 들어가면 해당 클라이언트에 콜백 또는 상태 업데이트 디스패치를 일시중지합니다.
  • 고정 해제 시 재개 및 전송: 클라이언트가 STATE_UNFROZEN로 전환되면 콜백 디스패치를 재개하고 필요한 일괄 또는 통합 업데이트를 전송합니다.
  • RemoteCallbackList 사용: RemoteCallbackList를 사용하는 시스템 서비스는 고정된 호출자 정책을 구성하여 수동 추적 로직을 유지하지 않고 콜백 디스패치를 자동으로 일시중지하고 재개할 수 있습니다. 자세한 내용은 시스템 서비스의 바인더 프리저 권장사항을 참고하세요.

비동기 바인더 트랜잭션 버퍼 오버플로

서버 앱 프로세스가 고정된 동안 비동기 (oneway) 바인더 트랜잭션을 수신하면 트랜잭션이 프로세스별 버퍼에 버퍼링됩니다. 서버가 고정된 동안 비동기 트랜잭션을 너무 많이 수신하면 버퍼가 오버플로되고 시스템이 서버 앱 프로세스를 종료합니다.

이 버퍼 오버플로를 방지하려면 캐시되거나 고정될 수 있는 프로세스에 과도한 비동기 바인더 트랜잭션을 전송하지 마세요.

고정 해제 시 예약된 작업의 반복 실행

앱이 반복 작업을 실행하는 경우 프로세스가 고정되는 동안 일시중지됩니다. 자세한 내용은 ScheduledThreadPoolExecutor.scheduleAtFixedRate 또는 Timer.scheduleAtFixedRate을 참고하세요. 프로세스가 고정 해제되면 누적된 누락된 실행이 지연 없이 연속으로 빠르게 실행될 수 있습니다.

앱이 고정 해제될 때 실행이 급증하는 것을 방지하려면 백그라운드 작업에 scheduleAtFixedRate 대신 scheduleWithFixedDelay를 사용하세요. WorkManager을 사용할 수도 있습니다.

앱 고정기 테스트 및 문제 해결

앱 고정기가 의도대로 작동하는지 확인하거나 고정기 관련 문제를 해결하려면 다음 진단 도구와 명령어를 사용하세요.

활동 관리자 명령어

adb shell am 명령어를 사용하여 특정 프로세스의 고정 및 압축을 수동으로 제어할 수 있습니다.

  • 프로세스를 강제 동결합니다.

    adb shell am freeze <process>
  • 프로세스 강제 해제:

    adb shell am unfreeze <process>
  • 프로세스에서 전체 메모리 압축을 강제 적용합니다.

    adb shell am compact full <process>

Logcat 검사

프로세스가 고정기 내부 또는 외부로 이전될 때마다 고정 및 고정 해제된 항목을 보여주는 logcat을 확인합니다.

adb logcat | grep -i "\(freezing\|froze\)"

UnfreezeReason 프로토콜 버퍼 enum에서 열거된 값을 고정 해제 이유 로그 출력

Dumpsys 검사

dumpsys activity를 사용하여 고정된 프로세스 목록을 확인합니다.

adb shell dumpsys activity | grep -A 20 "Apps frozen:"

/sys/fs/cgroup/uid_0/cgroup.freeze 파일이 있는지 확인합니다.

ApplicationExitInfo

이전 프로세스 종료 이유를 쿼리하려면 ActivityManager.getHistoricalProcessExitReasons를 참고하세요. 동결된 동안 동기 바인더 트랜잭션을 수신하는 등 냉동기 관련 문제로 인해 앱 프로세스가 종료된 경우 종료 이유는 ApplicationExitInfo.REASON_FREEZER로 설정됩니다.

Perfetto 추적

고정기 관련 이벤트는 Perfetto 트레이스에서 system_server 프로세스 아래의 Freezer 트랙으로 내보내집니다.

  • Freeze 및 Unfreeze 슬라이스는 프로세스가 상태를 변경하는 시점을 나타냅니다.
  • updateAppFreezeStateLSP 이벤트는 시스템 서버가 프로세스 속성을 다시 검사하여 고정 또는 고정 해제 결정을 내리는 시점을 보여줍니다.

Perfetto UI에서 이러한 이벤트를 직접 검사하거나 PerfettoSQL을 사용하여 분석할 수 있습니다.

INCLUDE PERFETTO MODULE slices.with_context;
SELECT *
FROM process_slice
WHERE process_name = "system_server"
AND track_name = "Freezer"
AND (name LIKE "Freeze %" OR name LIKE "Unfreeze %");

PerfettoSQL 표준 라이브러리에서 냉동기 이벤트는 android_freezer_events 테이블에도 요약됩니다.