將定期更新的影響降至最低

應用程式對網路發出的要求是造成電池耗電的主要原因 原因是他們啟用了耗用大量電力的行動網路或 Wi-Fi 無線電。力量大 傳送及接收封包所需的額外電力 打開並且保持啟用。只要每 15 秒就發出一次網路要求,行動無線電就會持續開啟,並快速耗盡電池電力。

定期更新分為三種類型:

  • 由使用者啟動。根據某些使用者行為執行更新,例如拉動手勢。
  • 應用程式啟動。定期更新。
  • 伺服器啟動。針對伺服器通知執行更新。

本主題會逐一探討這些因素,並討論可行的 且經過最佳化調整,可降低電池耗電量

針對使用者發出的要求進行最佳化

使用者發起的要求通常發生於回應某些使用者行為。適用對象 舉例來說,使用者用應用程式可以閱讀最新新聞報導 執行下拉重新整理手勢來查看新文章。您可以使用 運用技巧來回應使用者啟動的要求,同時進行最佳化 以及網路用量

限制使用者要求

如果不需要使用者發出的部分要求,建議您直接忽略 例如在短時間內使用多個下拉即可重新整理手勢 在目前資料仍為最新狀態的情況下檢查新資料。逐一採取行動 要求可能使無線電保持喚醒狀態,從而浪費大量電力。A 罩杯 更有效率的做法就是調節使用者發出的要求 但在這段時間內只能提出一項要求 收音機。

使用快取

藉由快取應用程式資料,您會建立資訊的本機副本 應用程式需要參照的資料這樣一來,應用程式就能多次存取相同的本機資訊副本,而無須開啟網路連線來提出新要求。

建議您盡可能積極快取資料,包括靜態 資源和隨選下載,例如原尺寸圖片您可以使用 HTTP 快取標頭,確保快取策略不會導致應用程式 顯示過時資料如要進一步瞭解如何快取網路回應,請參閱「避免下載重複內容」。

在 Android 11 以上版本中,您的應用程式可以使用其他應用程式用於機器學習和媒體播放等用途的大型資料集。當您 應用程式需要存取共用資料集,因此可以先檢查快取版本 再嘗試下載新副本如要進一步瞭解共用資料集,請參閱「存取共用資料集」。

使用較高的頻寬降低下載頻率

使用無線無線電連線時,頻寬通常較高; 但 5G 網路的耗電量通常較高。 相較於 LTE,使用成本比 3G 來得高。

這表示雖然基礎無線電狀態會隨著 無線電技術一般來說是 變更 tail-time 可放大頻寬較高的無線電時間如要進一步瞭解 請參閱無線電狀態 機器

但由於頻寬較高,您可以預先擷取更多 逐步下載更多資料。或許不太直覺,由於尾端時間的電池成本相對較高,因此在每次傳輸工作階段中,讓無線電保持較長時間的活動狀態,以減少更新頻率,也能提高效率。

舉例來說,如果 LTE 無線電的頻寬和耗電量是 3G 的兩倍,則您在每個工作階段下載的資料量應為 4 倍,也就是最多 10 MB。下載這麼多資料時 請考量預先擷取功能對可用本機儲存空間的影響 預先擷取快取

您可以使用 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
    }
}

}

Java

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 程式庫,依照有效率的時間表執行工作 來考量是否符合特定條件,例如網路可用性 和電源狀態舉例來說,假設您有一個名為 DownloadHeadlinesWorkerWorker 子類別,用於擷取最新的新聞標題。這個工作站 可以排定每小時執行一次 採用自訂重試策略,非計量付費網路和裝置電力充足 表示資料擷取時遇到任何問題,如下所示:

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)

Java

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);

除了 WorkManager,Android 平台也提供其他幾種工具,協助您建立有效的時間表,以便完成網路作業 (例如輪詢)。如要進一步瞭解如何使用這些工具,請參閱 背景處理作業指南

針對伺服器發出的要求進行最佳化

伺服器發出的要求通常用於回應來自 伺服器舉例來說,用來閱讀最新新聞報導的應用程式可能會收到 一則新的一批報導符合使用者的 個人化偏好設定,隨後就會下載。

使用 Firebase 雲端通訊傳送伺服器更新

Firebase 雲端通訊 (FCM) 是輕量化的 用於將資料從伺服器傳輸至特定應用程式執行個體。 使用 FCM 時,伺服器可通知執行中的應用程式在特定裝置上: 有一個可用的新資料

相較於輪詢,應用程式必須定期連線偵測伺服器才能查詢 這個事件導向模式可讓應用程式建立新連結 除非它知道有可下載的資料。模型盡可能減少不必要的 。

FCM 是透過永久 TCP/IP 連線實作。這可將 並讓平台將頻寬最佳化 並將對電池續航力的影響降至最低。