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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

على سبيل المثال، إذا كان راديو LTE يستهلك ضعف النطاق الترددي وضعف الطاقة مقارنةً بشبكة الجيل الثالث (3G)، عليك تنزيل أربعة أضعاف البيانات خلال كل جلسة، أو ما يصل إلى 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) هي آلية بسيطة تُستخدم لنقل البيانات من خادم إلى مثيل تطبيق معيّن. باستخدام &quot;مراسلة Firebase السحابية&quot;، يمكن لخادمك إرسال إشعار إلى تطبيقك الذي يعمل على جهاز معيّن بأنّه تتوفّر بيانات جديدة له.

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

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