앱이 네트워크에 전송하는 요청은 전력 소모가 많은 모바일 또는 Wi-Fi 무선 기능을 사용하기 때문에 배터리 소모의 주요 원인입니다. 이 무선 기능은 패킷을 보내고 받는 데 필요한 전력 외에는 기능을 켜고 켜진 상태로 유지하는 데에만 추가 전력을 소비합니다. 모바일 무선 기능에서 15초마다 발생하는 네트워크 요청과 같이 단순한 것일지라도 지속적으로 빠르게 배터리 전력을 소모할 수 있습니다.
일반 업데이트에는 세 가지 일반적인 유형이 있습니다.
- 사용자가 시작합니다. 당겨 새로고침 동작과 같은 일부 사용자 동작에 따라 업데이트를 실행합니다.
- 앱에서 시작된 경우. 반복적으로 업데이트를 수행합니다.
- 서버에서 시작 에서 알림에 대한 응답으로 업데이트 수행 만들 수 있습니다
이 주제에서는 이러한 각 항목을 살펴보고 배터리 소모를 줄이기 위해 최적화할 수 있는 추가 방법을 설명합니다.
사용자가 시작한 요청 최적화
사용자 시작 요청은 일반적으로 특정 사용자 동작에 대한 응답으로 발생합니다. 예를 들어 최신 뉴스 기사를 읽는 데 사용되는 앱에서 사용자가 새로고침을 풀어 그리기 동작을 실행하여 새 기사를 확인할 수 있습니다. 이 다음과 같은 기법을 활용하여 사용자가 시작한 요청에 응답하면서 네트워크 사용에 영향을 미치지 않습니다
사용자 요청 제한
현재 데이터가 아직 최신 상태일 때 새 데이터를 확인하기 위해 단기간에 여러 번 풀-투-리프레시 동작을 실행하는 등 필요하지 않은 일부 사용자 시작 요청은 무시하는 것이 좋습니다. 각각에 대해 조치 취하기 라디오를 켜진 상태로 유지하면 상당한 양의 전력이 낭비될 수 있습니다. 더 효율적인 접근 방식은 사용자 시작 요청을 제한하여 일정 기간 동안 하나의 요청만 할 수 있도록 하여 라디오 사용 빈도를 줄이는 것입니다.
캐시 사용
앱 데이터를 캐시하면 정보의 로컬 사본이 생성됩니다. 참조해야 합니다. 그러면 앱은 새 요청을 위해 네트워크 연결을 열지 않고도 동일한 정보의 로컬 사본에 여러 번 액세스할 수 있습니다.
정적 계층을 포함하여 가능한 한 적극적으로 데이터를 캐시해야 함 리소스 및 주문형 다운로드(예: 원본 크기 이미지)를 제공합니다. HTTP를 사용하여 캐싱 전략으로 인해 앱이 발생하지 않도록 하기 위한 캐시 헤더 표시합니다. 네트워크 응답 캐싱에 관한 자세한 내용은 중복 다운로드 방지를 참고하세요.
Android 11 이상에서는 앱이 머신러닝 및 미디어 재생과 같은 사용 사례에 다른 앱에서 사용하는 것과 동일한 대규모 데이터 세트를 사용할 수 있습니다. 앱이 공유 데이터 세트에 액세스해야 하는 경우 먼저 캐시된 버전을 확인할 수 있음 새 사본을 다운로드할 수 있습니다. 공유 데이터 세트에 대해 자세히 알아보려면 공유 데이터 세트 액세스를 참조하세요.
많은 데이터를 낮은 빈도로 다운로드하기 위해 큰 대역폭 사용
무선 통신을 연결하는 경우 일반적으로 더 높은 대역폭을 사용하면 더 큰 배터리 비용이 듭니다. 즉, 5G는 일반적으로 LTE보다 더 많은 에너지를 소비하고 3G보다 더 큰 비용이 듭니다.
즉, 기본 무선 상태는 무선 통신 기술에 따라 달라지지만 일반적으로 상태 변경 테일-타임에 따른 배터리 영향은 더 높은 대역폭의 무선 통신에서 더 큽니다. 자세한 내용은 무선 상태 머신의 한 가지 변형으로 나뉩니다.
동시에 대역폭이 높다는 것은 더 많은 데이터를 미리 가져와서 같은 시간 동안 더 많이 다운로드할 수 있음을 의미합니다. 또한 직관적이지는 않지만, 테일-타임 배터리 비용이 상대적으로 높기 때문에 업데이트 빈도를 줄이기 위해 각 전송 세션 동안 무선 통신을 더 장시간 활성 상태로 유지하는 것이 더 효율적입니다.
예를 들어 LTE 무선 통신이 대역폭과 에너지 비용은 두 배라면 각 세션 동안 네 배 더 많은 데이터를 다운로드해야 합니다. 잠재적으로 10MB에 이르렀습니다 이렇게 많은 데이터를 다운로드할 때는 미리 가져오기가 사용 가능한 로컬 저장소에 미치는 영향을 고려하고 프리페치 캐시를 정기적으로 확인하세요
ConnectivityManager
를 사용하여 기본 네트워크의 리스너를 등록하고 TelephonyManager
를 사용하여 PhoneStateListener
를 등록하여 현재 기기 연결 유형을 확인할 수 있습니다. 연결 유형을 알면 그에 따라 미리 가져오기 루틴을 수정할 수 있습니다.
Kotlin
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val tm = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager private var hasWifi = false private var hasCellular = false private var cellModifier: Float = 1f private val networkCallback = object : ConnectivityManager.NetworkCallback() { // Network capabilities have changed for the network override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities ) { super.onCapabilitiesChanged(network, networkCapabilities) hasCellular = networkCapabilities .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) hasWifi = networkCapabilities .hasTransport(NetworkCapabilities.TRANSPORT_WIFI) } } private val phoneStateListener = object : PhoneStateListener() { override fun onPreciseDataConnectionStateChanged( dataConnectionState: PreciseDataConnectionState ) { cellModifier = when (dataConnectionState.networkType) { TelephonyManager.NETWORK_TYPE_LTE or TelephonyManager.NETWORK_TYPE_HSPAP -> 4f TelephonyManager.NETWORK_TYPE_EDGE or TelephonyManager.NETWORK_TYPE_GPRS -> 1/2f else -> 1f } } private class NetworkState { private var defaultNetwork: Network? = null private var defaultCapabilities: NetworkCapabilities? = null fun setDefaultNetwork(network: Network?, caps: NetworkCapabilities?) = synchronized(this) { defaultNetwork = network defaultCapabilities = caps } val isDefaultNetworkWifi get() = synchronized(this) { defaultCapabilities?.hasTransport(TRANSPORT_WIFI) ?: false } val isDefaultNetworkCellular get() = synchronized(this) { defaultCapabilities?.hasTransport(TRANSPORT_CELLULAR) ?: false } val isDefaultNetworkUnmetered get() = synchronized(this) { defaultCapabilities?.hasCapability(NET_CAPABILITY_NOT_METERED) ?: false } var cellNetworkType: Int = TelephonyManager.NETWORK_TYPE_UNKNOWN get() = synchronized(this) { field } set(t) = synchronized(this) { field = t } private val cellModifier: Float get() = synchronized(this) { when (cellNetworkType) { TelephonyManager.NETWORK_TYPE_LTE or TelephonyManager.NETWORK_TYPE_HSPAP -> 4f TelephonyManager.NETWORK_TYPE_EDGE or TelephonyManager.NETWORK_TYPE_GPRS -> 1 / 2f else -> 1f } } val prefetchCacheSize: Int get() = when { isDefaultNetworkWifi -> MAX_PREFETCH_CACHE isDefaultNetworkCellular -> (DEFAULT_PREFETCH_CACHE * cellModifier).toInt() else -> DEFAULT_PREFETCH_CACHE } } private val networkState = NetworkState() private val networkCallback = object : ConnectivityManager.NetworkCallback() { // Network capabilities have changed for the network override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities ) { networkState.setDefaultNetwork(network, networkCapabilities) } override fun onLost(network: Network?) { networkState.setDefaultNetwork(null, null) } } private val telephonyCallback = object : TelephonyCallback(), TelephonyCallback.PreciseDataConnectionStateListener { override fun onPreciseDataConnectionStateChanged(dataConnectionState: PreciseDataConnectionState) { networkState.cellNetworkType = dataConnectionState.networkType } } connectivityManager.registerDefaultNetworkCallback(networkCallback) telephonyManager.registerTelephonyCallback(telephonyCallback) private val prefetchCacheSize: Int get() { return when { hasWifi -> MAX_PREFETCH_CACHE hasCellular -> (DEFAULT_PREFETCH_CACHE * cellModifier).toInt() else -> DEFAULT_PREFETCH_CACHE } } }
자바
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); private boolean hasWifi = false; private boolean hasCellular = false; private float cellModifier = 1f; private ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onCapabilitiesChanged( @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities ) { super.onCapabilitiesChanged(network, networkCapabilities); hasCellular = networkCapabilities .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); hasWifi = networkCapabilities .hasTransport(NetworkCapabilities.TRANSPORT_WIFI); } }; private PhoneStateListener phoneStateListener = new PhoneStateListener() { @Override public void onPreciseDataConnectionStateChanged( @NonNull PreciseDataConnectionState dataConnectionState ) { switch (dataConnectionState.getNetworkType()) { case (TelephonyManager.NETWORK_TYPE_LTE | TelephonyManager.NETWORK_TYPE_HSPAP): cellModifier = 4; Break; case (TelephonyManager.NETWORK_TYPE_EDGE | TelephonyManager.NETWORK_TYPE_GPRS): cellModifier = 1/2.0f; Break; default: cellModifier = 1; Break; } } }; cm.registerDefaultNetworkCallback(networkCallback); tm.listen( phoneStateListener, PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE ); public int getPrefetchCacheSize() { if (hasWifi) { return MAX_PREFETCH_SIZE; } if (hasCellular) { return (int) (DEFAULT_PREFETCH_SIZE * cellModifier); } return DEFAULT_PREFETCH_SIZE; }
앱에서 시작한 요청 최적화
앱에서 시작한 요청은 일반적으로 일정에 따라 발생합니다. 예를 들어 백엔드 서비스에 로그 또는 분석을 추가할 수 있습니다 앱에서 시작한 요청을 처리할 때는 요청의 우선순위, 요청을 일괄 처리할 수 있는지 여부, 기기가 충전 중이거나 무제한 네트워크에 연결될 때까지 지연할 수 있는지 여부를 고려하세요. 이러한 요청은 신중하게 최적화될 수 있습니다 예를 들면 다음과 같은 라이브러리를 사용하여 WorkManager:
네트워크 일괄 요청
휴대기기에서는 무선 기능을 사용 설정하고, 연결하고, 무선 기능을 켜진 상태로 유지하는 과정에서 많은 전력을 사용하게 됩니다. 따라서 개별 요청을 무작위로 처리할 때는 상당한 전력이 소모될 수 있습니다. 배터리 수명도 단축됩니다. 보다 효율적인 접근 방식은 네트워크 집합을 대기열에 추가하는 것입니다. 요청을 함께 처리하고 이를 통해 시스템에서는 무선 기능을 사용 설정할 때 드는 전력 비용을 한 번 지불하는 것으로도 앱에서 요청한 모든 데이터를 받을 수 있습니다.
WorkManager 사용
WorkManager
라이브러리를 사용하여 효율적인 일정에 따라 작업을 실행할 수 있습니다.
네트워크 가용성과 같은 특정 조건 충족 여부를 고려합니다.
전원 상태를 확인할 수 있습니다. 예를 들어 최신 뉴스 헤드라인을 가져오는 DownloadHeadlinesWorker
라는 Worker
서브클래스가 있다고 가정해 보겠습니다. 이 작업자
매시간 실행되도록 예약할 수 있습니다(단, 기기가 네트워크에 연결되어 있는 경우).
무제한 네트워크와 기기의 배터리가 부족하지 않으며, 맞춤 재시도 전략을 사용합니다.
데이터를 가져오는 데 문제가 있을 경우 아래와 같이 조치합니다.
Kotlin
val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresBatteryNotLow(true) .build() val request = PeriodicWorkRequestBuilder<DownloadHeadlinesWorker>(1, TimeUnit.HOURS) .setConstraints(constraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1L, TimeUnit.MINUTES) .build() WorkManager.getInstance(context).enqueue(request)
자바
Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) .setRequiresBatteryNotLow(true) .build(); WorkRequest request = new PeriodicWorkRequest.Builder(DownloadHeadlinesWorker.class, 1, TimeUnit.HOURS) .setBackoffCriteria(BackoffPolicy.LINEAR, 1L, TimeUnit.MINUTES) .build(); WorkManager.getInstance(this).enqueue(request);
Android 플랫폼은 WorkManager 외에도 여러 가지 다른 도구를 제공합니다. 네트워킹 작업 완료를 위한 효율적인 일정을 만드는 데 선택할 수 있습니다 이러한 도구의 사용법을 자세히 알아보려면 백그라운드 처리 가이드를 참고하세요.
서버에서 시작한 요청 최적화
서버에서 시작된 요청은 일반적으로 있습니다. 예를 들어 최신 뉴스 기사를 읽는 데 사용되는 앱은 사용자의 맞춤설정 환경설정에 맞는 새로운 기사 모음에 관한 알림을 수신한 후 이를 다운로드할 수 있습니다.
Firebase 클라우드 메시징으로 서버 업데이트 전송
Firebase 클라우드 메시징(FCM)은 서버에서 특정 앱 인스턴스로 데이터를 전송하는 데 사용되는 간단한 메커니즘입니다. FCM을 사용하면 서버가 특정 기기에서 실행 중인 앱에 새 데이터가 있는지 확인할 수 있습니다.
앱이 정기적으로 서버에 핑하여 다음을 쿼리해야 하는 폴링과 비교됩니다. 이 이벤트 기반 모델을 통해 앱에서 새 연결을 만들 수 있습니다. 다운로드할 데이터가 있는 경우에만 작동합니다. 이 모델은 앱의 정보를 업데이트할 때 불필요한 연결을 최소화하고 지연 시간을 줄여줍니다.
FCM은 지속적인 TCP/IP 연결을 사용하여 구현합니다. 이를 통해 지속적 연결 수를 최소화하고 플랫폼에서 대역폭을 최적화하고 배터리 수명에 미치는 영향을 최소화할 수 있습니다.