تقليل تأثير التحديثات المنتظمة

تُعد الطلبات التي يرسلها تطبيقك إلى الشبكة من الأسباب الرئيسية لاستنفاد البطارية لأنّها تشغّل أجهزة خلوية أو لاسلكية Wi-Fi تستهلك الطاقة. بخلاف الطاقة اللازمة لإرسال الحزم وتلقيها، تستهلك هذه الأجهزة اللاسلكية المزيد من الطاقة فقط عند تشغيلها وإبقائها في حالة يقظة. يمكن أن يؤدي شيء بسيط مثل طلب الشبكة كل 15 ثانية إلى إبقاء الراديو على الأجهزة الجوّالة قيد التشغيل بشكل مستمر وسريع.

هناك ثلاثة أنواع عامة من التحديثات المنتظمة:

  • يبدأه المستخدم: إجراء تحديث بناءً على سلوك المستخدم، مثل إيماءة السحب للتحديث.
  • الذي يبدأه التطبيق: إجراء تحديث بشكل متكرر.
  • إجراء يتم تشغيله بواسطة الخادم: إجراء تحديث استجابة لإشعار من الخادم.

ويلقي هذا الموضوع نظرة على كل من هذه العناصر ويناقش طرقًا إضافية لتحسينها للحدّ من استنزاف البطارية.

تحسين الطلبات التي يبدأها المستخدم

عادةً ما تحدث الطلبات التي يبدأها المستخدم استجابةً لبعض سلوك المستخدم. على سبيل المثال، قد يسمح التطبيق المستخدم لقراءة آخر المقالات الإخبارية للمستخدم بتنفيذ إيماءة السحب لإعادة التحميل للبحث عن مقالات جديدة. يمكنك استخدام الأساليب التالية للاستجابة للطلبات التي يجريها المستخدم أثناء تحسين استخدام الشبكة.

تقييد طلبات المستخدمين

يمكنك تجاهل بعض الطلبات التي أرسلها المستخدم إذا لم تكن هناك حاجة إليها، مثل إيماءات السحب لإعادة التحميل المتعددة خلال فترة زمنية قصيرة للتحقق من وجود بيانات جديدة عندما تكون البيانات الحالية لا تزال حديثة. فإن التصرف بناءً على كل طلب قد يؤدي إلى إهدار كمية كبيرة من الطاقة عن طريق إبقاء الراديو نشطًا. وهناك منهج أكثر كفاءة يتمثل في تقييد الطلبات التي يبدأها المستخدم بحيث يمكن إجراء طلب واحد فقط خلال فترة زمنية، ما يقلل من مرات استخدام الراديو.

استخدام ذاكرة تخزين مؤقت

من خلال التخزين المؤقت لبيانات تطبيقك، فإنك تنشئ نسخة محلية من المعلومات التي يحتاج التطبيق إلى الرجوع إليها. ويمكن لتطبيقك بعد ذلك الوصول إلى النسخة المحلية نفسها من المعلومات عدة مرات بدون الحاجة إلى فتح اتصال شبكة لإجراء طلبات جديدة.

يجب عليك تخزين البيانات مؤقتًا بأكبر قدر ممكن من القوة، بما في ذلك الموارد الثابتة والتنزيلات عند الطلب مثل الصور بالحجم الكامل. ويمكنك استخدام عناوين HTTP في ذاكرة التخزين المؤقت لضمان ألا تؤدي استراتيجية التخزين المؤقت إلى عرض تطبيقك لبيانات قديمة. لمزيد من المعلومات حول التخزين المؤقت لاستجابات الشبكة، يمكنك الاطّلاع على تجنُّب عمليات التنزيل المتكرّرة.

على نظام التشغيل Android 11 والإصدارات الأحدث، يمكن لتطبيقك استخدام مجموعات البيانات الكبيرة نفسها التي تستخدمها التطبيقات الأخرى لحالات الاستخدام، مثل تعلُّم الآلة وتشغيل الوسائط. عندما يحتاج التطبيق إلى الوصول إلى مجموعة بيانات مشتركة، يمكنه أولاً التحقق من وجود نسخة مخزَّنة مؤقتًا قبل محاولة تنزيل نسخة جديدة. لمعرفة المزيد حول مجموعات البيانات المشتركة، راجع الوصول إلى مجموعات البيانات المشتركة.

استخدام نطاق ترددي أكبر لتنزيل مزيد من البيانات بمعدّل أقل

عند الاتصال عبر لاسلكي لاسلكي، يكون معدل نقل البيانات أعلى بشكل عام مقابل تكلفة أعلى للبطارية، ما يعني أنّ شبكة الجيل الخامس تستهلك عادةً طاقة أكثر من شبكة LTE التي تكون بدورها أعلى تكلفة من شبكة الجيل الثالث.

وهذا يعني أنّه على الرغم من اختلاف حالة الراديو الأساسية بناءً على تكنولوجيا الراديو، فإنّ التأثير النسبي للبطارية عند تغيير وقت ذيل الحالة يكون أكبر في الأجهزة اللاسلكية ذات معدّل نقل البيانات الأعلى بشكل عام. لمزيد من المعلومات عن وقت التحميل، راجِع الجهاز حالة الراديو.

في الوقت نفسه، يعني ارتفاع معدّل نقل البيانات أنّه يمكنك الجلب المُسبق بشكل أكبر، وتنزيل المزيد من البيانات في الوقت نفسه. ربما يكون من غير المنطق أن تكون تكلفة بطارية وقت الرد أعلى نسبيًا، ولذلك من الأفضل أيضًا إبقاء الاتصال اللاسلكي نشطًا لفترات أطول أثناء كل جلسة نقل لتقليل معدّل تكرار التحديثات.

على سبيل المثال، إذا ازداد معدّل نقل البيانات في جهاز لاسلكي LTE وضعف معدّل نقل البيانات وضِعف تكلفة الطاقة لشبكة الجيل الثالث، يجب تنزيل بيانات أكبر بأربع مرات خلال كل جلسة، أو إلى حجم بيانات قد يصل إلى 10 ميغابايت. عند تنزيل هذا القدر الكبير من البيانات، من المهم مراعاة تأثير الجلب المُسبَق على مساحة التخزين المحلية المتاحة ومحو ذاكرة التخزين المؤقت للجلب المُسبَق بانتظام.

يمكنك استخدام 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"

تُعد المراسلة عبر السحابة الإلكترونية من Firebase (FCM) آلية بسيطة تُستخدم لنقل البيانات من خادم إلى مثيل تطبيق معيّن. باستخدام خدمة "المراسلة عبر السحابة الإلكترونية من Firebase"، يمكن للخادم إرسال إشعار إلى تطبيقك الذي يعمل على جهاز معيّن بتوفّر بيانات جديدة له.

بالمقارنة مع الاستطلاعات التي يجب فيها على تطبيقك فحص اتصال الخادم بانتظام للاستعلام عن بيانات جديدة، يسمح هذا النموذج المستند إلى الأحداث لتطبيقك بإنشاء اتصال جديد فقط عندما يعرف أنّ هناك بيانات لتنزيلها. يقلل النموذج من الاتصالات غير الضرورية، ويقلل من وقت الاستجابة عند تحديث المعلومات داخل تطبيقك.

يتم تنفيذ FCM باستخدام اتصال TCP/IP دائم. ويقلل ذلك من عدد الاتصالات المستمرة ويتيح للنظام الأساسي تحسين معدل نقل البيانات وتقليل التأثير المرتبط به على عمر البطارية.