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

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

لحلّ هذه المشكلة، يفرض نظام التشغيل 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 لواجهة برمجة التطبيقات).

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

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

القيود التي يبدأها المستخدم

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

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

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

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

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

وتحدِّد الشركة المصنّعة للجهاز القيود الدقيقة المفروضة. على سبيل المثال، في إصدارات AOSP التي تعمل بنظام التشغيل Android 9 (المستوى 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.

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

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

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

تحدّد عناصر NetworkRequest مَعلمات NetworkCapabilities callback للشبكة من حيث 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 لتوفير حل بديل.

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

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

JobInfo.TriggerContentUri()
تُستخدم هذه السمة لتضمين المَعلمات المطلوبة لبدء مهمة عند تغيير معرّف الموارد المنتظم (URI) للمحتوى.
JobInfo.Builder.addTriggerContentUri()
تمرير عنصر TriggerContentUri إلى JobInfo يرصد ContentObserver عنوان URL للمحتوى المُدمَج. إذا كانت هناك عناصر TriggerContentUri متعددة مرتبطة بمهمة، يقدّم النظام callback حتى إذا أبلغ عن تغيير في معرّف موارد منتظم واحد فقط للمحتوى.
أضِف العلامة 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());
}

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

تحديد هيئات المحتوى التي تسبّبت في بدء إحدى المهام

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

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

تلغي تعليمات الرمز البرمجي النموذجية التالية طريقة 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