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

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

כדי לפתור את הבעיה הזו, מערכת 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, כדי להתאים את האפליקציה שלך לשינויים האלה ההגבלות.

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

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

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

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

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

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

ההגבלות המדויקות שהוטלו נקבעות על ידי יצרן המכשיר. לדוגמה, בגרסאות build של AOSP שפועלות עם Android 9 (רמת API 28) ואילך, לאפליקציות שפועלות ברקע במצב 'מוגבל' יש את המגבלות הבאות:

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

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

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

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

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

הערה: מכשיר BroadcastReceiver שמשויך ל-Context.registerReceiver() ממשיך לקבל את השידור הזה בזמן שהאפליקציה פועלת.

תזמון משימות ברשת בחיבורים ללא חיוב לפי שימוש בנתונים

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

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

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

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

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

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

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

האפליקציה תמשיך לקבל קריאות חוזרות (callback) עד שהיא תצא או שהיא תתקשר 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) נוספו ל-JobInfo API השיטות הבאות:

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

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

הקוד לדוגמה הבא מתזמנ משימה להפעלה כשהמערכת מדווחת שינוי ב-URI של התוכן, MEDIA_URI:

KotlinJava
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 של התוכן שצוין, האפליקציה מקבלת קריאה חוזרת (callback) ואובייקט JobParameters מועבר לשיטה 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 שהפעילו את המשימה:

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

ביצוע אופטימיזציה נוספת לאפליקציה

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

הממשק הבא של הגשר לניפוי באגים ב-Android (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