메모리 최적화는 원활한 성능을 보장하고, 앱 비정상 종료를 방지하며, 시스템 안정성과 플랫폼 상태를 유지하는 데 매우 중요합니다. 모든 앱에서 메모리 사용량을 모니터링하고 최적화해야 하지만 TV용 콘텐츠 앱 기기에는 휴대기기용 일반적인 Android 앱과는 다른 특정 문제가 있습니다.
메모리 사용량이 많으면 다음과 같은 앱 및 시스템 동작 문제가 발생할 수 있습니다.
- 앱 자체가 느려지거나 지연되거나 최악의 경우 종료될 수 있습니다.
- 사용자에게 표시되는 시스템 서비스 (볼륨 제어, 사진 설정 대시보드, 음성 어시스턴트 등)가 매우 느려지거나 전혀 작동하지 않을 수 있습니다.
- 시스템 구성요소가 종료될 수 있습니다. 그러면 이러한 구성요소가 다시 시작되어 극심한 리소스 경합이 발생하고 포그라운드 앱에 직접적인 영향을 미칩니다.
- 런처로의 전환이 상당히 지연될 수 있으며 전환이 완료될 때까지 포그라운드 앱이 응답하지 않는 것처럼 보일 수 있습니다.
- 시스템은 직접 회수 상황으로 전환되어 메모리 할당을 기다리는 동안 스레드 실행을 일시적으로 일시중지할 수 있습니다. 이는 기본 스레드나 코덱 관련 스레드와 같은 모든 스레드에서 발생할 수 있으며, 이로 인해 오디오 및 동영상 프레임이 누락되고 UI 결함이 발생할 수 있습니다.
TV 기기의 메모리 고려사항
TV 기기는 일반적으로 휴대전화나 태블릿보다 메모리가 훨씬 적습니다. 예를 들어 TV에서 확인할 수 있는 구성은 RAM 1GB 및 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 기기: RAM 1GB, 720p/HD (1280x720) UI 해상도, 1080p/FullHD (1920x1080) 동영상 해상도
- 1.5GB 기기: RAM 1.5GB, 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 |
최대 총 메모리 (Anon+Swap + 그래픽 + 파일)는 다음을 초과해서는 안 됩니다.
- RAM이 1GB인 저사양 기기의 총 메모리 사용량 (Anon+Swap + Graphics + File)이 280MB입니다.
다음을 초과하지 않는 것이 좋습니다.
- (Anon+Swap + Graphics)의 메모리 사용량이 200MB입니다.
파일 메모리
파일 지원 메모리에 관한 일반적인 지침은 다음과 같습니다.
- 일반적으로 파일 메모리는 OS 메모리 관리에서 잘 처리합니다.
- 현재로서는 메모리 압력의 주요 원인으로 파악되지 않았습니다.
그러나 일반적으로 파일 메모리를 다룰 때는 다음 사항에 유의하세요.
- 빌드에 사용되지 않는 라이브러리를 포함하지 마세요. 그리고 가능하면 전체 라이브러리 대신 라이브러리의 작은 하위 집합을 사용하세요.
- 메모리에 대용량 파일을 열어 두지 마세요. 작업이 완료되는 즉시 파일을 해제하세요.
- Java 및 Kotlin 클래스의 컴파일된 코드 크기를 최소화하려면 앱 축소, 난독화, 최적화 가이드를 참고하세요.
특정 TV 추천
이 섹션에서는 TV 기기의 메모리 사용량을 최적화하기 위한 구체적인 권장사항을 제공합니다.
그래픽 메모리
적절한 이미지 형식과 해상도를 사용합니다.
- 기기 UI 해상도보다 높은 해상도의 이미지를 로드하지 마세요. 예를 들어 1080p 이미지는 720p UI 기기에서 720p로 축소되어야 합니다.
- 가능하면 하드웨어 지원 비트맵을 사용합니다.
- Glide와 같은 라이브러리에서 기본적으로 사용 중지된
Downsampler.ALLOW_HARDWARE_CONFIG
기능을 사용 설정합니다. 이 기능을 사용 설정하면 그래픽 메모리와 익명 메모리에 모두 있는 비트맵이 중복되지 않습니다.
- Glide와 같은 라이브러리에서 기본적으로 사용 중지된
- 중간 렌더링 및 재렌더링 방지
- Android GPU 검사기를 사용하여 다음을 식별할 수 있습니다.
- '텍스처' 섹션에서 텍스처를 구성하는 요소가 아니라 최종 렌더링을 향한 단계인 이미지를 찾습니다. 이를 일반적으로 '중간 렌더링'이라고 합니다.
- Android SDK 애플리케이션의 경우 레이아웃 플래그
forceHasOverlappedRendering:false
를 사용하여 이 레이아웃의 중간 렌더링을 사용 중지하면 이러한 렌더링을 삭제할 수 있습니다. - 중첩 렌더링에 관한 중첩 렌더링 방지를 유용한 리소스로 참고하세요.
- 가능하면 자리표시자 이미지를 로드하지 마세요. 자리표시자 텍스처에는
@android:color/
또는@color
를 사용하세요. - 컴포지션을 오프라인에서 실행할 수 있는 경우 기기에서 여러 이미지를 합성하지 마세요. 다운로드한 이미지에서 이미지 컴포지션을 실행하는 대신 독립형 이미지를 로드하는 것이 좋습니다.
- 비트맵 처리 가이드를 따라 비트맵을 더 효과적으로 처리하세요.
Anon+Swap 메모리
Anon+Swap은 Android 스튜디오 메모리 프로파일러의 네이티브 + Java + 스택 할당으로 구성됩니다. ActivityManager.isLowMemoryDevice()
를 사용하여 기기에 메모리 제약이 있는지 확인하고 이 가이드라인에 따라 이 상황에 적응합니다.
- 미디어:
- 기기 RAM 및 동영상 재생 해상도에 따라 미디어 버퍼의 가변 크기를 지정합니다. 동영상 재생 1분에 해당합니다.
- 1GB / 1080p의 경우 40~60MB
- 1.5GB / 1080p의 경우 60~80MB
- 1.5GB / 2160p의 경우 80~100MB
- 2GB / 2160p의 경우 100~120MB
- 익명의 메모리 총량이 증가하지 않도록 에피소드를 변경할 때 미디어 메모리 할당을 해제합니다.
- 앱이 중지될 때 미디어 리소스를 즉시 해제하고 중지: 활동 수명 주기 콜백을 사용하여 오디오 및 동영상 리소스를 처리합니다. 오디오 앱이 아닌 경우 활동에서
onStop()
이 발생하면 재생을 중지하고 실행 중인 모든 작업을 저장하고 리소스가 해제되도록 설정합니다. 나중에 필요할 수 있는 작업을 예약합니다. 작업 및 알람 섹션을 참고하세요.LiveData
및LifecycleOwner
와 같은 수명 주기 인식 구성요소를 사용하여 Activity 수명 주기 호출을 처리할 수 있습니다.- 작업의 수명 주기를 인식하도록 하려면 Kotlin 코루틴 및 Kotlin Flow를 사용해도 됩니다.
- 동영상 탐색 시 버퍼의 메모리 주의: 개발자는 사용자에게 동영상을 준비하기 위해 탐색 시 향후 콘텐츠를 15~60초 추가로 할당하는 경우가 많지만, 이렇게 하면 메모리 오버헤드가 추가로 발생합니다.
일반적으로 사용자가 새 동영상 위치를 선택할 때까지 5초 이상의 향후 버퍼를 사용하지 마세요. 탐색하는 동안 추가 시간을 미리 버퍼링해야 하는 경우 다음을 실행해야 합니다.
- 미리 탐색 버퍼를 할당하고 재사용합니다.
- 버퍼 크기는 기기 메모리에 따라 15~25MB 이하여야 합니다.
- 기기 RAM 및 동영상 재생 해상도에 따라 미디어 버퍼의 가변 크기를 지정합니다. 동영상 재생 1분에 해당합니다.
- 할당:
- 그래픽 메모리 안내를 사용하여 익명 메모리에 이미지를 중복하지 않도록 합니다.
- 이미지는 메모리를 가장 많이 사용하는 경우가 많으므로 이미지를 복제하면 기기에 큰 부담이 될 수 있습니다. 이는 이미지 그리드 뷰를 많이 탐색할 때 특히 그렇습니다.
- 화면을 이동할 때 참조를 삭제하여 할당 해제: 비트맵 및 객체에 대한 참조가 남아 있지 않은지 확인합니다.
- 그래픽 메모리 안내를 사용하여 익명 메모리에 이미지를 중복하지 않도록 합니다.
- 라이브러리:
- 새 라이브러리를 추가할 때 라이브러리의 메모리 할당을 프로파일링합니다. 새 라이브러리를 추가하면 추가 라이브러리도 로드될 수 있으며, 이로 인해 할당이 발생하고 바인딩이 생성될 수 있기 때문입니다.
- 네트워킹:
- 앱 시작 중에 차단 네트워크 호출을 실행하지 마세요. 차단 네트워크 호출은 애플리케이션 시작 시간을 느리게 하고 실행 시 메모리 오버헤드를 추가로 생성하며, 이때 메모리는 앱 로드로 인해 특히 제약을 받습니다. 먼저 로드 화면 또는 스플래시 화면을 표시하고 UI가 설정되면 네트워크 요청을 실행합니다.
바인딩
바인딩은 API 호출을 용이하게 하기 위해 다른 애플리케이션을 메모리로 가져오거나 바인딩된 앱의 메모리 소비를 늘립니다 (이미 메모리에 있는 경우). 따라서 추가 메모리 오버헤드가 발생합니다. 따라서 포그라운드 앱의 사용 가능한 메모리가 줄어듭니다. 서비스를 바인딩할 때는 바인딩을 사용하는 시점과 기간에 유의하세요. 필요하지 않은 경우 즉시 바인딩을 해제해야 합니다.
일반적인 바인딩 및 권장사항:
- Play Integrity API: 기기 무결성을 확인하는 데 사용됩니다.
- 로드 화면 후 및 미디어 재생 전에 기기 무결성 확인
- 콘텐츠를 재생하기 전에 PlayIntegrity
StandardIntegrityManager
참조를 해제합니다.
- Play 결제 라이브러리: Google Play를 사용하여 정기 결제 및 구매를 관리하는 데 사용됩니다.
- 로드 화면 후 라이브러리를 초기화하고 미디어를 재생하기 전에 모든 결제 작업을 처리합니다.
- 라이브러리 사용이 끝났을 때와 동영상이나 미디어를 재생하기 전에 항상
BillingClient.endConnection()
를 사용하세요. - 결제 작업을 다시 실행해야 하는 경우
BillingClient.isReady()
및BillingClient.getConnectionState()
를 사용하여 서비스 연결이 끊어졌는지 확인한 후 완료되면BillingClient.endConnection()
를 다시 실행합니다.
- GMS FontsProvider
- 글꼴을 다운로드하는 데 비용이 많이 들고 FontsProvider가 이를 위해 서비스를 바인딩하므로 RAM이 부족한 기기에서는 글꼴 제공자를 사용하는 대신 독립형 글꼴을 사용하는 것이 좋습니다.
- Google 어시스턴트 라이브러리: 검색 및 인앱 검색에 사용되는 경우가 있습니다. 가능하면 이 라이브러리를 대체하세요.
- Leanback 앱: Gboard 음성 텍스트 또는 androidx.leanback 라이브러리를 사용합니다.
- 검색을 구현하려면 검색 가이드라인을 따르세요.
- 참고: leanback은 지원 중단되었으며 앱은 TV Compose로 전환해야 합니다.
- Compose 앱:
- Gboard 음성 텍스트 변환을 사용하여 음성 검색을 구현합니다.
- 다음 볼만한 동영상을 구현하여 앱의 미디어 콘텐츠를 검색 가능하도록 합니다.
- Leanback 앱: Gboard 음성 텍스트 또는 androidx.leanback 라이브러리를 사용합니다.
포그라운드 서비스
포그라운드 서비스는 알림에 연결된 특수한 유형의 서비스입니다. 이 알림은 휴대전화 및 태블릿의 알림 표시줄에 표시되지만 TV 기기에는 이러한 기기와 같은 의미의 알림 표시줄이 없습니다. 포그라운드 서비스는 애플리케이션이 백그라운드에 있는 동안 계속 실행할 수 있으므로 유용하지만 TV 앱은 다음 가이드라인을 따라야 합니다.
Android TV 및 Google TV에서는 사용자가 앱을 종료한 후에만 포그라운드 서비스가 계속 실행되도록 허용됩니다.
- 오디오 앱: 사용자가 앱을 종료한 후 오디오 트랙을 계속 재생하기 위해 포그라운드 서비스를 계속 실행할 수 있습니다. 오디오 재생이 끝난 후에는 즉시 서비스를 중지해야 합니다.
- 기타 앱: 앱이 계속 실행되고 리소스를 소비하고 있다는 사실을 사용자에게 알리는 알림이 없으므로 사용자가 앱을 나간 후 모든 포그라운드 서비스가 중지되어야 합니다.
- 맞춤 동영상 업데이트 또는 다음 볼만한 동영상과 같은 백그라운드 작업에는
WorkManager
를 사용하세요.
작업 및 알람
WorkManager
는 백그라운드 반복 작업을 예약하기 위한 최신 Android API입니다.
WorkManager는 사용 가능한 경우 (SDK 23 이상) 새 JobScheduler
를 사용하고 사용 불가능한 경우 이전 AlarmManager
를 사용합니다. TV에서 예약된 작업을 실행하는 권장사항은 다음과 같습니다.
- SDK 23 이상에서
AlarmManager
API, 특히AlarmManager.set()
,AlarmManager.setExact()
및 유사한 메서드를 사용하지 마세요. 이러한 메서드를 사용하면 시스템에서 작업을 실행할 적절한 시점 (예: 기기가 유휴 상태일 때)을 결정할 수 없습니다. - 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()
를 강제로 호출하면 메모리의 압축과 압축 해제로 인해 메모리 사용량이 일시적으로 증가할 수 있습니다.- Compose의 카탈로그 브라우저에 나와 있는 것과 같이
LazyList
를 사용하거나, 이제 지원 중단된 Leanback UI 툴킷의RecyclerView
를 사용하여 목록 요소를 다시 만들지 않고 뷰를 재사용합니다. - 변경될 가능성이 낮은 외부 콘텐츠 제공업체에서 읽은 요소를 로컬에 캐시하고 추가 외부 메모리 할당을 방지하는 업데이트 간격을 정의합니다.
- 메모리 누수 가능성을 확인합니다.
- 익명의 스레드 내 참조, 해제되지 않는 동영상 버퍼 재할당, 기타 유사한 상황과 같은 일반적인 메모리 누수 사례에 주의하세요.
- 힙 덤프를 사용하여 메모리 누수를 디버그합니다.
- 기준 프로필을 생성하여 콜드 스타트에서 앱을 실행할 때 필요한 JIT 컴파일의 양을 최소화합니다.
도구 요약
- Android 스튜디오 메모리 프로파일러 도구를 사용하여 사용 중에 메모리 소비량을 확인합니다.
- heapdump를 사용하여 특정 객체 및 비트맵 할당을 확인합니다.
- 네이티브 메모리 프로파일러를 사용하여 Java 또는 Kotlin이 아닌 할당을 확인합니다.
- Android GPU 검사기를 사용하여 그래픽 할당을 확인합니다.