הגבלות מערכת על משימות ברקע

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

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

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

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

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

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

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

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

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

תזמון עבודה בחיבורים ללא שימוש נמדד

כשיוצרים WorkRequest, צריך להוסיף NetworkType.UNMETERED Constraint.

fun scheduleWork(context: Context) {
    val workManager = WorkManager.getInstance(context)
    val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.UNMETERED)
                .build()
        )
        .build()

    workManager.enqueue(workRequest)
}

כשהתנאים של העבודה מתקיימים, האפליקציה מקבלת קריאה חוזרת (callback) להפעלת השיטה doWork() במחלקה Worker שצוינה.

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

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

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

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

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

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

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

WorkerParameters מאפשר לאפליקציה לקבל מידע שימושי על רשויות התוכן ומזהי ה-URI שהפעילו את העבודה:

List<Uri> getTriggeredContentUris()

מחזירה רשימה של כתובות URI שהפעילו את העבודה. הערך יהיה ריק אם אף URI לא הפעיל את העבודה (לדוגמה, העבודה הופעלה בגלל מועד אחרון או סיבה אחרת), או אם מספר ה-URI שהשתנו גדול מ-50.

List<String> getTriggeredContentAuthorities()

מחזירה רשימת מחרוזות של רשויות תוכן שהפעילו את העבודה. אם הרשימה שמוחזרת לא ריקה, משתמשים ב-getTriggeredContentUris() כדי לאחזר את הפרטים של כתובות ה-URI שהשתנו.

הקוד לדוגמה הבא מבטל את השיטה CoroutineWorker.doWork() ומתעד את רשויות התוכן ומזהי ה-URI שהפעילו את העבודה:

class MyWorker(
    appContext: Context,
    private val params: WorkerParameters
) : CoroutineWorker(appContext, params) {
    override suspend fun doWork(): Result {
        StringBuilder().apply {
            append("Media content has changed:\n")
            params.triggeredContentAuthorities
                .takeIf { it.isNotEmpty() }
                ?.let { authorities ->
                    append("Authorities: ${authorities.joinToString(", ")}\n")
                    append(params.triggeredContentUris.joinToString("\n"))
                } ?: append("(No content)")
            Log.i(TAG, toString())
        }
        return Result.success()
    }
}

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

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

יש כמה פקודות נוספות של 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

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

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