סקירה כללית של תהליכים ושרשורים

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

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

במסמך הזה מוסבר איך פועלים תהליכים ושרשורים באפליקציות ל-Android.

תהליכים

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

רשומת המניפסט של כל סוג של רכיב רכיב — <activity>, <service>, <receiver>, ו-<provider> — תומכת במאפיין android:process שיכול לציין שבה הרכיב רץ. אפשר להגדיר את המאפיין הזה כך שכל רכיב יפעל בתהליך עצמאי, או שרכיבים מסוימים ישתפו תהליך, ואחרים לא.

אפשר גם להגדיר android:process כך שרכיבים של אפליקציות שונות יפעלו באותו אופן בתנאי שהאפליקציות חולקות את אותו מזהה משתמש ב-Linux אותם אישורים.

<application> הרכיב תומך גם במאפיין android:process, שבו ניתן להשתמש כדי להגדיר ערך ברירת המחדל שחל על כל הרכיבים.

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

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

הפרטים על מחזור החיים של התהליך והקשר שלו למצבי האפליקציה מתוארים תהליכים ומחזור חיים של אפליקציה.

שרשורים

כאשר אפליקציה מופעלת, המערכת יוצרת שרשור ביצוע עבור האפליקציה, שנקרא ה-thread הראשי. השרשור הזה חשוב מאוד, כי הוא אחראי על שליחת אירועים אל את הווידג'טים המתאימים של ממשק המשתמש, כולל אירועי שרטוט. כמו כן כמעט תמיד השרשור שבו האפליקציה מקיימת אינטראקציה עם רכיבים מandroid.widget של ערכת הכלים לבניית ממשק משתמש ב-Android חבילות android.view. לכן, לפעמים ה-thread הראשי שנקרא UIthread. עם זאת, בנסיבות מיוחדות, יכול להיות שה-thread הוא לא ה-thread שלו בממשק המשתמש. אפשר לקרוא מידע נוסף במאמר שרשורים בהערות.

המערכת לא יוצרת שרשור נפרד לכל מופע של רכיב. הכול רכיבים שרצים באותו תהליך נוצרים ב-thread של ממשק המשתמש, וקריאות מערכת כל רכיב נשלח מהשרשור הזה. כתוצאה מכך, שיטות שמגיבות למערכת קריאות חוזרות (callback) – כמו onKeyDown() כדי לדווח על פעולות של משתמשים, או על שיטת קריאה חוזרת (callback) במחזור החיים – הן תמיד מופעלות בשרשור בממשק המשתמש של התהליך.

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

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

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

חשוב לזכור שערכת הכלים בממשק המשתמש ב-Android לא בטוחה בשרשור. אז אל תתחמכו בממשק המשתמש משרשור של עובדים. לבצע את כל הפעולות המניפולטיביות בממשק המשתמש דרך ממשק המשתמש. של שרשור. יש שני כללים במודל של שרשור יחיד ב-Android:

  1. אין לחסום את השרשור של ממשק המשתמש.
  2. אין לגשת לערכת הכלים לבניית ממשק משתמש ב-Android מחוץ לשרשור של ממשק המשתמש.

שרשורי עובדים

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

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

הדוגמה הבאה משתמשת ב-View.post(Runnable):

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // A potentially time consuming task.
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // A potentially time consuming task.
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

ההטמעה הזו בטוחה לשרשורים, כי הפעולה ברקע מתבצעת משרשור נפרד בזמן שה-ImageView תמיד עובר מניפולציה דרך ה-thread של ממשק המשתמש.

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

שיטות בטוחות לשרשורים

במצבים מסוימים, ל-methods שמטמיעים קוראים מכמה שרשורים. ולכן צריך לכתוב אותן כך שיהיו בטוחות לשרשורים.

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

לדוגמה, בעוד ששירות מסוים בוצעה קריאה ל-method onBind() מה-thread של ממשק המשתמש של התהליך של השירות, methods שהוטמעו באובייקט ש-onBind() מחזיר, כמו מחלקה משנית שמטמיעה שיטות קריאה לשירות מרוחק (RPC), קריאות לשרשורים בבריכה. שירות מסוים יכול לכלול יותר מלקוח אחד, ולכן יכול להיות שחלק משרשורי המאגר יכולים להיות מעורבים אותה method IBinder בו-זמנית, כך שהשיטות של IBinder חייבות להיות להיות בטוחות לשרשורים.

באופן דומה, ספק תוכן יכול לקבל בקשות לנתונים שמקורן בתהליכים אחרים. ContentResolver וגם ContentProvider ה-מחלקות מסתירות את הפרטים לגבי ניהול התקשורת בין תהליכים (IPC), אבל השיטות ContentProvider שמגיבות לבקשות האלה — השיטות query(), insert(), delete(), update(), ו-getType() – שנקראה מתוך מאגר של שרשורים בתהליך של ספק התוכן, לא בממשק המשתמש של התהליך. בגלל שיכול להיות שה-methods האלה ייקראו מכל מספר של תהליכונים במקביל, צריך להטמיע גם אותם כדי שיהיו בטוחים לשרשורים.

תקשורת בין תהליכים

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

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

כדי לבצע IPC, האפליקציה שלך צריכה לקשר לשירות באמצעות bindService(). אפשר לקרוא מידע נוסף במאמר סקירה כללית על השירותים.