메모리 사용량 최적화

메모리 최적화는 원활한 성능을 보장하고, 앱 비정상 종료를 방지하며, 시스템 안정성과 플랫폼 상태를 유지하는 데 매우 중요합니다. 메모리 사용량은 모든 앱에서 모니터링하고 최적화해야 하지만 TV용 콘텐츠 앱에는 휴대기기용 일반 Android 앱과 다른 특정 문제가 있습니다.

메모리 소비량이 많으면 다음과 같은 앱 및 시스템 동작 문제가 발생할 수 있습니다.

  • 앱 자체가 느려지거나 지연될 수 있으며 최악의 경우 종료될 수 있습니다.
  • 사용자에게 표시되는 시스템 서비스 (볼륨 제어, 사진 설정 대시보드, 음성 어시스턴트 등)가 매우 느려지거나 전혀 작동하지 않을 수 있습니다.
  • 로우 메모리 킬러 (LMK) 데몬 프로세스는 가장 필수적이지 않은 프로세스를 종료하여 높은 메모리 압력에 반응할 수 있습니다. 그러면 이러한 구성요소가 곧 다시 시작되어 추가 리소스 경합이 급증하여 포그라운드 앱에 직접적인 영향을 미칠 수 있습니다.
  • 런처로의 전환이 크게 지연되어 전환이 완료될 때까지 포그라운드 앱이 응답하지 않는 것처럼 보일 수 있습니다.
  • 시스템에서 직접 회수를 사용하기 시작할 수 있습니다. 메모리 할당을 기다리는 동안 스레드 실행이 일시적으로 일시중지됩니다. 이는 기본 스레드나 코덱 관련 스레드와 같은 모든 스레드에서 발생할 수 있으며 오디오 및 동영상 프레임 드롭과 UI 결함이 발생할 수 있습니다.

TV 기기의 메모리 고려사항

TV 기기는 일반적으로 휴대전화나 태블릿보다 메모리가 훨씬 적습니다. 예를 들어 TV에서 볼 수 있는 구성은 1GB RAM과 1080p 동영상 해상도입니다. 동시에 대부분의 TV 앱에는 유사한 기능이 있으므로 유사한 구현과 공통적인 문제가 있습니다. 이 두 가지 상황에서는 다른 기기 유형 및 앱에서는 볼 수 없는 문제가 발생합니다.

  • 미디어 TV 앱은 일반적으로 그리드 이미지 뷰전체 화면 배경 이미지로 구성되어 있어 짧은 시간 내에 많은 이미지를 메모리에 로드해야 합니다.
  • TV 앱은 동영상과 오디오를 재생하기 위해 특정 양의 메모리를 할당해야 하고 원활한 재생을 위해 상당한 미디어 버퍼가 필요한 멀티미디어 스트림을 재생합니다.
  • 추가 미디어 기능 (탐색, 에피소드 변경, 오디오 트랙 변경 등)은 제대로 구현되지 않으면 추가 메모리 압력을 받을 수 있습니다.

TV 기기 이해하기

이 가이드에서는 주로 RAM 부족 기기의 앱 메모리 사용량과 메모리 타겟에 중점을 둡니다.

TV 기기에서는 다음 특성을 고려하세요.

  • 기기 메모리: 기기에 설치된 랜덤 액세스 메모리 (RAM)의 양입니다.
  • 기기 UI 해상도: 기기에서 OS 및 애플리케이션 UI를 렌더링하는 데 사용하는 해상도입니다. 일반적으로 기기 동영상 해상도보다 낮습니다.
  • 동영상 해상도: 기기에서 동영상을 재생할 수 있는 최대 해상도입니다.

이로 인해 다양한 기기 유형과 기기에서 메모리를 사용하는 방법을 분류할 수 있습니다.

TV 기기 요약

기기 메모리 기기 동영상 해상도 기기 UI 해상도 isLowRAMDevice()
1GB 1080p 720p
1.5GB 2160p 1080p
1.5GB 이상 1080p 720p 또는 1080p 아니요*
2GB 이상 2160p 1080p 아니요*

RAM 부족 TV 기기

이러한 기기는 메모리 제한 상황에 있으며 ActivityManager.isLowRAMDevice()를 true로 보고합니다. RAM 부족 TV 기기에서 실행되는 애플리케이션은 추가 메모리 제어 조치를 구현해야 합니다.

다음과 같은 특성을 가진 기기는 이 카테고리에 속하는 것으로 간주됩니다.

  • 1GB 기기: 1GB RAM, 720p/HD (1280x720) UI 해상도, 1080p/FullHD (1920x1080) 동영상 해상도
  • 1.5GB 기기: 1.5GB RAM, 1080p/FullHD (1920x1080) UI 해상도, 2160p/UltraHD/4K (3840x2160) 동영상 해상도
  • 추가 메모리 제약으로 인해 OEM이 ActivityManager.isLowRAMDevice() 플래그를 정의한 기타 상황

일반 TV 기기

이러한 기기는 메모리 압력 상황이 심각하지 않습니다. 이러한 기기는 다음과 같은 특성을 갖는 것으로 간주됩니다.

  • RAM 1.5GB 이상, 720p 또는 1080p UI, 1080p 동영상 해상도
  • RAM 2GB 이상, 1080p UI, 1080p 또는 2160p 동영상 해상도

일부 특정 메모리 오용은 여전히 사용 가능한 메모리를 소진하고 성능이 저하될 수 있으므로 이러한 기기에서 앱이 메모리 사용량을 신경 쓰지 않아도 된다는 의미는 아닙니다.

RAM이 부족한 TV 기기의 메모리 타겟

이러한 기기에서 메모리를 측정할 때는 Android 스튜디오 메모리 프로파일러를 사용하여 메모리의 모든 섹션을 모니터링하는 것이 매우 좋습니다. TV 앱은 메모리 사용량을 프로파일링하고 이 섹션에 정의된 기준점 아래로 카테고리를 배치하도록 노력해야 합니다.

메모리 프로파일러

메모리 계산 방법 섹션에서 보고된 메모리 수치에 대한 자세한 설명을 확인할 수 있습니다. TV 앱의 기준 정의의 경우 다음 세 가지 메모리 카테고리에 중점을 둡니다.

  • 익명 + 스왑: Android 스튜디오의 Java + 네이티브 + 스택 할당 메모리로 구성됩니다.
  • 그래픽: 프로파일러 도구에 직접 보고됩니다. 일반적으로 그래픽 텍스처로 구성됩니다.
  • 파일: Android 스튜디오에서 '코드' + '기타' 카테고리로 보고됩니다.

이러한 정의를 바탕으로 다음 표에는 각 메모리 그룹 유형이 사용해야 하는 최대값이 표시되어 있습니다.

메모리 유형 목적 사용량 목표 (1GB)
익명 + 스왑 (Java + 네이티브 + 스택) 할당, 미디어 버퍼, 변수, 기타 메모리 집약적 작업에 사용됩니다. 160MB 미만
그래픽 텍스처 및 디스플레이 관련 버퍼에 GPU에서 사용 30~40MB
파일 메모리의 코드 페이지 및 파일에 사용됩니다. 60~80MB

최대 총 메모리 (익명+스왑+그래픽+파일)는 다음을 초과해서는 안 됩니다.

  • RAM 부족 기기 1GB의 총 메모리 사용량 (익명+스왑 + 그래픽 + 파일)이 280MB입니다.

다음 값을 초과하지 않는 것이 좋습니다.

  • (익명+스왑 + 그래픽)에서 메모리 사용량이 200MB입니다.

파일 메모리

파일 지원 메모리에 관한 일반적인 안내는 다음과 같습니다.

  • 일반적으로 파일 메모리는 OS 메모리 관리에서 잘 처리됩니다.
  • 현재는 메모리 압력의 주요 원인으로 확인되지 않았습니다.

하지만 일반적으로 파일 메모리를 처리할 때는 다음 사항을 고려하세요.

  • 빌드에 사용하지 않는 라이브러리를 포함하지 말고 가능한 경우 전체 라이브러리 대신 작은 라이브러리 하위 집합을 사용하세요.
  • 큰 파일을 메모리에 열어 두지 말고 사용이 끝나면 즉시 해제하세요.
  • Java 및 Kotlin 클래스의 컴파일된 코드 크기를 최소화하려면 앱 축소, 난독화 및 최적화 가이드를 참고하세요.

특정 TV 추천

이 섹션에서는 TV 기기에서 메모리 사용량을 최적화하기 위한 구체적인 권장사항을 제공합니다.

그래픽 메모리

적절한 이미지 형식과 해상도를 사용합니다.

  • 기기 UI 해상도보다 높은 해상도의 이미지를 로드하지 마세요. 예를 들어 1080p 이미지는 720p UI 기기에서 720p로 다운사이즈되어야 합니다.
  • 가능하면 하드웨어 기반의 비트맵을 사용합니다.
    • Glide와 같은 라이브러리에서 기본적으로 사용 중지된 Downsampler.ALLOW_HARDWARE_CONFIG 기능을 사용 설정합니다. 이를 사용 설정하면 그래픽 메모리와 익명 메모리에 모두 있을 수 있는 비트맵이 중복되지 않습니다.
  • 중간 렌더링 및 재렌더링 방지
    • 이러한 문제는 Android GPU 검사기로 식별할 수 있습니다.
    • '텍스처' 섹션에서 요소를 형성하는 것만이 아니라 최종 렌더링을 향한 단계인 이미지를 찾습니다. 이를 보통 '중간 렌더링'이라고 합니다.
    • Android SDK 애플리케이션의 경우 레이아웃 플래그 forceHasOverlappedRendering:false를 사용하여 이 레이아웃의 중간 렌더링을 사용 중지하면 이러한 항목을 삭제할 수 있는 경우가 많습니다.
    • 중복 렌더링에 관한 유용한 리소스로 중복 렌더링 방지를 참고하세요.
  • 가능한 경우 자리표시자 이미지 로드를 피하고 자리표시자 텍스처에는 @android:color/ 또는 @color를 사용하세요.
  • 오프라인으로 구성할 수 있는 경우 기기에서 여러 이미지를 합성하지 마세요. 다운로드한 이미지에서 이미지 컴포지션을 실행하는 대신 독립형 이미지를 로드하는 것이 좋습니다.
  • 비트맵 처리 가이드를 따라 비트맵을 더 효과적으로 처리하세요.

익명+스왑 메모리

익명+스왑은 Android 스튜디오 메모리 프로파일러의 네이티브 + Java + 스택 할당으로 구성됩니다. ActivityManager.isLowMemoryDevice()를 사용하여 기기의 메모리가 제한되어 있는지 확인하고 다음 가이드라인에 따라 이 상황에 적응하세요.

  • 미디어:
    • 기기 RAM 및 동영상 재생 해상도에 따라 미디어 버퍼의 가변 크기를 지정합니다. 동영상 재생 1분에 해당하는 시간입니다.
      1. 1GB / 1080p의 경우 40~60MB
      2. 1.5GB / 1080p의 경우 60~80MB
      3. 1.5GB / 2160p의 경우 80~100MB
      4. 2GB / 2160p의 경우 100~120MB
    • 익명 메모리의 총량이 증가하지 않도록 에피소드를 변경할 때 미디어 메모리 할당 해제
    • 앱이 중지되면 미디어 리소스를 즉시 해제하고 중지합니다. 활동 수명 주기 콜백을 사용하여 오디오 및 동영상 리소스를 처리합니다. 오디오 앱이 아닌 경우 활동에서 onStop()이 발생하면 재생을 중지하고 실행 중인 작업을 모두 저장하고 리소스가 해제되도록 설정합니다. 나중에 필요할 수 있는 작업을 예약합니다. 작업 및 알람 섹션을 참고하세요.
    • 동영상 탐색 시 버퍼의 메모리에 주의: 개발자는 사용자가 동영상을 바로 볼 수 있도록 탐색 시 향후 콘텐츠를 15~60초 추가로 할당하는 경우가 많지만 이렇게 하면 메모리 오버헤드가 추가로 발생합니다. 일반적으로 사용자가 새 동영상 위치를 선택할 때까지 5초 이상의 미래 버퍼를 사용하지 마세요. 탐색 중에 추가 시간을 미리 버퍼링해야 하는 경우 다음을 확인하세요.
      • 탐색 버퍼를 미리 할당하고 재사용합니다.
      • 버퍼 크기는 15~25MB(기기 메모리에 따라 다름)를 초과해서는 안 됩니다.
  • 할당:
    • 그래픽 메모리 안내를 사용하여 익명 메모리에서 이미지를 중복하지 않도록 합니다.
      • 이미지는 메모리를 가장 많이 사용하는 경우가 많으므로 이미지를 중복하면 기기에 많은 부담을 줄 수 있습니다. 이는 이미지 그리드 뷰를 많이 탐색하는 동안 특히 그렇습니다.
    • 화면을 이동할 때 참조를 삭제하여 할당 해제: 비트맵과 객체에 대한 참조가 남아 있지 않은지 확인합니다.
  • 라이브러리:
    • 새 라이브러리를 추가할 때 라이브러리에서 메모리 할당 프로파일링. 라이브러리가 추가 라이브러리를 로드할 수 있으며, 이로 인해 할당이 이루어지고 바인딩이 생성될 수 있기 때문입니다.
  • 네트워킹:
    • 앱 시작 중에 차단 네트워크 호출을 실행하지 마세요. 앱 시작 시간이 느려지고 앱 로드로 인해 메모리가 특히 제한되는 실행 시 추가 메모리 오버헤드가 발생합니다. 먼저 로딩 또는 스플래시 화면을 표시하고 UI가 준비되면 네트워크 요청을 실행합니다.

결합

바인딩다른 애플리케이션을 메모리로 가져오거나 바인딩된 앱의 메모리 소비를 늘려 (이미 메모리에 있는 경우) API 호출을 용이하게 하므로 추가 메모리 오버헤드를 도입합니다. 따라서 포그라운드 앱에 사용 가능한 메모리가 줄어듭니다. 서비스를 바인딩할 때는 바인딩을 사용하는 시점과 기간을 염두에 두세요. 바인딩이 필요하지 않게 되면 즉시 바인딩을 해제해야 합니다.

일반적인 바인딩 및 권장사항:

  • Play Integrity API: 기기 무결성을 확인하는 데 사용됩니다.
    • 로딩 화면 후 미디어 재생 전에 기기 무결성 확인
    • 콘텐츠를 재생하기 전에 PlayIntegrity StandardIntegrityManager에 대한 참조를 해제합니다.
  • Play 결제 라이브러리: Google Play를 사용하여 정기 결제 및 구매를 관리하는 데 사용됩니다.
  • GMS FontsProvider
    • 글꼴 다운로드는 비용이 많이 들고 FontsProvider는 이를 위해 서비스를 바인딩하므로 RAM이 부족한 기기에서는 글꼴 제공자를 사용하는 대신 독립형 글꼴을 사용하는 것이 좋습니다.
  • Google 어시스턴트 라이브러리: 검색 및 인앱 검색에 사용되는 경우가 있습니다. 가능하면 이 라이브러리를 대체하세요.
    • Leanback 앱의 경우: Gboard 텍스트 음성 변환 또는 androidx.leanback 라이브러리를 사용합니다.
      • 검색을 구현하려면 검색 가이드라인을 따르세요.
      • 참고: leanback은 지원 중단되었으며 앱은 TV Compose로 이전해야 합니다.
    • Compose 앱의 경우:
      • Gboard 텍스트 음성 변환을 사용하여 음성 검색을 구현합니다.
    • 다음 볼만한 동영상을 구현하여 앱의 미디어 콘텐츠를 검색 가능하게 만드세요.

포그라운드 서비스

포그라운드 서비스는 알림에 연결된 특수한 유형의 서비스입니다. 이 알림은 휴대전화와 태블릿의 알림 트레이에 표시되지만 TV 기기에는 이러한 기기와 동일한 의미의 알림 트레이가 없습니다. 포그라운드 서비스는 애플리케이션이 백그라운드에 있는 동안 계속 실행될 수 있으므로 유용하지만 TV 앱은 다음 가이드라인을 따라야 합니다.

Android TV와 Google TV에서는 사용자가 앱을 종료한 후 포그라운드 서비스가 계속 실행되는 것이 허용되는 경우는 다음과 같습니다.

  • 오디오 앱: 포그라운드 서비스는 사용자가 앱을 종료한 후 오디오 트랙을 계속 재생하기 위해 계속 실행할 수만 있습니다. 오디오 재생이 종료된 후 서비스가 즉시 중지되어야 합니다.
  • 기타 앱: 사용자가 앱을 나간 후에는 모든 포그라운드 서비스를 중지해야 합니다. 앱이 계속 실행되고 리소스를 소비하고 있음을 사용자에게 알리는 알림이 없기 때문입니다.
  • 추천 또는 다음 볼만한 동영상 업데이트와 같은 백그라운드 작업의 경우 WorkManager를 사용합니다.

작업 및 알람

WorkManager는 백그라운드 반복 작업을 예약하기 위한 최신 Android API입니다. WorkManager는 사용 가능한 경우 (SDK 23 이상) 새 JobScheduler를 사용하고, 그렇지 않은 경우 이전 AlarmManager를 사용합니다. TV에서 예약된 작업을 실행하는 권장사항은 다음을 따르세요.

  • SDK 23 이상에서는 특히 AlarmManager.set(), AlarmManager.setExact() 및 유사한 메서드를 사용하는 AlarmManager API를 사용하지 마세요. 시스템에서 작업 실행에 적절한 시간 (예: 기기가 유휴 상태일 때)을 결정할 수 없습니다.
  • RAM이 부족한 기기에서는 엄격하게 필요한 경우가 아니면 작업을 실행하지 마세요. 필요한 경우 WorkManager WorkRequest재생 후 추천 업데이트에만 사용하고 앱이 열려 있는 동안 시도하세요.
  • 적절한 시점에 시스템에서 작업을 실행하도록 WorkManager Constraints를 정의합니다.

Kotlin

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()

자바

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()
  • 정기적으로 작업을 실행해야 하는 경우 (예: 다른 기기에서 앱의 사용자 콘텐츠 시청 활동을 기반으로 다음 볼 콘텐츠를 업데이트하는 경우) 작업의 메모리 소비를 30MB 미만으로 유지하여 메모리 사용량을 낮게 유지합니다.

일반적인 메모리 고려사항

다음 가이드라인은 Android 앱 개발에 관한 일반 정보를 제공합니다.

  • 객체 할당을 최소화하고 객체 재사용을 최적화하며 사용하지 않는 객체를 즉시 할당 해제합니다.
    • 객체, 특히 비트맵에 대한 참조를 보유하지 마세요.
    • System.gc() 및 직접 출시 메모리 호출은 시스템의 메모리 처리 프로세스를 방해하므로 사용하지 마세요. 예를 들어 zRAM을 사용하는 기기에서 gc()를 강제로 호출하면 메모리 압축 및 압축 해제로 인해 메모리 사용량이 일시적으로 증가할 수 있습니다.
    • 이제 지원이 중단된 Leanback UI 툴킷의 Compose에 있는 카탈로그 브라우저 또는 RecyclerView에 표시된 대로 LazyList를 사용하여 뷰를 재사용하고 목록 요소를 다시 만들지 마세요.
    • 변경될 가능성이 낮은 외부 콘텐츠 제공자에서 읽은 요소를 로컬로 캐시하고 추가 외부 메모리 할당을 방지하는 업데이트 간격을 정의합니다.
  • 메모리 누수가 있는지 확인합니다.
    • 익명 스레드 내부의 참조, 해제되지 않는 동영상 버퍼 재할당, 기타 유사한 상황과 같은 일반적인 메모리 누수 사례를 주의하세요.
    • 힙 덤프를 사용하여 메모리 누수를 디버그합니다.
  • 콜드 스타트 시 앱을 실행할 때 필요한 JIT 컴파일 양을 최소화하도록 기준 프로필을 생성합니다.

직접 메모리 회수 이해

Android TV 애플리케이션이 메모리를 요청하고 시스템에 압력이 가해지면 Android를 뒷받침하는 Linux 커널이 직접 메모리 회수를 사용해야 할 수 있습니다.

이 프로세스에는 할당 스레드를 완전히 일시중지하여 확보된 메모리 페이지를 기다리는 작업이 포함됩니다. 이는 백그라운드 회수가 충분한 메모리 풀을 사전 예방적으로 유지할 수 없는 경우에 발생합니다.

이로 인해 시스템에서 충분한 메모리가 확보될 때까지 스레드 할당을 일시중지하므로 사용자 환경에서 눈에 띄는 일시중지 또는 버벅거림이 발생할 수 있습니다. 이러한 의미에서 스레드 할당은 malloc()와 같은 애플리케이션 코드 호출로 제한되지 않습니다. 예를 들어 코드 페이지에서 페이지에 메모리를 할당해야 합니다.

도구 요약