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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

على سبيل المثال، إذا كان جهاز البث اللاسلكي 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) هي أسلوب خفيف الوزن يُستخدَم لنقل البيانات من خادم إلى مثيل تطبيق معيّن. باستخدام "المراسلة عبر السحابة الإلكترونية من Firebase"، يمكن لخادمك إرسال إشعار إلى تطبيقك الذي يعمل على جهاز معيّن يفيد بأنّه تتوفّر بيانات جديدة له.

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

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