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

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

一般更新分為三種類型:

  • 使用者啟動。根據部分使用者行為執行更新,例如 並做出下拉重新整理手勢
  • 應用程式啟動。定期更新。
  • 伺服器啟動。執行更新以回應來自 的通知 伺服器

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

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

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

限制使用者要求

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

使用快取

藉由快取應用程式資料,您會建立資訊的本機副本 應用程式需要參照的資料這樣一來,您的應用程式就能存取相同的本機儲存空間 多次複製資訊,而不必開啟網路 連結,以提出新的要求

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

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

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

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

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

但由於頻寬較高,您可以預先擷取更多 逐步下載更多資料。可能較少 直覺化,由於尾部電池成本相對較高,因此 更有效率,在每次傳輸期間讓電台保持使用中的時間 」工作階段來減少更新頻率。

舉例來說,如果 LTE 無線電的頻寬是兩倍,能源費用也翻倍 建議你在每個工作階段期間的下載量是 3G 的四倍,或者 最多可達 10 MB下載這麼多資料時 請考量預先擷取功能對可用本機儲存空間的影響 預先擷取快取

您可以使用 需登記 ConnectivityManager 預設網路的接聽程式 待報名的 TelephonyManagerPhoneStateListener 至 判斷目前的裝置連線類型。得知連線類型後 您可以據此修改預先擷取處理常式:

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

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 連線實作。這可將 並讓平台將頻寬最佳化 並將對電池續航力的影響降至最低。