캐시되고 고정된 앱 처리

바인더를 사용하여 프로세스 간에 통신할 때는 원격 프로세스가 캐시되거나 고정된 상태인 경우 특히 주의하세요. 캐시되거나 고정된 앱에 대한 호출은 앱이 비정상 종료되거나 불필요하게 리소스를 소비하도록 할 수 있습니다.

캐시되고 고정된 앱 상태

Android는 메모리 및 CPU와 같은 시스템 리소스를 관리하기 위해 앱을 다양한 상태로 유지합니다.

캐시된 상태

앱에 활동이나 서비스와 같은 사용자에게 표시되는 구성요소가 없으면 캐시됨 상태로 이동할 수 있습니다. 자세한 내용은 프로세스 및 앱 수명 주기를 참고하세요. 캐시된 앱은 사용자가 다시 전환할 경우를 대비해 메모리에 보관되지만 활성 상태로 작동하지는 않습니다.

bindService를 사용하는 등 하나의 앱 프로세스에서 다른 앱 프로세스로 바인딩할 때 서버 프로세스의 프로세스 상태는 클라이언트 프로세스만큼 중요하도록 상향됩니다 (Context#BIND_WAIVE_PRIORITY이 지정된 경우는 제외). 예를 들어 클라이언트가 캐시된 상태가 아니면 서버도 캐시된 상태가 아닙니다.

반대로 서버 프로세스의 상태는 클라이언트의 상태를 결정하지 않습니다. 따라서 서버에는 클라이언트와의 바인더 연결이 있을 수 있습니다(가장 일반적인 형태는 콜백). 원격 프로세스가 캐시된 상태에 있는 동안 서버는 캐시되지 않습니다.

상승된 프로세스에서 시작되고 앱에 전달되는 콜백이 있는 API를 설계할 때는 앱이 캐시된 상태로 전환될 때 콜백 디스패치를 일시중지하고 이 상태에서 종료될 때 다시 시작하는 것이 좋습니다. 이렇게 하면 캐시된 앱 프로세스에서 불필요한 작업을 방지할 수 있습니다.

앱이 캐시된 상태로 전환되거나 캐시된 상태에서 종료되는 시점을 추적하려면 ActivityManager.addOnUidImportanceListener를 사용하세요.

// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
    new UidImportanceListener() { ... },
    IMPORTANCE_CACHED);

고정 상태

시스템은 캐시된 앱을 고정하여 리소스를 절약할 수 있습니다. 앱이 고정되면 CPU 시간을 0으로 수신하고 작업을 실행할 수 없습니다. 자세한 내용은 캐시된 앱 냉동기를 참고하세요.

프로세스가 고정된 다른 원격 프로세스에 동기 (oneway 아님) 바인더 트랜잭션을 전송하면 시스템은 원격 프로세스를 종료합니다. 이렇게 하면 호출 프로세스의 호출 스레드가 원격 프로세스가 고정 해제되기를 기다리는 동안 무기한 중단되지 않으므로 호출 앱에서 스레드 기아 또는 교착 상태가 발생할 수 있습니다.

프로세스가 고정된 앱에 비동기 (oneway) 바인더 트랜잭션을 전송하면 (일반적으로 콜백을 알림, 일반적으로 oneway 메서드) 트랜잭션은 원격 프로세스가 고정 해제될 때까지 버퍼링됩니다. 버퍼가 오버플로되면 수신자 앱 프로세스가 비정상 종료될 수 있습니다. 또한 앱 프로세스가 고정 해제되고 트랜잭션을 처리할 때까지 버퍼링된 트랜잭션이 오래될 수 있습니다.

오래된 이벤트로 앱이 과부하되거나 버퍼가 오버플로되지 않도록 수신자 앱의 프로세스가 고정되어 있는 동안 콜백 디스패치를 일시중지해야 합니다.

앱이 고정되거나 고정 해제되는 시점을 추적하려면 IBinder.addFrozenStateChangeCallback를 사용하세요.

// The binder token of the remote process
IBinder binder = service.getBinder();

// Keep track of frozen state
AtomicBoolean remoteFrozen = new AtomicBoolean(false);

// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(
    myExecutor,
    new IBinder.FrozenStateChangeCallback() {
        @Override
        public void onFrozenStateChanged(boolean isFrozen) {
            remoteFrozen.set(isFrozen);
        }
    });

// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
    // dispatch callback to remote process
}

RemoteCallbackList 사용

RemoteCallbackList 클래스는 원격 프로세스가 등록하는 IInterface 콜백 목록을 관리하는 도우미입니다. 이 클래스는 바인더 종료 알림을 자동으로 처리하고 고정된 앱에 콜백을 처리하는 옵션을 제공합니다.

RemoteCallbackList를 빌드할 때 고정된 호출 수신자 정책을 지정할 수 있습니다.

  • FROZEN_CALLEE_POLICY_DROP: 고정된 앱에 대한 콜백은 자동으로 삭제됩니다. 앱이 캐시되는 동안 발생한 이벤트가 앱에 중요하지 않은 경우(예: 실시간 센서 이벤트) 이 정책을 사용하세요.
  • FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: 앱이 고정된 동안 여러 콜백이 브로드캐스트되면 앱이 고정 해제될 때 가장 최근 콜백만 대기열에 추가되고 전송됩니다. 이는 최신 상태 업데이트만 중요한 상태 기반 콜백(예: 현재 미디어 볼륨을 앱에 알리는 콜백)에 유용합니다.
  • FROZEN_CALLEE_POLICY_ENQUEUE_ALL: 앱이 고정되어 있는 동안 브로드캐스트되는 모든 콜백은 앱이 고정 해제될 때 대기열에 추가되고 전달됩니다. 이 정책은 콜백이 너무 많이 대기열에 추가되거나 오래된 이벤트가 누적될 수 있으므로 주의해야 합니다.

다음 예에서는 고정된 앱에 콜백을 삭제하는 RemoteCallbackList 인스턴스를 만들고 사용하는 방법을 보여줍니다.

RemoteCallbackList<IMyCallbackInterface> callbacks =
        new RemoteCallbackList.Builder<IMyCallbackInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
                .setExecutor(myExecutor)
                .build();

// Registering a callback:
callbacks.register(callback);

// Broadcasting to all registered callbacks:
callbacks.broadcast((callback) -> callback.onSomeEvent(eventData));

FROZEN_CALLEE_POLICY_DROP을 사용하는 경우 시스템은 콜백을 호스팅하는 프로세스가 고정되지 않은 경우에만 callback.onSomeEvent()을 호출합니다.

시스템 서비스 및 앱 상호작용

시스템 서비스는 바인더를 사용하여 다양한 앱과 상호작용하는 경우가 많습니다. 앱이 캐시되고 고정된 상태로 전환될 수 있으므로 시스템 서비스는 시스템 안정성과 성능을 유지하기 위해 이러한 상호작용을 적절하게 처리하는 데 특별히 주의해야 합니다.

시스템 서비스는 이미 다양한 이유로 앱 프로세스가 종료되는 상황을 처리해야 합니다. 여기에는 사용자를 대신하여 작업을 중지하고 비활성 프로세스에 콜백을 계속 전달하려고 시도하지 않는 것이 포함됩니다. 앱 고정 고려는 기존 모니터링 책임의 확장입니다.

시스템 서비스에서 앱 상태 추적

system_server에서 실행되거나 네이티브 데몬으로 실행되는 시스템 서비스는 앞에서 설명한 API를 사용하여 앱 프로세스의 중요도와 고정 상태를 추적할 수도 있습니다.

  • ActivityManager.addOnUidImportanceListener: 시스템 서비스는 리스너를 등록하여 UID 중요도 변경사항을 추적할 수 있습니다. 앱에서 바인더 호출이나 콜백을 수신할 때 서비스는 Binder.getCallingUid()를 사용하여 UID를 가져오고 리스너가 추적한 중요도 상태와 상관관계를 지정할 수 있습니다. 이를 통해 시스템 서비스는 호출 앱이 캐시된 상태인지 알 수 있습니다.

  • IBinder.addFrozenStateChangeCallback: 시스템 서비스가 앱에서 바인더 객체를 수신하는 경우 (예: 콜백 등록의 일부로) 해당 특정 IBinder 인스턴스에 FrozenStateChangeCallback을 등록해야 합니다. 이 메서드는 바인더를 호스팅하는 앱 프로세스가 고정되거나 고정 해제될 때 시스템 서비스에 직접 알립니다.

시스템 서비스 권장사항

앱과 상호작용할 수 있는 모든 시스템 서비스는 통신하는 앱 프로세스의 캐시되고 고정된 상태를 추적하는 것이 좋습니다. 이렇게 하지 않으면 다음과 같은 문제가 발생할 수 있습니다.

  • 리소스 소비: 캐시되고 사용자에게 표시되지 않는 앱의 작업을 실행하면 시스템 리소스가 낭비될 수 있습니다.
  • 앱 비정상 종료: 고정된 앱에 대한 동기 바인더 호출로 인해 앱이 비정상 종료됩니다. 고정된 앱에 대한 비동기 바인더 호출은 비동기 트랜잭션 버퍼가 오버플로되는 경우 비정상 종료로 이어집니다.
  • 예상치 못한 앱 동작: 고정 해제된 앱은 고정되어 있는 동안 전송된 버퍼링된 비동기 바인더 트랜잭션을 즉시 수신합니다. 앱은 냉동 상태로 무기한 유지될 수 있으므로 버퍼링된 트랜잭션이 매우 오래될 수 있습니다.

시스템 서비스는 RemoteCallbackList를 사용하여 원격 콜백을 관리하고 비활성 프로세스를 자동으로 처리하는 경우가 많습니다. 고정된 앱을 처리하려면 RemoteCallbackList 사용에 설명된 대로 고정된 호출 수신자 정책을 적용하여 기존 RemoteCallbackList 사용을 확장하세요.