캐시된 앱 고정기

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

캐시된 앱 고정기는 앱을 CPU에서 분리된 상태로 유지하면서 RAM에 보관합니다. 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 keepalive 핑을 전송하지 못하도록 합니다.

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

시스템 상태 영향

캐시된 앱 프로세스가 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_freezertrue로 설정합니다. 예를 들면 다음과 같습니다.

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

앱 고정기는 공식 API를 노출하지 않고 참조 구현 클라이언트가 없으나 숨겨진 시스템 API setProcessFrozen을 사용하여 개별 프로세스를 고정하고 enableFreezer를 사용하여 고정을 전역적으로 사용 설정 또는 사용 중지합니다.

맞춤 기능 처리

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

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

일반적인 오류 모드

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

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

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

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

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

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

서버 앱 프로세스가 고정된 상태에서 비동기 (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라는 트랙으로 내보내집니다.

  • FreezeUnfreeze 슬라이스는 프로세스가 상태를 변경하는 시점을 나타냅니다.
  • 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 표에도 요약되어 있습니다.