به‌حداقل رساندن تأثیر به‌روزرسانی‌های منظم

درخواست‌هایی که برنامه‌تان از شبکه می‌کند دلیل اصلی خالی شدن باتری است چون این درخواست‌ها رادیوهای Wi-Fi یا تلفن همراه را که مصرف برق دارند روشن می‌کنند. فراتر از توان موردنیاز برای ارسال و دریافت بسته‌ها، این رادیوها توان اضافی را فقط برای روشن شدن و بیدار ماندن مصرف می‌کنند. چیزی به سادگی درخواست شبکه هر ۱۵ ثانیه یک‌بار می‌تواند رادیو تلفن همراه را به‌طور مداوم روشن نگه دارد و به‌سرعت شارژ باتری را مصرف کند.

سه نوع کلی به‌روزرسانی منظم وجود دارد:

  • آغازشده ازسوی کاربر. انجام به‌روزرسانی براساس برخی رفتارهای کاربر، مانند اشاره کشیدن برای بازآوری.
  • شروع‌شده توسط برنامه. انجام به‌روزرسانی به‌صورت تکرارشونده.
  • سرور آغاز کرده است. انجام به‌روزرسانی در پاسخ به اعلان از سرور.

این موضوع به هریک از این موارد می‌پردازد و روش‌های دیگری را برای بهینه‌سازی آن‌ها جهت کاهش مصرف باتری مورد بحث قرار می‌دهد.

بهینه‌سازی درخواست‌های آغازشده توسط کاربر

درخواست‌های آغازشده توسط کاربر معمولاً در پاسخ به برخی رفتارهای کاربر رخ می‌دهد. برای مثال، برنامه‌ای که برای خواندن جدیدترین مقاله‌های خبری استفاده می‌شود ممکن است به کاربر اجازه دهد با انجام اشاره کشیدن برای بازآوری، مقاله‌های جدید را بررسی کند. می‌توانید از روش‌های زیر برای پاسخ دادن به درخواست‌های آغازشده توسط کاربر و هم‌زمان بهینه‌سازی استفاده از شبکه استفاده کنید.

محدود کردن درخواست‌های کاربر

ممکن است بخواهید برخی‌از درخواست‌های آغازشده توسط کاربر را نادیده بگیرید اگر نیازی به آن‌ها نباشد، مانند چندین اشاره کشیدن برای بازآوری در یک دوره زمانی کوتاه برای بررسی داده‌های جدید درحالی‌که داده‌های فعلی هنوز تازه هستند. اقدام کردن برای هر درخواست می‌تواند با بیدار نگه داشتن رادیو، مقدار قابل‌توجهی از شارژ باتری را هدر دهد. رویکرد کارآمدتر این است که درخواست‌های آغازشده توسط کاربر را محدود کنید تا فقط یک درخواست در یک دوره زمانی انجام شود، و تعداد دفعات استفاده از رادیو کاهش یابد.

استفاده از حافظه نهان

با ذخیره کردن داده‌های برنامه‌تان در حافظه نهان، نسخه‌ای محلی از اطلاعاتی ایجاد می‌کنید که برنامه‌تان باید به آن ارجاع دهد. سپس برنامه‌تان می‌تواند چندین بار به همان نسخه محلی اطلاعات دسترسی پیدا کند، بدون اینکه لازم باشد اتصال شبکه را برای ایجاد درخواست‌های جدید باز کند.

باید داده‌ها را تا حد امکان به‌صورت تهاجمی ذخیره کنید، ازجمله منابع ثابت و بارگیری‌های درخواستی مانند تصاویر با اندازه کامل. می‌توانید از سرصفحه‌های حافظه نهان HTTP استفاده کنید تا مطمئن شوید که استراتژی ذخیره‌سازی در حافظه نهان شما منجر به نمایش داده‌های قدیمی در برنامه‌تان نمی‌شود. برای اطلاعات بیشتر درباره ذخیره پاسخ‌های شبکه در حافظه نهان، به اجتناب از بارگیری‌های زائد مراجعه کنید.

در Android نسخه ۱۱ و بالاتر، برنامه‌تان می‌تواند از همان مجموعه داده‌های بزرگی استفاده کند که برنامه‌های دیگر برای موارد استفاده‌ای مثل یادگیری ماشین و بازپخش رسانه استفاده می‌کنند. وقتی برنامه شما نیاز به دسترسی به مجموعه داده هم‌رسانی‌شده دارد، می‌تواند ابتدا نسخه ذخیره‌شده در حافظه نهان را بررسی کند و سپس نسخه جدید را بارگیری کند. برای کسب اطلاعات بیشتر درباره مجموعه‌داده‌های هم‌رسانی‌شده، دسترسی به مجموعه‌داده‌های هم‌رسانی‌شده را ببینید.

استفاده از پهنای باند بیشتر برای بارگیری داده‌های بیشتر با دفعات کمتر

وقتی ازطریق رادیو بی‌سیم متصل می‌شوید، پهنای باند بالاتر معمولاً به قیمت مصرف بیشتر باتری تمام می‌شود، یعنی 5G معمولاً انرژی بیشتری نسبت‌به LTE مصرف می‌کند که آن هم به‌نوبه خود گران‌تر از 3G است.

این یعنی درحالی‌که وضعیت رادیویی زیربنایی براساس فناوری رادیویی متفاوت است، به‌طور کلی تأثیر نسبی باتری زمان دنباله تغییر وضعیت برای رادیوهای با پهنای باند بالاتر بیشتر است. برای اطلاعات بیشتر درباره زمان دنباله، به ماشین حالت رادیو مراجعه کنید.

درعین‌حال، پهنای باند بالاتر به این معنی است که می‌توانید پیش‌واکشی را با شدت بیشتری انجام دهید و داده‌های بیشتری را در همان زمان بارگیری کنید. شاید کمتر بدیهی باشد، اما چون هزینه باتری زمان دنباله نسبتاً بالاتر است، کارآمدتر است که رادیو را برای دوره‌های طولانی‌تر درطول هر جلسه انتقال فعال نگه دارید تا دفعات به‌روزرسانی کاهش یابد.

برای مثال، اگر رادیو LTE دو برابر پهنای باند و دو برابر هزینه انرژی 3G داشته باشد، باید چهار برابر بیشتر داده درطول هر جلسه بارگیری کنید—یا احتمالاً تا ۱۰ مگابایت. هنگام بارگیری این حجم از داده، مهم است که تأثیر واکشی پیشاپیش را بر فضای ذخیره‌سازی محلی دردسترس درنظر بگیرید و حافظه پنهان واکشی پیشاپیش را به‌طور منظم پاک کنید.

می‌توانید از ConnectivityManager برای ثبت شنونده برای شبکه پیش‌فرض، و از TelephonyManager برای ثبت PhoneStateListener برای تعیین نوع اتصال فعلی دستگاه استفاده کنید. وقتی نوع اتصال مشخص شد، می‌توانید روال‌های پیش‌واکشی را براساس آن اصلاح کنید:

کاتلین

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 برای انجام کار در زمان‌بندی کارآمدی استفاده کنید که برآورده شدن شرایط خاصی مثل دردسترس بودن شبکه و وضعیت برق را درنظر می‌گیرد. برای مثال، فرض کنید یک زیرکلاس Worker به نام DownloadHeadlinesWorker دارید که جدیدترین عناوین خبری را بازیابی می‌کند. این کارگر می‌تواند برای اجرا در هر ساعت زمان‌بندی شود، مشروط بر اینکه دستگاه به یک شبکه بدون محدودیت متصل باشد و باتری دستگاه کم نباشد، با یک استراتژی تلاش مجدد سفارشی در صورت وجود هرگونه مشکل در بازیابی داده‌ها، همان‌طور که در زیر نشان داده شده است:

کاتلین

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

علاوه‌بر WorkManager، پلاتفرم Android چندین ابزار دیگر نیز ارائه می‌دهد تا به شما کمک کند زمان‌بندی کارآمدی برای تکمیل تکالیف شبکه‌ای، مانند نظرسنجی، ایجاد کنید. برای کسب اطلاعات بیشتر درباره استفاده از این ابزارها، راهنمای پردازش پس‌زمینه‌ای را ببینید.

بهینه‌سازی درخواست‌های آغازشده ازسوی سرور

درخواست‌های آغازشده از سرور معمولاً در پاسخ به اعلانی از سرور رخ می‌دهند. برای مثال، برنامه‌ای که برای خواندن جدیدترین مقاله‌های خبری استفاده می‌شود ممکن است اعلانی درباره مجموعه جدیدی از مقاله‌ها دریافت کند که با اولویت‌های شخصی‌سازی کاربر مطابقت دارد، و سپس آن را بارگیری کند.

ارسال به‌روزرسانی‌های سرور با «پیام‌رسانی ابریِ Firebase»

پیام‌رسانی ابری Firebase (FCM) سازوکاری سبک است که برای انتقال داده از سرور به نمونه برنامه خاصی استفاده می‌شود. بااستفاده از FCM، سرور شما می‌تواند به برنامه‌تان که در دستگاه خاصی اجرا می‌شود اطلاع دهد که داده‌های جدیدی برای آن دردسترس است.

درمقایسه با نظرسنجی که در آن برنامه شما باید به‌طور منظم به سرور پینگ کند تا داده‌های جدید را جستجو کند، این مدل رویدادمحور به برنامه شما اجازه می‌دهد فقط زمانی اتصال جدید ایجاد کند که می‌داند داده‌ای برای بارگیری وجود دارد. این مدل اتصالات غیرضروری را به‌حداقل می‌رساند و تأخیر را هنگام به‌روزرسانی اطلاعات در برنامه‌تان کاهش می‌دهد.

FCM بااستفاده از اتصال TCP/IP پایدار پیاده‌سازی می‌شود. این کار تعداد اتصالات مداوم را به‌حداقل می‌رساند و به پلاتفرم اجازه می‌دهد پهنای باند را بهینه‌سازی کند و تأثیر مرتبط بر عمر باتری را به‌حداقل برساند.