尽量减少定期更新的影响

您的应用向网络发出的请求是耗电的主要原因 因为它们会开启比较耗电的移动网络或 Wi-Fi 无线装置。强大功能 这些无线装置需要消耗额外的功率 开启并保持唤醒状态。例如每 15 次发送一次网络请求 电源。

常规更新有三种类型:

  • 由用户启动。根据某些用户行为(如 下拉刷新手势。
  • 由应用启动。定期执行更新。
  • 由服务器发起。执行更新以响应来自 的通知 服务器

本主题将介绍上述各项,并讨论可通过哪些其他方式对其进行优化以减少电池耗电量。

优化用户发起的请求

用户发起的请求通常是为了响应某些用户行为。对于 例如,用于阅读最新新闻报道的应用可能会允许用户 执行下拉刷新手势,以检查是否有新报道。您可以使用 以下技术响应用户发起的请求,同时优化 网络使用。

限制用户请求

如果不需要某些由用户发起的请求(例如在短时间内多次执行下拉刷新手势来检查是否有新数据,而当前数据仍很新鲜),您可能需要忽略这些请求。根据 请求可能会使无线装置保持唤醒状态,从而浪费大量电量。答 更高效的方法是限制用户发起的请求, 在一段时间内只能发出一个请求,这样可以降低 使用无线装置

使用缓存

通过缓存应用数据,您可以创建相关信息的本地副本 您的应用需要引用的一系列属性然后,您的应用就可以访问 无需打开网络即可多次复制信息 以便发出新请求

您应该尽可能积极地缓存数据,包括静态 资源和按需下载(如完整尺寸图片)。您可以使用 HTTP 缓存标头,以确保您的缓存策略不会使您的应用产生 显示过时数据如需详细了解如何缓存网络响应,请参阅 避免冗余的 下载

在 Android 11 及更高版本中,您的应用可以使用与其他应用相同的大型数据集 用于机器学习和媒体播放等用例的应用。当您 应用需要访问共享数据集时,可以先检查是否有缓存版本 然后再尝试下载新副本。如需详细了解共享数据集, 请参阅访问共享数据集

使用更高的带宽以更低的频率下载更多数据

通过无线装置连接时,带宽越高, 但电池成本较高,这意味着 5G 通常消耗的能源更多 而 LTE 的价格也比 3G 更昂贵

这意味着,虽然底层无线装置状态 无线电技术,一般来说就是州或省/直辖市/自治区对电池的相对影响 带宽越高,更改尾时间越长。如需详细了解 尾时间,请参阅无线装置状态 机器

同时,带宽越高, 同时下载更多数据。也许较少 直观地说,由于尾时间电池成本相对较高, 让无线装置在每次传输期间长时间保持活跃状态的效率更高 以降低更新频率。

例如,如果 LTE 无线装置的带宽和能源成本都翻了一番, 则应该在每次使用 3G 网络时下载四倍于相同的数据,或者 大小可能高达 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
    }
}

}

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 子类,用于检索最新的新闻头条。此 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)

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 Cloud Messaging 发送服务器更新

Firebase 云消息传递 (FCM) 是一种轻量级机制,用于将数据从服务器传输到特定应用实例。借助 FCM,您的服务器可以通知您在特定设备上运行的应用有新数据可用。

与轮询不同,在轮询中,您的应用必须定期 ping 服务器才能查询 这种事件驱动型模型可让您的应用创建新的连接, 。模型会尽可能减少 在更新应用内信息时缩短延迟时间。

FCM 通过持久性 TCP/IP 连接实现。这样可以尽可能减少 持久连接的数量,并使平台能够优化带宽 并最大限度地减少对电池续航时间的影响。