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

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

تتوفر ثلاثة أنواع عامة من التحديثات المنتظمة:

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

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

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

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

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

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

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

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

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

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

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

عند الاتصال بجهاز لاسلكي لاسلكي، يرتفع معدّل نقل البيانات بشكل عام بسعر أعلى للبطارية، ما يعني أنّ شبكة الجيل الخامس (5G) تستهلك عادةً المزيد من الطاقة مقارنةً بـ 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"، يمكن للخادم إرسال إشعار إلى تطبيقك قيد التشغيل على جهاز معيّن بتوفُّر بيانات جديدة له.

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

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