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

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

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

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

این مبحث به بررسی هر یک از این موارد می‌پردازد و روش‌های اضافی بهینه‌سازی آنها برای کاهش تخلیه باتری را مورد بحث قرار می‌دهد.

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

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

درخواست‌های کاربران دریچه گاز

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

از حافظه پنهان (cache) استفاده کنید

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

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

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

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

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

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

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

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

شما می‌توانید از ConnectivityManager برای ثبت یک شنونده (listener) برای شبکه پیش‌فرض و از 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;
}

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

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

درخواست‌های شبکه دسته‌ای

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

از WorkManager استفاده کنید

شما می‌توانید از کتابخانه WorkManager برای انجام کار بر اساس یک برنامه زمانی کارآمد استفاده کنید که در آن شرایط خاص، مانند دسترسی به شبکه و وضعیت برق، در نظر گرفته می‌شود. برای مثال، فرض کنید یک زیرکلاس Worker به نام DownloadHeadlinesWorker دارید که آخرین عناوین خبری را بازیابی می‌کند. این Worker می‌تواند طوری برنامه‌ریزی شود که هر ساعت اجرا شود، مشروط بر اینکه دستگاه به یک شبکه بدون محدودیت زمانی متصل باشد و باتری دستگاه کم نباشد، و در صورت وجود هرگونه مشکل در بازیابی داده‌ها، یک استراتژی تلاش مجدد سفارشی، همانطور که در زیر نشان داده شده است، داشته باشد:

کاتلین

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، پلتفرم اندروید ابزارهای دیگری را نیز برای کمک به شما در ایجاد یک برنامه زمانی کارآمد برای انجام وظایف شبکه، مانند نظرسنجی، ارائه می‌دهد. برای کسب اطلاعات بیشتر در مورد استفاده از این ابزارها، به راهنمای پردازش پس‌زمینه مراجعه کنید.

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

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

ارسال به‌روزرسانی‌های سرور با Firebase Cloud Messaging

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

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

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