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

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

للتخفيف من هذه المشكلة، يفرض نظام التشغيل 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 Vitals، قد يطلب النظام من المستخدم حظر وصول التطبيق إلى موارد النظام.

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

  • عمليات قفل التنشيط الزائدة عن الحد: عملية قفل تنشيط جزئي واحدة لمدة ساعة عندما تكون الشاشة مطفأة
  • الخدمات المفرطة التي تعمل في الخلفية: إذا كان التطبيق يستهدف مستويات أقل من 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. يساعد هذا القيد في الحدّ من التأثيرات في الأداء وتجربة المستخدم عندما تحتاج عدة تطبيقات إلى التنشيط لمعالجة صورة أو فيديو جديدَين. يوسّع الإصدار 7.0 من نظام التشغيل Android (المستوى 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 التي أدّت إلى تشغيل المهمة:

Uri[] getTriggeredContentUris()
تعرِض هذه السمة صفيفًا من معرّفات الموارد الموحّدة (URI) التي أدّت إلى تشغيل المهمة. ستكون القيمة null إذا لم تؤدِّ أي معرّفات موارد موحّدة إلى تشغيل المهمة (على سبيل المثال، تم تشغيل المهمة بسبب موعد نهائي أو سبب آخر)، أو إذا كان عدد معرّفات الموارد الموحّدة المتغيرة أكبر من 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