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

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

כדי לפתור את הבעיה הזו, מערכת 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 כפרמטר של משימה. דוגמת הקוד הבאה מתזמנת הפעלה של שירות כשהמכשיר מתחבר לרשת סלולרית ברשת ונטען:

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 שמאפשר לתזמן משימות ברקע מובטחת השלמה, בין אם תהליך האפליקציה זמין ובין אם לא. מנהל עבודה בוחר את הדרך המתאימה להריץ את העבודה (ישירות בשרשור בתהליך האפליקציה, וגם שימוש ב-JobScheduler, FirebaseJobDispatcher או AlertManager) על סמך גורמים כמו רמת ה-API של המכשיר. בנוסף, WorkManager לא דורש את שירותי Play ומספק מספר תכונות מתקדמות, כמו שרשור של משימות ביחד או בדיקת סטטוס של משימה. למידה למידע נוסף, ראו WorkManager.

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

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

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

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

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

Uri[] getTriggeredContentUris()
מחזירה מערך של מזהי URI שהפעילו את המשימה. הערך יהיה null אם אין מזהי URI שהפעילו את המשימה (לדוגמה, המשימה בעקבות תאריך יעד או מסיבה אחרת), או מספר השינויים מספר מזהי ה-URI גדול מ-50.
String[] getTriggeredContentAuthorities()
מחזירה מערך מחרוזות של רשויות תוכן שהפעילו את המשימה. אם המערך המוחזר אינו null, יש להשתמש ב-getTriggeredContentUris() כדי לאחזר את הפרטים של מזהי ה-URI שהשתנו.

הקוד לדוגמה הבא מבטל את ה-method 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 (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