효율적인 네트워크 액세스를 위한 다운로드 최적화

앱에서 무선 통신을 사용하여 데이터를 전송하는 작업은 배터리 소모의 주된 원인 중 하나입니다. 네트워크 활동과 연결된 배터리 소모를 최소화하려면 연결 모델이 기반 무선 통신 하드웨어에 미치는 영향을 이해하는 것이 중요합니다.

이 과정에서는 무선 통신 상태 시스템을 소개하고 앱의 연결 모델과 상호작용하는 방법을 설명합니다. 그리고 계속해서 데이터 연결을 최소화하고, 미리 가져오기를 사용하고, 전송을 번들로 처리하여 데이터 전송과 관련된 배터리 소모를 최소화하는 방법을 제안합니다.

무선 통신 상태 시스템

무선 통신이 완전히 활성화되어 있으면 전력이 매우 많이 소모되기 때문에, 사용하지 않는 동안 여러 에너지 상태 간 전환을 통해 전력을 절약하고 필요할 때 무선 통신을 '켜는' 데 드는 지연 시간을 최소화합니다.

일반적인 3G 네트워크 무선 통신의 상태 시스템은 세 가지 에너지 상태로 구성됩니다.

  1. 전체 전력: 연결이 활성 상태일 때 사용하며 기기에서 가능한 최고 속도로 데이터를 전송할 수 있습니다.
  2. 저전력: 완전한 상태에서 배터리 전력의 약 50% 정도를 사용하는 중간 상태입니다.
  3. 대기: 네트워크 연결이 활성화되지 않았거나 필요하지 않은 최소 에너지 상태입니다.

저전력 상태와 대기 상태에서는 배터리 소모가 매우 낮지만, 네트워크 요청에 대한 지연 시간은 매우 깁니다. 저전력 상태에서 전체 전력 상태로 돌아가는 데에는 약 1.5초가 걸리는 반면 대기 상태에서 전체 전력 상태로 가는 데에는 2초 이상이 걸릴 수 있습니다.

상태 시스템은 지연 시간을 최소화하기 위해 지연을 사용하여 낮은 에너지 상태로의 전환을 연기합니다. 그림 1은 AT&T의 일반적인 3G 무선 통신 타이밍을 나타냅니다.

그림 1. 일반적인 3G 무선 통신 상태 시스템

각 기기의 무선 통신 상태 시스템, 특히 관련 전환 지연('테일 타임') 및 시작 지연 시간은 사용하는 무선 통신 기술(2G, 3G, LTE 등)에 따라 다르며 기기가 작동하는 이동통신사 네트워크에서 정의 및 구성합니다.

이 단원에서는 AT&T에서 제공한 데이터를 기반으로 일반적인 3G 무선 통신의 대표적인 상태 시스템을 설명합니다. 그러나 일반적인 원칙과 그에 따른 권장사항은 모든 무선 통신 구현에 적용됩니다.

이 접근 방식은 사용자가 웹을 탐색하는 동안 원치 않는 지연 시간을 방지하므로 일반적인 웹 탐색에 특히 효과적입니다. 또한 테일 타임이 비교적 낮기 때문에 탐색 세션이 완료되면 무선 통신을 낮은 에너지 상태로 전환할 수 있습니다.

그러나 포그라운드(지연 시간이 중요)와 백그라운드(배터리 수명이 우선) 모두에서 앱을 실행하는 Android와 같은 최신 스마트폰 OS에 이 접근 방식을 사용하면 앱 효율이 떨어질 수 있습니다.

앱이 무선 통신 상태 시스템에 영향을 주는 방식

새로운 네트워크 연결을 만들 때마다 무선 통신이 전체 전력 상태로 전환됩니다. 위에 설명된 일반적인 3G 무선 통신 상태 시스템의 경우, 전송하는 동안과 추가로 테일 타임 5초 동안 전체 전력 상태를 유지하다가 12초 동안 낮은 에너지 상태를 유지합니다. 따라서 일반적인 3G 기기의 경우 모든 데이터 전송 세션에서 무선 통신은 거의 20초 동안 에너지를 소비합니다.

실제로 이는 앱이 18초마다 번들되지 않은 데이터를 1초 동안 전송한다면 무선 통신이 영구적으로 활성 상태를 유지하며, 대기 상태로 전환되기 직전에 고전력 상태로 돌아간다는 의미입니다. 따라서 1분 중 18초는 고전력 상태로, 나머지 42초 동안은 저전력 상태에서 배터리를 소비합니다.

한편, 같은 앱이 1분에 3초씩 번들을 전송한다면 무선 통신은 약 8초 동안만 고전력 상태를 유지하고 추가로 12초만 더 저전력 상태를 지속합니다.

두 번째 예에서는 1분에 추가로 40초 동안 무선 통신을 대기 상태로 둘 수 있으므로 배터리 소모가 크게 절감됩니다.

그림 2. 번들된 전송과 번들되지 않은 전송의 무선 통신 전력 사용 비교

데이터 미리 가져오기

데이터 미리 가져오기는 독립 데이터 전송 세션의 수를 줄이는 효과적인 방법입니다. 미리 가져오기를 사용하면 지정된 기간 동안 필요할 만한 모든 데이터를 한 번에 하나의 연결을 사용하여 전체 용량으로 다운로드할 수 있습니다.

전송을 미리 로드하면 데이터 다운로드에 필요한 무선 통신 활성화 수를 줄일 수 있습니다. 따라서 배터리 수명을 연장할 뿐 아니라 지연 시간을 개선하고, 필요한 대역폭을 낮추고, 다운로드 시간을 절약할 수 있습니다.

또한 작업을 실행하거나 데이터를 보기 전에 다운로드 완료를 기다리느라 발생하는 인앱 지연 시간을 최소화하므로 사용자 환경도 개선됩니다.

그러나 미리 가져오기를 너무 많이 사용하면 사용하지 않는 데이터를 다운로드하여 배터리 소모와 대역폭 사용, 다운로드 할당량 사용이 모두 증가할 위험도 있습니다. 앱이 미리 가져오기 완료를 기다리는 동안 애플리케이션 시작이 지연되지 않도록 하는 것도 중요합니다. 이는 실제 상황에서 데이터를 순서대로 처리하거나 애플리케이션 시작에 필요한 데이터를 먼저 다운로드 및 처리하도록 연속적인 전송에 우선순위를 두는 것을 의미합니다.

미리 가져오기 사용 정도는 다운로드하는 데이터의 크기와 사용 가능성에 따라 결정됩니다. 위에 설명된 상태 시스템을 기반으로, 현재 사용자 세션에서 사용할 가능성이 50%인 데이터를 위한 대략적인 가이드를 제시하자면, 사용하지 않은 데이터를 다운로드하는 잠재적인 비용이 시작할 데이터를 다운로드하여 잠재적으로 절약되는 비용과 같아지기 전에 약 6초(약 1~2Mb) 정도 미리 가져오는 것입니다.

일반적으로, 2~5분 간격으로 다운로드를 시작할 만큼씩 1~5MB 단위로 데이터를 미리 가져오는 것이 좋습니다.

이 원칙에 따라, 동영상 파일과 같은 큰 다운로드 파일은 정기적인 간격으로(2~5분마다) 청크 단위로 다운로드하여 다음 몇 분 동안 볼 가능성이 있는 동영상 데이터만 미리 가져와야 합니다.

추가 다운로드는 다음 섹션, 일괄 전송 및 연결에 설명된 대로 번들로 처리되어야 하며 추정값은 연결 유형에 따른 다운로드 패턴 수정에 설명된 대로 연결 유형과 속도에 따라 달라집니다.

몇 가지 실제 예를 살펴보겠습니다.

음악 플레이어

앨범 전체를 미리 가져올 수도 있지만, 사용자가 첫 곡만 듣고 멈추는 경우 상당한 양의 대역폭과 배터리 수명이 낭비됩니다.

재생 중인 곡 외에 추가로 한 곡을 준비하여 버퍼링을 유지하는 것이 좋습니다. 음악 스트리밍의 경우 항상 무선 통신을 사용하도록 연속 스트림을 유지하는 것보다 HTTP 실시간 스트리밍을 사용해 위에 설명된 미리 가져오기와 비슷한 방법으로 오디오 스트림을 한 번에 전송하는 것이 좋습니다.

뉴스 리더

수많은 뉴스 앱이 카테고리를 선택한 후에만 헤드라인을 다운로드하고, 사용자가 읽으려고 할 때만 기사 전문을 다운로드하고, 미리보기 이미지는 스크롤해서 보기에 표시될 때에만 다운로드하여 대역폭을 줄이려고 합니다.

이 접근 방식을 사용하면 사용자가 헤드라인을 스크롤하고, 카테고리를 변경하고, 기사를 읽는 동안 무선 통신이 사용자가 뉴스를 읽는 대부분의 세션에서 활성 상태를 유지하게 됩니다. 게다가 에너지 상태 간을 지속해서 전환하기 때문에 카테고리를 전환하거나 기사를 읽을 때 생기는 지연 시간도 깁니다.

첫 번째 뉴스 헤드라인과 미리보기 이미지 세트로 시작하여 초반에 합당한 양의 데이터를 미리 가져와서 시작 지연 시간을 줄인 후 나머지 헤드라인 및 미리보기 이미지와 기본 헤드라인 목록에서 사용할 수 있는 각 기사의 본문을 계속 미리 가져오는 방식이 더 좋습니다.

또는 미리 정해진 일정에 따라 모든 헤드라인, 미리보기 이미지, 기사 본문, 전체 기사 사진 등을 (일반적으로 백그라운드에서) 미리 가져올 수도 있습니다. 이 접근 방식은 사용하지 않을 콘텐츠를 다운로드하여 대역폭과 배터리 수명을 소모할 위험이 있으므로 주의해서 구현해야 합니다.

한 가지 해결 방법은 Wi-Fi에 연결된 동안에만, 그리고 가능하면 기기를 충전 중인 동안에만 전체 다운로드를 실행하도록 예약하는 것입니다. 자세한 내용은 연결 유형에 따라 다운로드 패턴 수정에서 자세히 다룹니다.

일괄 전송 및 연결

일반적인 3G 무선 통신을 사용하는 경우, 관련 데이터 전송의 크기와 관계없이 연결을 시작할 때마다 무선 통신에 거의 20초의 전력이 소비될 수 있습니다.

20초마다 서버를 핑하여 앱이 실행 중이며 사용자에게 표시되는 것을 확인하는 앱은 무선 통신 전원을 계속 켜 두기 때문에 실제 데이터 전송이 거의 없어도 배터리를 많이 사용합니다.

이 점을 염두에 두고 데이터를 번들로 전송하며 대기 중인 전송 대기열을 만드는 것이 중요합니다. 올바르게 구현하면 비슷한 기간에 실행될 전송 단계를 효과적으로 전환하여 동시에 모두 실행하고 무선 통신이 전력을 사용하는 시간을 가능한 한 짧게 만들 수 있습니다.

이 접근 방식의 근본적인 철학은 필요한 세션 수를 줄이기 위해 각 전환 세션에서 가능한 한 많은 데이터를 전송하는 것입니다.

즉, 지연에 영향을 크게 받지 않는 전송을 대기열에 넣어 일괄 전송을 만들고 예약된 업데이트 및 미리 가져오기를 선점하여 신속한 전송이 필요할 때 모두 함께 실행해야 합니다. 마찬가지로 예약된 업데이트와 정기적인 미리 가져오기를 실행하면 대기 중인 전송 대기열의 실행도 시작해야 합니다.

데이터 미리 가져오기의 실제 예로 돌아가 보겠습니다.

위에 설명된 미리 가져오기 루틴을 사용하는 뉴스 애플리케이션이 있습니다. 뉴스 리더는 분석 정보를 수집하여 사용자의 읽기 패턴을 이해하고 인기 기사 순위를 정합니다. 최신 뉴스를 표시하기 위해 매시간 업데이트를 확인합니다. 대역폭을 절약하려면 각 기사의 사진을 모두 다운로드하는 대신 미리보기 이미지만 미리 가져오고 전체 사진은 기사가 선택된 경우에만 다운로드합니다.

이 예에서 앱에서 수집된 모든 분석 정보는 바로 전송하는 대신 번들로 처리하여 다운로드 대기열에 넣어야 합니다. 처리된 번들은 원본 크기 사진을 다운로드하거나 시간별 업데이트를 실행할 때 전송해야 합니다.

시간에 민감하거나 주문형 전송(원본 크기 이미지 다운로드 등)은 정기적으로 예약된 업데이트보다 우선적으로 처리해야 합니다. 계획된 업데이트는 주문형 전송과 동시에 실행되고, 다음 업데이트가 설정된 간격 이후에 발생하도록 예약해야 합니다. 이 접근 방식을 사용하면 시간에 민감한 필요한 사진의 다운로드에 편승하여 정기 업데이트 실행 비용을 절감할 수 있습니다.

연결 감소

일반적으로 새로 연결을 시작하는 것보다 기존 네트워크 연결을 재사용하는 것이 더 효율적입니다. 연결을 재사용하면 네트워크에서 혼잡 및 관련 네트워크 데이터 문제에 더 지능적으로 대응할 수 있습니다.

동시에 여러 연결을 만들어 데이터를 다운로드하거나 여러 개의 연속적인 GET 요청을 연결하는 것보다, 가능하면 이러한 요청을 번들로 만들어 하나의 GET 요청으로 처리하는 것이 좋습니다.

예를 들어 여러 뉴스 카테고리와 관련하여 여러 쿼리를 만드는 것보다 모든 뉴스 기사에 관한 요청 하나를 만들어 단일 요청/응답에 반환하는 것이 더 효율적입니다. 서버 및 클라이언트 시간 초과와 관련된 종료/종료 확인 패킷을 전송하려면 무선 통신이 활성화되어야 하므로 시간 초과를 기다리는 대신 사용하지 않는 연결을 닫는 것이 좋습니다.

그러나 연결을 너무 빨리 닫으면 재사용할 수 없게 되며 새 연결을 설정하기 위한 추가 오버헤드가 필요합니다. 연결을 즉시 닫지 않고, 기본 시간 초과로 만료되기 전에 닫는 것이 좋습니다.

네트워크 프로파일러로 문제 식별

네트워크 프로파일러를 사용해 애플리케이션에서 네트워크 요청을 하는 시기를 추적하세요. 앱에서 데이터를 전송하는 방법과 시기를 모니터링하고 기반 코드를 적절하게 최적화할 수 있습니다.

그림 3은 약 15초 간격으로 데이터를 소량 전송하는 패턴을 보여주며 이 경우 각 요청을 미리 가져오거나 업로드를 번들로 처리하면 효율을 크게 높일 수 있습니다.

그림 3. 네트워크 사용 추적

데이터 전송 빈도와 각 연결에서 전송되는 데이터의 양을 모니터링하면 애플리케이션에서 배터리 효율을 높일 수 있는 부분을 식별할 수 있습니다. 일반적으로는 지연될 수 있거나 나중에 전송이 선점되어야 하는 짧은 급증 지점을 찾습니다.

전송이 급증하는 원인을 더 잘 식별하기 위해 트래픽 통계 API를 사용하면 TrafficStats.setThreadStatsTag() 메서드를 사용하여 스레드 내에 발생하는 데이터 전송에 태그할 수 있고, 그 후 TrafficStats.tagSocket()TrafficStats.untagSocket()을 사용하여 개별 소켓을 수동으로 태그하거나 태그 해제할 수 있습니다. 예:

Kotlin

    TrafficStats.setThreadStatsTag(0xF00D)
    TrafficStats.tagSocket(outputSocket)
    // Transfer data using socket
    TrafficStats.untagSocket(outputSocket)
    

자바

    TrafficStats.setThreadStatsTag(0xF00D);
    TrafficStats.tagSocket(outputSocket);
    // Transfer data using socket
    TrafficStats.untagSocket(outputSocket);
    

HttpURLConnection 라이브러리는 현재 TrafficStats.getThreadStatsTag() 값에 따라 자동으로 소켓에 태그를 지정합니다. 라이브러리는 또한 연결 유지 풀을 통해 재활용된 소켓에 태그를 지정 및 해제합니다.

Kotlin

    private class IdentifyTransferSpikeTask : AsyncTask<String, Nothing, String>() {

        override fun onPreExecute() = TrafficStats.setThreadStatsTag(0xF00D)

        override fun doInBackground(vararg urls: String): String {
            try {
                // Make network request using HttpURLConnection.connect()
            }
        }

        override fun onPostExecute(result: String) = TrafficStats.clearThreadStatsTag()
    }
    

자바

    private class IdentifyTransferSpikeTask extends AsyncTask<String, Void, String> {
        @Override
        protected void onPreExecute() {
          TrafficStats.setThreadStatsTag(0xF00D);
        }

        @Override
        protected String doInBackground(String... urls) {
            try {
                // Make network request using HttpURLConnection.connect()
            }
        }

        @Override
        protected void onPostExecute(String result) {
            TrafficStats.clearThreadStatsTag();
       }
    }
    

소켓 태그 지정은 Android 4.0에서 지원되지만, 실시간 통계는 Android 4.0.3 이상을 실행하는 기기에만 표시됩니다.