تحسين الخلفية

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

للتخفيف من هذه المشكلة، يفرض نظام التشغيل Android 7.0 (المستوى 24 من واجهة برمجة التطبيقات) القيود التالية:

  • لا تتلقّى التطبيقات التي تستهدف الإصدار 7.0 من نظام التشغيل Android (المستوى 24 من واجهة برمجة التطبيقات) والإصدارات الأحدث عمليات بث CONNECTIVITY_ACTION إذا كانت تعرّف أداة استقبال عمليات البث في البيان. ستظل التطبيقات تتلقّى عمليات البث CONNECTIVITY_ACTION إذا سجّلت BroadcastReceiver لدى Context.registerReceiver() وكانت هذه الحالة لا تزال صالحة.
  • لا يمكن للتطبيقات إرسال أو تلقّي عمليات بث ACTION_NEW_PICTURE أو ACTION_NEW_VIDEO. ويؤثّر هذا التحسين في جميع التطبيقات، وليس فقط تلك التي تستهدف الإصدار 7.0 من نظام التشغيل Android (المستوى 24 من واجهة برمجة التطبيقات).

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

في هذه الصفحة، سنتعرّف على كيفية استخدام طرق بديلة، مثل JobScheduler، لتكييف تطبيقك مع هذه القيود الجديدة.

القيود التي يفرضها المستخدم

في صفحة استخدام البطارية ضمن إعدادات النظام، يمكن للمستخدم الاختيار من بين الخيارات التالية:

  • بدون قيود: السماح بجميع العمليات في الخلفية، ما قد يستهلك المزيد من طاقة البطارية
  • محسّن (الإعداد التلقائي): تحسين قدرة التطبيق على تنفيذ العمل في الخلفية استنادًا إلى طريقة تفاعل المستخدم مع التطبيق
  • محظور: يمنع التطبيق تمامًا من العمل في الخلفية. قد لا تعمل التطبيقات على النحو المتوقَّع.

إذا كان التطبيق يتضمّن بعض السلوكيات السيئة الموضّحة في مقاييس Android الحيوية، قد يطلب النظام من المستخدم حظر وصول التطبيق إلى موارد النظام.

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

  • عمليات قفل التنشيط الزائدة عن الحد: عملية قفل تنشيط جزئي واحدة لمدة ساعة عندما تكون الشاشة مطفأة
  • الخدمات المفرطة التي تعمل في الخلفية: إذا كان التطبيق يستهدف مستويات أقل من 26 لواجهة برمجة التطبيقات ويتضمّن خدمات مفرطة تعمل في الخلفية

وتحدّد الشركة المصنّعة للجهاز القيود الدقيقة المفروضة. على سبيل المثال، في إصدارات مشروع Android المفتوح المصدر (AOSP) التي تعمل بالإصدار 9 من نظام التشغيل Android (المستوى 28 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، تسري القيود التالية على التطبيقات التي تعمل في الخلفية والتي تكون في الحالة "مقيّدة":

  • لا يمكن تشغيل الخدمات التي تعمل في المقدّمة
  • تتم إزالة الخدمات الحالية التي تعمل في المقدّمة منها
  • عدم تفعيل المنبّهات
  • عدم تنفيذ المهام

بالإضافة إلى ذلك، إذا كان التطبيق يستهدف الإصدار 13 من نظام التشغيل Android (المستوى 33 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث وكان في حالة "مقيّد"، لن يرسل النظام البث BOOT_COMPLETED أو البث LOCKED_BOOT_COMPLETED إلى أن يتم تشغيل التطبيق لأسباب أخرى.

تتوفّر القيود المحدّدة في مقالة قيود إدارة الطاقة.

قيود على تلقّي عمليات بث نشاط الشبكة

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

ملاحظة: يستمر BroadcastReceiver المسجّل باستخدام Context.registerReceiver() في تلقّي عمليات البث هذه أثناء تشغيل التطبيق.

جدولة مهام الشبكة على الاتصالات غير المحدودة

عند استخدام فئة JobInfo.Builder لإنشاء عنصر JobInfo، طبِّق طريقة setRequiredNetworkType() ومرِّر JobInfo.NETWORK_TYPE_UNMETERED كمعلَمة مهمة. يعمل نموذج الرمز التالي على جدولة خدمة ليتم تشغيلها عندما يتصل الجهاز بشبكة غير محدودة البيانات ويكون قيد الشحن:

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MyJobService::class.java)
    )
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
            .setRequiresCharging(true)
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
      (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo job = new JobInfo.Builder(
    MY_BACKGROUND_JOB,
    new ComponentName(context, MyJobService.class))
      .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
      .setRequiresCharging(true)
      .build();
  js.schedule(job);
}

عند استيفاء شروط مهمتك، يتلقّى تطبيقك عملية ردّ الاتصال لتشغيل طريقة onStartJob() في JobService.class المحدّد. للاطّلاع على المزيد من الأمثلة حول تنفيذ JobScheduler، يمكنك الاطّلاع على تطبيق JobScheduler النموذجي.

يُعدّ WorkManager بديلاً جديدًا عن JobScheduler، وهو عبارة عن واجهة برمجة تطبيقات تتيح لك جدولة المهام التي تعمل في الخلفية والتي يجب إكمالها بغض النظر عمّا إذا كانت عملية التطبيق قيد التشغيل أم لا. تختار WorkManager الطريقة المناسبة لتنفيذ العمل (إما مباشرةً على سلسلة محادثات في عملية تطبيقك، أو باستخدام JobScheduler أو FirebaseJobDispatcher أو AlarmManager) استنادًا إلى عوامل مثل مستوى واجهة برمجة التطبيقات على الجهاز. بالإضافة إلى ذلك، لا تتطلّب WorkManager توفّر "خدمات Play"، وتوفّر العديد من الميزات المتقدّمة، مثل ربط المهام ببعضها أو التحقّق من حالة إحدى المهام. لمزيد من المعلومات، يُرجى الاطّلاع على WorkManager.

مراقبة إمكانية الاتصال بالشبكة أثناء تشغيل التطبيق

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

تحدّد عناصر NetworkRequest مَعلمات ردّ الاتصال على الشبكة من حيث NetworkCapabilities. يمكنك إنشاء NetworkRequest كائن باستخدام الفئة NetworkRequest.Builder. registerNetworkCallback() ثم يمرِّر الكائن NetworkRequest إلى النظام. عند استيفاء شروط الشبكة، يتلقّى التطبيق عملية ردّ لتنفيذ الطريقة onAvailable() المحدّدة في الفئة ConnectivityManager.NetworkCallback.

يستمر التطبيق في تلقّي عمليات الاستدعاء إلى أن يتم إغلاقه أو إلى أن يستدعي unregisterNetworkCallback().

قيود على تلقّي عمليات بث الصور والفيديوهات

في نظام التشغيل Android 7.0 (المستوى 24 من واجهة برمجة التطبيقات)، لا يمكن للتطبيقات إرسال أو تلقّي عمليات بث ACTION_NEW_PICTURE أو ACTION_NEW_VIDEO. يساعد هذا القيد في الحد من التأثيرات في الأداء وتجربة المستخدم عندما تحتاج عدة تطبيقات إلى التنشيط لمعالجة صورة أو فيديو جديدَين. يوسّع نظام التشغيل Android 7.0 (المستوى 24 من واجهة برمجة التطبيقات) نطاق JobInfo وJobParameters لتوفير حل بديل.

تشغيل المهام عند تغيير معرّف الموارد المنتظم للمحتوى

لتشغيل المهام عند حدوث تغييرات في معرّف الموارد المنتظم (URI) الخاص بالمحتوى، يوسّع نظام التشغيل Android 7.0 (المستوى 24 من واجهة برمجة التطبيقات) واجهة برمجة التطبيقات JobInfo باستخدام الطرق التالية:

JobInfo.TriggerContentUri()
تتضمّن هذه السمة المَعلمات المطلوبة لتشغيل مهمة عند حدوث تغييرات في معرّف الموارد المنتظم (URI) الخاص بالمحتوى.
JobInfo.Builder.addTriggerContentUri()
تمرير عنصر TriggerContentUri إلى JobInfo يراقب ContentObserver معرّف الموارد المنتظم للمحتوى المغلَّف. إذا كانت هناك عدة عناصر TriggerContentUri مرتبطة بوظيفة، يوفّر النظام دالة ردّ الاتصال حتى إذا أبلغ عن تغيير في معرّف موارد منتظم واحد فقط من معرّفات الموارد المنتظمة الخاصة بالمحتوى.
أضِف العلامة TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS لبدء المهمة إذا تغيّر أي عنصر فرعي من معرّف الموارد المنتظم (URI) المحدّد. يتوافق هذا الخيار مع المَعلمة notifyForDescendants التي تم تمريرها إلى registerContentObserver().

ملاحظة: لا يمكن استخدام TriggerContentUri() مع setPeriodic() أو setPersisted(). لمراقبة التغييرات في المحتوى بشكل مستمر، حدِّد موعدًا لـ JobInfo جديد قبل أن ينتهي JobService للتطبيق من معالجة أحدث عملية ردّ.

يجدول نموذج الرمز التالي مهمة يتم تشغيلها عندما يبلغ النظام عن تغيير في معرّف الموارد المنتظم الخاص بالمحتوى، MEDIA_URI:

Kotlin

const val MY_BACKGROUND_JOB = 0
...
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    val job = JobInfo.Builder(
            MY_BACKGROUND_JOB,
            ComponentName(context, MediaContentJob::class.java)
    )
            .addTriggerContentUri(
                    JobInfo.TriggerContentUri(
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                            JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
                    )
            )
            .build()
    jobScheduler.schedule(job)
}

Java

public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
  JobScheduler js =
          (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
  JobInfo.Builder builder = new JobInfo.Builder(
          MY_BACKGROUND_JOB,
          new ComponentName(context, MediaContentJob.class));
  builder.addTriggerContentUri(
          new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
          JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
  js.schedule(builder.build());
}

عندما يرصد النظام تغييرًا في عناوين URI المحدّدة للمحتوى، يتلقّى تطبيقك ردّ اتصال ويتم تمرير عنصر JobParameters إلى الطريقة onStartJob() في MediaContentJob.class.

تحديد الجهات المسؤولة عن المحتوى التي أطلقت مهمة

يوسّع الإصدار 7.0 من نظام التشغيل Android (المستوى 24 من واجهة برمجة التطبيقات) أيضًا نطاق JobParameters ليسمح لتطبيقك بتلقّي معلومات مفيدة حول جهات توفير المحتوى ومعرّفات الموارد الموحّدة التي أدّت إلى تشغيل المهمة:

Uri[] getTriggeredContentUris()
تعرِض هذه السمة صفيفًا من معرّفات الموارد الموحّدة (URI) التي أدّت إلى تشغيل المهمة. ستكون القيمة null إذا لم تؤدِّ أي عناوين URI إلى تشغيل المهمة (على سبيل المثال، تم تشغيل المهمة بسبب موعد نهائي أو سبب آخر)، أو إذا كان عدد عناوين URI التي تم تغييرها أكبر من 50.
String[] getTriggeredContentAuthorities()
تعرِض هذه السمة صفيفًا من السلاسل يضم جهات ترخيص المحتوى التي شغّلت المهمة. إذا لم تكن المصفوفة المعروضة null، استخدِم getTriggeredContentUris() لاسترداد تفاصيل عناوين URI التي تم تغييرها.

يستبدل نموذج الرمز التالي طريقة JobService.onStartJob() ويسجّل الجهات الموثوقة للمحتوى ومعرّفات الموارد المنتظمة (URI) التي أدّت إلى تشغيل المهمة:

Kotlin

override fun onStartJob(params: JobParameters): Boolean {
    StringBuilder().apply {
        append("Media content has changed:\n")
        params.triggeredContentAuthorities?.also { authorities ->
            append("Authorities: ${authorities.joinToString(", ")}\n")
            append(params.triggeredContentUris?.joinToString("\n"))
        } ?: append("(No content)")
        Log.i(TAG, toString())
    }
    return true
}

Java

@Override
public boolean onStartJob(JobParameters params) {
  StringBuilder sb = new StringBuilder();
  sb.append("Media content has changed:\n");
  if (params.getTriggeredContentAuthorities() != null) {
      sb.append("Authorities: ");
      boolean first = true;
      for (String auth :
          params.getTriggeredContentAuthorities()) {
          if (first) {
              first = false;
          } else {
             sb.append(", ");
          }
           sb.append(auth);
      }
      if (params.getTriggeredContentUris() != null) {
          for (Uri uri : params.getTriggeredContentUris()) {
              sb.append("\n");
              sb.append(uri);
          }
      }
  } else {
      sb.append("(No content)");
  }
  Log.i(TAG, sb.toString());
  return true;
}

تحسين تطبيقك بشكل أكبر

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

يمكن أن تساعدك أوامر Android Debug Bridge (ADB) التالية في اختبار سلوك التطبيق مع إيقاف العمليات التي تعمل في الخلفية:

  • لمحاكاة الحالات التي لا تتوفّر فيها عمليات البث الضمنية والخدمات التي تعمل في الخلفية، أدخِل الأمر التالي:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
    
  • لإعادة تفعيل عمليات البث الضمني والخدمات التي تعمل في الخلفية، أدخِل الأمر التالي:
  • $ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
    
  • يمكنك محاكاة وضع المستخدم لتطبيقك في حالة "محظور" من حيث استخدام البطارية في الخلفية. يمنع هذا الإعداد تطبيقك من إمكانية التشغيل في الخلفية. لإجراء ذلك، نفِّذ الأمر التالي في نافذة الوحدة الطرفية:
  • $ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny