尽量减少定期更新的影响

您的应用向网络发出的请求是耗电的主要原因 因为它们会开启比较耗电的移动网络或 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 库高效地执行工作 会考虑是否满足特定条件,例如网络可用性 和电源状态。例如,假设您有一个 调用了 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 Cloud Messaging 发送服务器更新

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

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

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