بهینه سازی پس زمینه

فرآیندهای پس‌زمینه می‌توانند مصرف حافظه و باتری بالایی داشته باشند. برای مثال، یک پخش ضمنی ممکن است بسیاری از فرآیندهای پس‌زمینه را که برای گوش دادن به آن ثبت‌نام کرده‌اند، شروع کند، حتی اگر آن فرآیندها کار زیادی انجام ندهند. این می‌تواند تأثیر قابل توجهی بر عملکرد دستگاه و تجربه کاربر داشته باشد.

برای رفع این مشکل، اندروید ۷.۰ (سطح API 24) محدودیت‌های زیر را اعمال می‌کند:

  • برنامه‌هایی که اندروید ۷.۰ (سطح API 24) و بالاتر را هدف قرار می‌دهند، اگر گیرنده پخش خود را در مانیفست اعلام کنند، پخش‌های CONNECTIVITY_ACTION را دریافت نمی‌کنند. برنامه‌ها اگر BroadcastReceiver خود را با Context.registerReceiver() ثبت کنند و آن زمینه هنوز معتبر باشد، همچنان پخش‌های CONNECTIVITY_ACTION را دریافت خواهند کرد.
  • برنامه‌ها نمی‌توانند پخش‌های ACTION_NEW_PICTURE یا ACTION_NEW_VIDEO را ارسال یا دریافت کنند. این بهینه‌سازی بر همه برنامه‌ها تأثیر می‌گذارد، نه فقط برنامه‌هایی که اندروید ۷.۰ (سطح API ۲۴) را هدف قرار می‌دهند.

اگر برنامه شما از هر یک از این intentها استفاده می‌کند، باید در اسرع وقت وابستگی‌های آنها را حذف کنید تا بتوانید به درستی دستگاه‌هایی را که اندروید ۷.۰ یا بالاتر را اجرا می‌کنند، هدف قرار دهید. چارچوب اندروید چندین راه‌حل برای کاهش نیاز به این broadcastهای ضمنی ارائه می‌دهد. به عنوان مثال، JobScheduler و WorkManager جدید، مکانیسم‌های قدرتمندی را برای زمان‌بندی عملیات شبکه در صورت برآورده شدن شرایط مشخص شده، مانند اتصال به یک شبکه بدون محدودیت زمانی، ارائه می‌دهند. اکنون می‌توانید از JobScheduler برای واکنش به تغییرات در ارائه دهندگان محتوا نیز استفاده کنید. اشیاء JobInfo پارامترهایی را که JobScheduler برای زمان‌بندی کار شما استفاده می‌کند، کپسوله‌سازی می‌کنند. هنگامی که شرایط کار برآورده شود، سیستم این کار را روی JobService برنامه شما اجرا می‌کند.

در این صفحه، یاد خواهیم گرفت که چگونه از روش‌های جایگزین، مانند JobScheduler ، برای تطبیق برنامه خود با این محدودیت‌های جدید استفاده کنیم.

محدودیت‌های اعمال‌شده توسط کاربر

در صفحه مصرف باتری در تنظیمات سیستم ، کاربر می‌تواند از گزینه‌های زیر انتخاب کند:

  • نامحدود: اجازه دهید تمام کارهای پس‌زمینه انجام شوند، که ممکن است باتری بیشتری مصرف کنند.
  • بهینه‌شده (پیش‌فرض): توانایی برنامه برای انجام کارهای پس‌زمینه را بر اساس نحوه تعامل کاربر با برنامه بهینه می‌کند.
  • محدود شده: به طور کامل از اجرای یک برنامه در پس‌زمینه جلوگیری می‌کند. ممکن است برنامه‌ها آنطور که انتظار می‌رود کار نکنند.

اگر برنامه‌ای برخی از رفتارهای بد شرح داده شده در Android Vitals را از خود نشان دهد، سیستم ممکن است از کاربر بخواهد دسترسی آن برنامه به منابع سیستم را محدود کند.

اگر سیستم متوجه شود که یک برنامه منابع زیادی را مصرف می‌کند، به کاربر اطلاع می‌دهد و به او امکان محدود کردن اقدامات برنامه را می‌دهد. رفتارهایی که می‌توانند باعث ایجاد این اخطار شوند عبارتند از:

  • قفل‌های بیدارباش بیش از حد: ۱ قفل بیدارباش جزئی به مدت یک ساعت هنگام خاموش بودن صفحه نمایش نگه داشته می‌شود
  • سرویس‌های پس‌زمینه بیش از حد: اگر برنامه سطوح API پایین‌تر از ۲۶ را هدف قرار دهد و سرویس‌های پس‌زمینه بیش از حد داشته باشد.

محدودیت‌های دقیق اعمال‌شده توسط سازنده دستگاه تعیین می‌شوند. برای مثال، در نسخه‌های AOSP که اندروید ۹ (سطح API 28) یا بالاتر را اجرا می‌کنند، برنامه‌هایی که در پس‌زمینه اجرا می‌شوند و در حالت «محدود» قرار دارند، محدودیت‌های زیر را دارند:

  • نمی‌توان سرویس‌های پیش‌زمینه را راه‌اندازی کرد
  • سرویس‌های پیش‌زمینه موجود از پیش‌زمینه حذف می‌شوند
  • آلارم‌ها فعال نمی‌شوند
  • کارها اجرا نمی‌شوند

همچنین، اگر برنامه‌ای اندروید ۱۳ (سطح API ۳۳) یا بالاتر را هدف قرار دهد و در حالت «محدود» باشد، سیستم تا زمانی که برنامه به دلایل دیگر شروع به کار نکند، پخش BOOT_COMPLETED یا پخش LOCKED_BOOT_COMPLETED را ارائه نمی‌دهد.

محدودیت‌های خاص در بخش «محدودیت‌های مدیریت نیرو» فهرست شده‌اند.

محدودیت‌های دریافت پخش‌های فعالیت شبکه

برنامه‌هایی که اندروید ۷.۰ (سطح API 24) را هدف قرار می‌دهند، اگر در مانیفست خود برای دریافت اعلان‌های CONNECTIVITY_ACTION ثبت‌نام کنند، آنها را دریافت نمی‌کنند و فرآیندهایی که به این اعلان‌ها وابسته هستند، شروع نمی‌شوند. این می‌تواند برای برنامه‌هایی که می‌خواهند به تغییرات شبکه گوش دهند یا فعالیت‌های شبکه‌ای انبوهی را هنگام اتصال دستگاه به یک شبکه بدون محدودیت زمانی انجام دهند، مشکل ایجاد کند. چندین راه‌حل برای دور زدن این محدودیت در چارچوب اندروید وجود دارد، اما انتخاب راه‌حل مناسب به هدفی که می‌خواهید برنامه شما انجام دهد بستگی دارد.

نکته: یک BroadcastReceiver که با Context.registerReceiver() ثبت شده است، در حین اجرای برنامه، همچنان این broadcastها را دریافت می‌کند.

زمانبندی کارهای شبکه در اتصالات بدون محدودیت

هنگام استفاده از کلاس JobInfo.Builder برای ساخت شیء JobInfo خود، متد setRequiredNetworkType() را اعمال کنید و JobInfo.NETWORK_TYPE_UNMETERED به عنوان پارامتر job ارسال کنید. نمونه کد زیر، سرویسی را برای اجرا در زمانی که دستگاه به یک شبکه بدون محدودیت زمانی متصل شده و در حال شارژ است، زمان‌بندی می‌کند:

کاتلین

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)
}

جاوا

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 است، یک API که به شما امکان می‌دهد وظایف پس‌زمینه‌ای را که نیاز به تکمیل تضمین‌شده دارند، صرف نظر از اینکه فرآیند برنامه در حال انجام است یا خیر، زمان‌بندی کنید. WorkManager روش مناسب برای اجرای کار (یا مستقیماً روی یک نخ در فرآیند برنامه شما و همچنین با استفاده از JobScheduler، FirebaseJobDispatcher یا AlarmManager) را بر اساس عواملی مانند سطح API دستگاه انتخاب می‌کند. علاوه بر این، WorkManager به سرویس‌های Play نیاز ندارد و چندین ویژگی پیشرفته مانند زنجیره‌سازی وظایف با هم یا بررسی وضعیت یک وظیفه را ارائه می‌دهد. برای کسب اطلاعات بیشتر، به WorkManager مراجعه کنید.

نظارت بر اتصال شبکه در حین اجرای برنامه

برنامه‌هایی که در حال اجرا هستند، همچنان می‌توانند با یک BroadcastReceiver ثبت‌شده به CONNECTIVITY_CHANGE گوش دهند. با این حال، API ConnectivityManager روشی قوی‌تر برای درخواست فراخوانی مجدد تنها در صورت برآورده شدن شرایط مشخص‌شده در شبکه ارائه می‌دهد.

اشیاء NetworkRequest پارامترهای فراخوانی شبکه را بر اساس NetworkCapabilities تعریف می‌کنند. شما اشیاء NetworkRequest با کلاس NetworkRequest.Builder ایجاد می‌کنید. registerNetworkCallback() سپس شیء NetworkRequest را به سیستم ارسال می‌کند. هنگامی که شرایط شبکه برآورده شد، برنامه یک فراخوانی برای اجرای متد onAvailable() تعریف شده در کلاس ConnectivityManager.NetworkCallback خود دریافت می‌کند.

برنامه تا زمانی که از برنامه خارج نشود یا تابع unregisterNetworkCallback() را فراخوانی نکند، به دریافت فراخوانی‌های برگشتی ادامه می‌دهد.

محدودیت در دریافت تصاویر و ویدیوهای ارسالی

در اندروید ۷.۰ (سطح API ۲۴)، برنامه‌ها قادر به ارسال یا دریافت پخش‌های ACTION_NEW_PICTURE یا ACTION_NEW_VIDEO نیستند. این محدودیت به کاهش تأثیرات عملکرد و تجربه کاربری در زمانی که چندین برنامه باید برای پردازش یک تصویر یا ویدیوی جدید فعال شوند، کمک می‌کند. اندروید ۷.۰ (سطح API ۲۴) توابع JobInfo و JobParameters را برای ارائه یک راه‌حل جایگزین، توسعه داده است.

اجرای کارها در صورت تغییر URI محتوا

برای اجرای کارها بر اساس تغییرات URI محتوا، اندروید ۷.۰ (سطح API 24) API JobInfo را با متدهای زیر توسعه می‌دهد:

JobInfo.TriggerContentUri()
پارامترهای مورد نیاز برای اجرای یک کار (job) در صورت تغییر در محتوای URI را کپسوله‌سازی می‌کند.
JobInfo.Builder.addTriggerContentUri()
یک شیء TriggerContentUri به JobInfo ارسال می‌کند. یک ContentObserver URI محتوای کپسوله‌شده را رصد می‌کند. اگر چندین شیء TriggerContentUri با یک job مرتبط باشند، سیستم حتی اگر تغییر فقط در یکی از URIهای محتوا را گزارش کند، یک callback ارائه می‌دهد.
پرچم TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS را اضافه کنید تا در صورت تغییر هر یک از فرزندان URI داده شده، کار را آغاز کند. این پرچم مربوط به پارامتر notifyForDescendants ارسال شده به registerContentObserver() است.

نکته: نمی‌توان از TriggerContentUri() در ترکیب با setPeriodic() یا setPersisted() استفاده کرد. برای نظارت مداوم بر تغییرات محتوا، یک JobInfo جدید را قبل از اینکه JobService برنامه، مدیریت آخرین فراخوانی را تمام کند، زمان‌بندی کنید.

کد نمونه زیر، وظیفه‌ای را زمان‌بندی می‌کند تا زمانی که سیستم تغییری در URI محتوا، MEDIA_URI گزارش می‌دهد، فعال شود:

کاتلین

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)
}

جاوا

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 ارسال می‌شود.

مشخص کنید کدام مراجع محتوا باعث ایجاد یک کار شده‌اند

اندروید ۷.۰ (سطح API 24) همچنین JobParameters گسترش می‌دهد تا به برنامه شما اجازه دهد اطلاعات مفیدی در مورد اینکه کدام منابع محتوا و URIها کار را آغاز کرده‌اند، دریافت کند:

Uri[] getTriggeredContentUris()
آرایه‌ای از URIهایی که کار را آغاز کرده‌اند را برمی‌گرداند. اگر هیچ URI کار را آغاز نکرده باشد (مثلاً کار به دلیل مهلت یا دلیل دیگری آغاز شده باشد)، یا تعداد URIهای تغییر یافته بیشتر از ۵۰ باشد، این null خواهد بود.
String[] getTriggeredContentAuthorities()
یک آرایه رشته‌ای از مجوزهای محتوایی که کار را آغاز کرده‌اند، برمی‌گرداند. اگر آرایه برگردانده شده null نباشد، از getTriggeredContentUris() برای بازیابی جزئیات URI های تغییر یافته استفاده کنید.

کد نمونه زیر متد JobService.onStartJob() را بازنویسی می‌کند و مجوزهای محتوا و URIهایی را که کار را آغاز کرده‌اند، ثبت می‌کند:

کاتلین

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
}

جاوا

@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;
}

برنامه خود را بیشتر بهینه کنید

بهینه‌سازی برنامه‌های شما برای اجرا در دستگاه‌های با حافظه کم یا در شرایط کمبود حافظه، می‌تواند عملکرد و تجربه کاربری را بهبود بخشد. حذف وابستگی‌ها به سرویس‌های پس‌زمینه و گیرنده‌های پخش ضمنی ثبت‌شده در مانیفست می‌تواند به اجرای بهتر برنامه شما در چنین دستگاه‌هایی کمک کند. اگرچه اندروید ۷.۰ (سطح API 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