אופטימיזציה ברקע

תהליכים שפועלים ברקע יכולים לצרוך הרבה זיכרון וסוללה. לדוגמה, שידור מרומז עשוי להפעיל הרבה תהליכי רקע שנדרשים להאזין לו, גם אם התהליכים האלה לא מבצעים הרבה עבודה. המצב הזה משפיע באופן משמעותי על הביצועים של המכשיר ועל חוויית המשתמש.

כדי לפתור את הבעיה, ב-Android 7.0 (רמת API‏ 24) חלות ההגבלות הבאות:

  • אפליקציות שמטרגטות ל-Android מגרסה 7.0 (רמת API‏ 24) ומעלה לא מקבלות שידורי CONNECTIVITY_ACTION אם הן מצהירות על מקלט השידור שלהן במניפסט. אפליקציות עדיין יקבלו שידורי CONNECTIVITY_ACTION אם הן ירשמו את ה-BroadcastReceiver שלהן באמצעות Context.registerReceiver() וההקשר הזה עדיין יהיה תקף.
  • אפליקציות לא יכולות לשלוח או לקבל שידורים מסוג ACTION_NEW_PICTURE או ACTION_NEW_VIDEO. האופטימיזציה הזו משפיעה על כל האפליקציות, לא רק על אלה שמטרגטות ל-Android 7.0 (רמת API ‏24).

אם האפליקציה שלכם משתמשת באחד מה-Intent האלה, אתם צריכים להסיר את יחסי התלות בהם בהקדם האפשרי כדי שתוכלו לטרגט כראוי מכשירים עם Android מגרסה 7.0 ואילך. מסגרת Android מספקת כמה פתרונות כדי לצמצם את הצורך בהודעות המרומזות האלה. לדוגמה, JobScheduler ו-WorkManager החדש מספקים מנגנונים חזקים לתזמון פעולות ברשת כשמתקיימים תנאים מסוימים, כמו חיבור לרשת ללא חיוב לפי שימוש. עכשיו אפשר להשתמש ב-JobScheduler גם כדי להגיב לשינויים בספקים לניהול גישה. אובייקטים מסוג JobInfo כוללים את הפרמטרים שמשמשים את JobScheduler לתזמון המשימה. כשהתנאים של המשימה מתקיימים, המערכת מריצה את המשימה ב-JobService של האפליקציה.

בדף הזה נסביר איך להשתמש בשיטות חלופיות, כמו JobScheduler, כדי להתאים את האפליקציה להגבלות החדשות האלה.

הגבלות שהמשתמשים יוזמים

בדף שימוש בסוללה בהגדרות המערכת, המשתמש יכול לבחור מבין האפשרויות הבאות:

  • ללא הגבלות: האפשרות הזו מאפשרת את כל הפעולות ברקע, מה שעלול להגביר את צריכת הסוללה.
  • אופטימיזציה (ברירת מחדל): היכולת של האפליקציה לבצע פעולות ברקע, על סמך פעולת המשתמש בה.
  • מוגבלת: האפשרות הזו מונעת לחלוטין את הפעלת האפליקציה ברקע. האפליקציות לא יפעלו כמצופה במצב הזה.

אם אפליקציה מציגה חלק מהתפקודים הלא תקינים שמתוארים בתפקוד האפליקציה, יכול להיות שהמערכת תבקש מהמשתמש להגביל את הגישה של האפליקציה הזו למשאבי המערכת.

אם המערכת מזהה שאפליקציה צורכת משאבים רבים מדי, היא מודיעה למשתמש ומאפשרת לו להגביל את הפעולות של האפליקציה. ההודעה יכולה להופיע בעקבות פעולות כמו:

  • שימוש מוגזם בחסימת מצב שינה: חסימה חלקית אחת של מצב שינה למשך שעה כשהמסך כבוי
  • שירותים מוגזמים שפועלים ברקע: אם האפליקציה מטרגטת לרמות API נמוכות מ-26 ויש בה שירותים מוגזמים שפועלים ברקע

יצרן המכשיר קובע את ההגבלות המדויקות שחלות על המכשיר. לדוגמה, בגרסאות build של AOSP שפועלות ב-Android 9 (רמת API‏ 28) ומעלה, לאפליקציות שפועלות ברקע ונמצאות במצב "מוגבל" יש את המגבלות הבאות:

  • לא ניתן להפעיל שירותים שפועלים בחזית
  • שירותים קיימים שפועלים בחזית מוסרים מהחזית
  • השעונים המעוררים לא מופעלים
  • המשימות לא מבוצעות

בנוסף, אם האפליקציה מטרגטת ל-Android 13 (רמת API‏ 33) ומעלה והיא במצב "מוגבל", המערכת לא מעבירה את השידור BOOT_COMPLETED או את השידור LOCKED_BOOT_COMPLETED עד שהאפליקציה מופעלת מסיבות אחרות.

ההגבלות הספציפיות מפורטות בקטע הגבלות על ניהול צריכת החשמל.

הגבלות על קבלת שידורים של פעילות ברשת

אפליקציות שמטרגטות את Android 7.0 (רמת API‏ 24) לא מקבלות שידורי CONNECTIVITY_ACTION אם הן נרשמות לקבלת השידורים האלה במניפסט שלהן, ותהליכים שתלויים בשידור הזה לא יתחילו. זה יכול להיות בעייתי באפליקציות שרוצות להאזין לשינויים ברשת או לבצע פעילויות ברשת בכמות גדולה כשהמכשיר מתחבר לרשת ללא שימוש נמדד. יש כמה פתרונות לעקיפת המגבלה הזו שכבר קיימים ב-Android framework, אבל הבחירה בפתרון הנכון תלויה במה שאתם רוצים שהאפליקציה תעשה.

הערה: 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);
}

כשהתנאים של המשימה מתקיימים, האפליקציה מקבלת קריאה חוזרת (callback) להפעלת השיטה onStartJob() ב-JobService.class שצוין. כדי לראות דוגמאות נוספות להטמעה של JobScheduler, אפשר לעיין באפליקציה לדוגמה של JobScheduler.

חלופה חדשה ל-JobScheduler היא WorkManager, ממשק API שמאפשר לתזמן משימות ברקע שצריך להשלים, גם אם תהליך האפליקציה לא פועל. ‫WorkManager בוחר את הדרך המתאימה להריץ את העבודה (ישירות בשרשור בתהליך של האפליקציה, או באמצעות JobScheduler, ‏ FirebaseJobDispatcher או AlarmManager) על סמך גורמים כמו רמת ה-API של המכשיר. בנוסף, WorkManager לא דורש את Play Services ומספק כמה תכונות מתקדמות, כמו שרשור משימות או בדיקת סטטוס של משימה. מידע נוסף זמין במאמר WorkManager.

מעקב אחרי החיבור לרשת כשהאפליקציה פועלת

אפליקציות שפועלות עדיין יכולות להאזין ל-CONNECTIVITY_CHANGE באמצעות BroadcastReceiver רשום. עם זאת, ה-API של ConnectivityManager מספק שיטה יעילה יותר לבקש קריאה חוזרת (callback) רק כשמתקיימים תנאי רשת מסוימים.

אובייקטים מסוג NetworkRequest מגדירים את הפרמטרים של הקריאה החוזרת (callback) של הרשת במונחים של NetworkCapabilities. אתם יוצרים אובייקטים מסוג NetworkRequest באמצעות המחלקה NetworkRequest.Builder. ‫registerNetworkCallback() מעבירה את האובייקט NetworkRequest למערכת. כשתנאי הרשת מתקיימים, האפליקציה מקבלת קריאה חוזרת (callback) כדי להפעיל את ה-method‏ onAvailable() שמוגדרת בכיתה ConnectivityManager.NetworkCallback שלה.

האפליקציה ממשיכה לקבל קריאות חוזרות עד שהיא נסגרת או עד שהיא קוראת ל-unregisterNetworkCallback().

הגבלות על קבלת שידורי תמונות וסרטונים

ב-Android 7.0 (רמת API ‏24), אפליקציות לא יכולות לשלוח או לקבל שידורים של ACTION_NEW_PICTURE או ACTION_NEW_VIDEO. ההגבלה הזו עוזרת לצמצם את ההשפעה על הביצועים ועל חוויית המשתמש כשכמה אפליקציות צריכות להתעורר כדי לעבד תמונה או סרטון חדשים. ב-Android 7.0 (רמת API ‏24) הרחבנו את JobInfo ו-JobParameters כדי לספק פתרון חלופי.

הפעלת משימות כשמתבצעים שינויים ב-URI של תוכן

כדי להפעיל משימות כשמתבצעים שינויים ב-URI של תוכן, ב-Android 7.0 (רמת API‏ 24) ה-API של JobInfo הורחב באמצעות השיטות הבאות:

JobInfo.TriggerContentUri()
‫
מכיל את הפרמטרים שנדרשים כדי להפעיל עבודה כשמתבצעים שינויים ב-URI של התוכן.
JobInfo.Builder.addTriggerContentUri()
‫
מעבירה אובייקט TriggerContentUri אל JobInfo. ‫ContentObserver עוקב אחרי ה-URI של התוכן המצורף. אם יש כמה אובייקטים של TriggerContentUri שמשויכים לעבודה, המערכת מספקת קריאה חוזרת גם אם היא מדווחת על שינוי רק באחד ממזהי ה-URI של התוכן. ‫
מוסיפים את הדגל TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS כדי להפעיל את העבודה אם יש שינוי בצאצאים של ה-URI שצוין. הדגל הזה תואם לפרמטר notifyForDescendants שמועבר אל registerContentObserver().

הערה: אי אפשר להשתמש ב-TriggerContentUri() בשילוב עם setPeriodic() או setPersisted(). כדי לעקוב באופן רציף אחרי שינויים בתוכן, צריך לתזמן JobInfo חדש לפני שה-JobService של האפליקציה מסיים לטפל בקריאה החוזרת האחרונה.

הקוד לדוגמה הבא מתזמן עבודה שתופעל כשהמערכת תדווח על שינוי ב-URI של התוכן, 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 של התוכן שצוינו, האפליקציה מקבלת קריאה חוזרת(callback) ואובייקט JobParameters מועבר ל-method‏ onStartJob() ב-MediaContentJob.class.

איך קובעים אילו רשויות תוכן הפעילו עבודה

ב-Android 7.0 (רמת API‏ 24) גם הורחב השימוש ב-JobParameters כדי לאפשר לאפליקציה לקבל מידע שימושי על רשויות התוכן ועל מזהי ה-URI שהפעילו את המשימה:

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 (רמת 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