הטמעת פעילויות

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

איור 1. אפליקציית הגדרות שמציגה פעילויות זו לצד זו.

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

אין צורך לבצע רפאקציה של הקוד כדי להטמיע פעילויות. אתם קובעים איך האפליקציה תציג את הפעילויות שלה – זה לצד זה או מוערמות – על ידי יצירת קובץ תצורה של XML או על ידי שליחת קריאות ל-API של Jetpack WindowManager.

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

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

הטמעת פעילות נתמכת ברוב המכשירים עם מסך גדול שפועלת בהם מערכת Android 12L (רמת API 32) ומעלה.

חלון משימה מפוצל

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

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

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

איור 2. שתי פעילויות זו לצד זו.

לחלופין, אפשר ליצור חלוקה של פעילות שממלאת את כל חלון המשימות על ידי הפעלת פעילות חדשה לצד:

איור 3. פעילות א' מתחילה את פעילות ב' בצד.

פעילויות שכבר נמצאות מפוצלות ושיתוף חלון משימות יכולות לפעול פעילויות אחרות בדרכים הבאות:

  • בצד, מעל פעילות אחרת:

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

    איור 5. פעילות ב' מתחילה את פעילות ג' לצד מתפצלים הצידה.
  • להפעיל פעילות במקום העליון, כלומר באותה רשימת פעילויות (activity stack):

    איור 6. פעילות ב' מתחילה את פעילות ג' ללא דגלים נוספים של כוונה.
  • הפעלת פעילות בחלון מלא באותה משימה:

    איור 7. פעילות א' או פעילות ב' מתחילות את פעילות ג', שממלאת את חלון המשימות.

ניווט חזרה

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

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

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

לניווט מבוסס-תנועות:

  • Android 14 (רמת API 34) ומטה – האירוע הקודם נשלח אל הפעילות שבה התרחשה התנועה. כשמשתמשים מחליקים מצד ימין של המסך, אירוע החזרה לאחור נשלח לפעילות בחלונית הימנית של החלון המפוצל. כשמשתמשים מחליקים מהצד השמאלי של האירוע 'הקודם' נשלח לפעילות שבחלונית שמשמאל.

  • Android בגרסה 15 (רמת API 35) ואילך

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

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

פריסת חלוניות מרובות

Jetpack WindowManager מאפשר ליצור פעילות שמוטמעת בתצוגה עם כמה חלונות במכשירים עם מסך גדול עם Android 12L‏ (API ברמה 32) ואילך, ובמכשירים מסוימים עם גרסאות קודמות של הפלטפורמה. אפליקציות קיימות שמבוססות על כמה פעילויות במקום על קטעים או על פריסות שמבוססות על תצוגות, כמו SlidingPaneLayout, יכולות לספק חוויית משתמש משופרת במסך גדול בלי לבצע רפאקציה של קוד המקור.

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

איור 8. שתי פעילויות שהתחילו בו-זמנית בפריסה עם כמה חלונות.

פיצול מאפיינים

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

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

  • splitRatio: הגדרת הפרופורציות של הקונטיינר. הערך הוא מספר נקודה צפה (float) בטווח הפתוח (0.0, 1.0).
  • splitLayoutDirection: מציין את אופן הפריסה של הקונטיינרים המפוצלים ביחס זה לזה. הערכים כוללים:
    • ltr: משמאל לימין
    • rtl: מימין לשמאל
    • locale: הערך ltr או rtl נקבע לפי הגדרת השפה והאזור

דוגמאות מופיעות בקטע הגדרת XML.

לכללים שנוצרו באמצעות ממשקי ה-API של windowManager, יש ליצור SplitAttributes אובייקט עם SplitAttributes.Builder וקריאה ל-builder הבא אמצעי תשלום:

  • setSplitType(): הגדרת הפרופורציות של המאגרים המפוצלים. אפשר לעיין במאמר SplitAttributes.SplitType כדי לראות אילו ארגומנטים חוקיים, כולל השיטה SplitAttributes.SplitType.ratio().
  • setLayoutDirection(): הגדרת הפריסה של הקונטיינרים. הערכים האפשריים מפורטים במאמר SplitAttributes.LayoutDirection.

דוגמאות מופיעות בקטע WindowManager API.

איור 9. שתי חלוקות של פעילות שממוקמות מימין לשמאל, אבל עם יחסי חלוקה שונים.

placeholders

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

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

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

עם זאת, המאפיין stickyPlaceholder של שיטת SplitPlaceholderRule או setSticky() של SplitPlaceholder.Builder יכול לבטל את התנהגות ברירת המחדל. כשהערך של המאפיין או השיטה הוא true, המערכת מציגה את placeholder כפעילות העליונה בחלון המשימות כשהגודל של המסך משתנה מחלון עם שתי חלוניות לחלון עם חלונית אחת (לדוגמה, ראו הגדרת פיצול).

איור 11. מכשירים מתקפלים במצב מתקפל ובמצב לא מתקפל. הפעילות של placeholder היא דביקה.

שינויים בגודל החלון

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

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

אפשר להצמיד פעילויות כי WindowManager מסדר את הפעילויות בחלונית המשנית לפי סדר 'z' מעל הפעילויות בחלונית הראשית.

פעילויות מרובות בחלונית המשנית

פעילות ב' מפעילה את פעילות ג' במקום ללא דגלים נוספים של כוונת השימוש:

חלוקת פעילות שמכילה את הפעילויות A, B ו-C עם ערימה של C
          בחלק העליון של B.

התוצאה היא סדר z של הפעילויות באותה משימה:

מקבץ פעילויות משני שמכיל פעילות C מוערמת מעל B.
          ערימה משנית מוערם מעל ערימת הפעילות של החברים
          שמכילה את פעילות A.

לכן, בחלון משימות קטן יותר, האפליקציה מצטמצמת לפעילות אחת עם C בחלק העליון של הסטאק:

חלון קטן שמוצגת בו רק פעילות ג'.

ניווט חזרה בחלון הקטן יותר מאפשר לנווט בין הפעילויות שממוזערות זו על גבי זו.

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

פלחים מוערמים

פעילות ב' מתחילה את פעילות ג' לצד ומזיזה את הפיצול הצידה:

חלון משימות שבו מוצגות הפעילויות א' ו-ב', ואז הפעילויות ב' ו-ג'.

התוצאה היא סדר z הבא של הפעילויות באותה משימה:

פעילויות א', ב' ו-ג' ב-stack אחד. ערימה של הפעילויות
          בסדר הבא, מלמעלה למטה: C, B, A.

בחלון משימות קטן יותר, האפליקציה מצטמצמת לפעילות אחת עם C בראש:

חלון קטן שבו מוצגת רק הפעילות ג'.

כיוון לאורך קבוע

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

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

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

איור 13. פעילות א' בפורמט לאורך קבוע מתחילה את פעילות ב' בצד.

יש להוסיף תמיד את android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED לנכס בקובץ המניפסט של האפליקציה כדי ליידע את המכשירים שהאפליקציה תומכת בהם הטמעת פעילות (למידע נוסף על הגדרת הפיצול ). לאחר מכן, במכשירים בהתאמה אישית של יצרן ציוד מקורי אפשר יהיה לקבוע אם להציג בפורמט letterbox פעילויות בפורמט לאורך קבוע.

הגדרת פיצול

כללים לפיצול מגדירים פיצולי פעילות. מגדירים את כללי הפיצול בקובץ תצורה של XML או באמצעות קריאות ל-API של WindowManager ב-Jetpack.

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

מבצעים את הפעולות הבאות:

  1. הוספת התלות האחרונה של ספריית windowManager ברמת המודול של האפליקציה קובץ build.gradle, לדוגמה:

    implementation 'androidx.window:window:1.1.0-beta02'

    ספריית windowManager מספקת את כל הרכיבים שנדרשים לפעילות הטמעה אוטומטית.

  2. מודיעים למערכת שהאפליקציה הטמיעה הטמעת פעילות.

    מוסיפים את המאפיין android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED לאלמנט <application> בקובץ המניפסט של האפליקציה ומגדירים את הערך כ-true, לדוגמה:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    ב-WindowManager גרסה 1.1.0-alpha06 ואילך, הטמעת פעילות מפוצלת מושבתים אלא אם הנכס מתווסף למניפסט ומוגדר כ-True.

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

הגדרות XML

כדי ליצור הטמעה של הטמעת פעילויות שמבוססת על XML:

  1. יוצרים קובץ משאבים בפורמט XML שמבצע את הפעולות הבאות:

    • הגדרה של פעילויות שחולקות פיצול
    • הגדרת אפשרויות הפיצול
    • יוצר placeholder למאגר המשני של הפיצול כשהתוכן לא זמין
    • מציין פעילויות שאף פעם לא צריכות להיות חלק מחלוק

    לדוגמה:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. יצירת מאתחל.

    הרכיב WindowManager‏ RuleController מנתח את קובץ התצורה של ה-XML ומאפשר למערכת לגשת לכללים. ספריית Startup של Jetpack‏ Initializer הופכת את קובץ ה-XML לזמין ל-RuleController בזמן הפעלת האפליקציה, כך שהכללים ייכנסו לתוקף כשהפעילויות יתחילו.

    כדי ליצור מאתחלת:

    1. מוסיפים את יחסי התלות העדכניים ביותר של ספריית Jetpack Startup לקובץ build.gradle ברמת המודול, לדוגמה:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. יוצרים מחלקה שמטמיעה את הממשק Initializer.

      ה-initializer הופך את כללי הפיצול לזמינים ל-RuleController על ידי העברת המזהה של קובץ התצורה של ה-XML (main_split_config.xml) לשיטה RuleController.parseRules().

      Kotlin

      class SplitInitializer : Initializer<RuleController> {
      
          override fun create(context: Context): RuleController {
              return RuleController.getInstance(context).apply {
                  setRules(RuleController.parseRules(context, R.xml.main_split_config))
              }
          }
      
          override fun dependencies(): List<Class<out Initializer<*>>> {
              return emptyList()
          }
      }

      Java

      public class SplitInitializer implements Initializer<RuleController> {
      
           @NonNull
           @Override
           public RuleController create(@NonNull Context context) {
               RuleController ruleController = RuleController.getInstance(context);
               ruleController.setRules(
                   RuleController.parseRules(context, R.xml.main_split_config)
               );
               return ruleController;
           }
      
           @NonNull
           @Override
           public List<Class<? extends Initializer<?>>> dependencies() {
               return Collections.emptyList();
           }
      }
  3. יוצרים ספק תוכן להגדרות הכללים.

    מוסיפים את androidx.startup.InitializationProvider לקובץ המניפסט של האפליקציה בתור <provider>. מוסיפים הפניה להטמעה של המפעיל RuleController, SplitInitializer:

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- Make SplitInitializer discoverable by InitializationProvider. -->
        <meta-data android:name="${applicationId}.SplitInitializer"
            android:value="androidx.startup" />
    </provider>
    

    InitializationProvider מזהה ומאתחל את SplitInitializer לפני קוראים ל-method onCreate() של האפליקציה. כתוצאה מכך, כללי הפיצול כשהפעילות העיקרית של האפליקציה מתחילה.

WindowManager API

ניתן לבצע הטמעת פעילות באופן פרוגרמטי באמצעות מספר קטן של ממשקי API שיחות. צריך לבצע את הקריאות בשיטה onCreate() של תת-סוג של Application כדי לוודא שהכללים ייכנסו לתוקף לפני שהפעילויות יופעלו.

כדי ליצור חלוקה פרוגרמטית של פעילות:

  1. יוצרים כלל פיצול:

    1. יצירת SplitPairFilter שמזהה את הפעילויות שחולקות את הפיצול:

      Kotlin

      val splitPairFilter = SplitPairFilter(
         ComponentName(this, ListActivity::class.java),
         ComponentName(this, DetailActivity::class.java),
         null
      )

      Java

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );
    2. מוסיפים את המסנן לקבוצת מסננים:

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. יוצרים מאפייני פריסה לפיצול:

      Kotlin

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()

      Java

      final SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

      SplitAttributes.Builder יוצר אובייקט שמכיל פריסה :

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

      Kotlin

      val splitPairRule = SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build()

      Java

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();

      הפונקציה SplitPairRule.Builder יוצרת ומגדירה את הכלל:

      • filterSet: מכיל מסננים של צמדי פילוח שמאפשרים לקבוע מתי להחיל את הכלל על ידי זיהוי פעילויות שיש להן פילוח משותף.
      • setDefaultSplitAttributes(): החלת מאפייני פריסה על הכלל.
      • setMinWidthDp(): הגדרת רוחב המסך המינימלי (בפיקסלים בלתי תלויים בדחיסות, dp) שמאפשר פיצול.
      • setMinSmallestWidthDp(): מגדיר את הערך המינימלי (ב-dp) שצריך להיות למאפיין הקטן מבין שני מאפייני התצוגה כדי לאפשר פיצול, ללא קשר לכיוון המכשיר.
      • setMaxAspectRatioInPortrait(): הגדרת יחס הגובה-רוחב המקסימלי של התצוגה (גובה:רוחב) בכיוון לאורך שבו מוצגים חלוקות הפעילות. אם יחס הגובה-רוחב של תצוגה בפורמט לאורך חורג מיחס הגובה-רוחב המקסימלי, הפיצולים יושבתו ללא קשר לרוחב התצוגה. הערה: ערך ברירת המחדל הוא 1.4, וכתוצאה מכך הפעילויות תופסות את כל חלון המשימות ברוב הטאבלטים כשהמסך בכיוון לאורך. מידע נוסף זמין במאמרים SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT ו-setMaxAspectRatioInLandscape(). ערך ברירת המחדל של לרוחב ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): מגדיר איך מסיימים הכול פעילויות במאגר התגים המשני משפיעות על הפעילויות מאגר ראשי. הערך NEVER מציין שהמערכת לא צריכה לסיים את הפעילויות הראשיות כשכל הפעילויות בקונטיינר המשני מסתיימות (ראו סיום פעילויות).
      • setFinishSecondaryWithPrimary(): ההגדרה קובעת איך סיום כל הפעילויות בקונטיינר הראשי משפיע על הפעילויות בקונטיינר המשני. הערך ALWAYS מציין שהמערכת תמיד צריכה לסיים את הפעילויות בקונטיינר המשני כשכל הפעילויות בקונטיינר הראשי מסתיימות (ראו סיום פעילויות).
      • setClearTop(): מציין אם כל הפעילויות המאגר המשני מסתיים כשפעילות חדשה מופעלת מאגר התגים. הערך false מציין שהפעילויות החדשות נערמות מעל הפעילויות שכבר נמצאות במיכל המשני.
    5. מקבלים את מופע היחיד של WindowManager‏ RuleController ומוסיפים את הכלל:

      Kotlin

        val ruleController = RuleController.getInstance(this)
        ruleController.addRule(splitPairRule)
        

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. יוצרים placeholder לקונטיינר המשני כשהתוכן לא זמין:

    1. יוצרים את השדה ActivityFilter שמזהה את הפעילות שאיתה המקום הזמין לשמירת תוכן משתף את החלוקה של חלון המשימות:

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. מוסיפים את המסנן לקבוצת מסננים:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. יוצרים SplitPlaceholderRule:

      Kotlin

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            Intent(context, PlaceholderActivity::class.java)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build()

      Java

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();

      הפונקציה SplitPlaceholderRule.Builder יוצרת ומגדירה את הכלל:

      • placeholderActivityFilterSet: מכיל מסנני פעילות שמאפשרים לקבוע מתי להחיל את הכלל על ידי זיהוי הפעילויות שפעילות placeholder משויכת אליהן.
      • Intent: מציין את ההפעלה של הפעילות מסוג placeholder.
      • setDefaultSplitAttributes(): המערכת מחילה מאפייני פריסה על הכלל.
      • setMinWidthDp(): הגדרת רוחב התצוגה המינימלי (בפיקסלים שלא תלויים בדחיסות, dp) שמאפשר פיצול.
      • setMinSmallestWidthDp(): הגדרת הערך המינימלי (ב-dp) שיוצג הערך הקטן מבין השניים המימדים חייבים לאפשר פיצול ללא קשר למכשיר לכיוון מסוים.
      • setMaxAspectRatioInPortrait(): הגדרת היחס הגובה-רוחב המקסימלי של התצוגה (גובה:רוחב) בכיוון לאורך שבו מוצגים חלוקות הפעילות. הערה: ערך ברירת המחדל הוא 1.4, וכתוצאה מכך הפעילויות ממלאות את חלון המשימות ברוב הטאבלטים בכיוון לאורך. עוד באותו הקשר SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT ו- setMaxAspectRatioInLandscape() ערך ברירת המחדל לרוחב הוא ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder(): מגדירה איך סיום הפעילות מסוג placeholder משפיע על הפעילויות במאגר הראשי. הערך ALWAYS מציין שהמערכת תמיד צריכה לסיים את הפעילויות במאגר הראשי כשהסמל החלופי מסתיים (ראו סיום פעילויות).
      • setSticky(): קובע אם הפעילות של ה-placeholder מופיעה מעל מקבץ הפעילות במסכים קטנים ה-placeholder הופיע לראשונה בפיצול עם מספיק מינימום רוחב.
    4. מוסיפים את הכלל ל-windowManager RuleController:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. ציון פעילויות שאף פעם לא יכולות להיות חלק מפיצול:

    1. יוצרים ActivityFilter שמזהה פעילות שצריכה תמיד לתפוס את כל אזור התצוגה של המשימות:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. מוסיפים את המסנן לקבוצת מסננים:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. יוצרים ActivityRule:

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();

      הפונקציה ActivityRule.Builder יוצרת ומגדירה את הכלל:

      • expandedActivityFilterSet: מכיל מסנני פעילות שמזהים את הפעילויות שרוצים להחריג מהחלוקות, ומאפשרים לקבוע מתי להחיל את הכלל.
      • setAlwaysExpand(): קובע אם הפעילות תמלא את כל חלון המשימות.
    4. מוסיפים את הכלל ל-WindowManager‏ RuleController:

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

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

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

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

איור 14. אפליקציית ההגדרות (תפריט מימין) עם בורר הטפטים כפעילות מוטמעת (בצד ימין).

מודל אמון

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

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

מארחים מהימנים

כדי לאפשר לאפליקציות אחרות להטמיע את הפעילות מהאפליקציה שלכם ולשלוט באופן מלא בהצגתה, צריך לציין את אישור ה-SHA-256 של אפליקציית המארח במאפיין android:knownActivityEmbeddingCerts של האלמנטים <activity> או <application> בקובץ המניפסט של האפליקציה.

מגדירים את הערך של android:knownActivityEmbeddingCerts כמחרוזת:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

או, כדי לציין אישורים מרובים, מערך של מחרוזות:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

שמפנה למשאב כמו זה:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

בעלי אפליקציות יכולים לקבל תקציר אישורי SHA על ידי הפעלת Gradle משימה אחת (signingReport). תקציר האישורים הוא טביעת האצבע SHA-256 ללא את הנקודתיים. מידע נוסף זמין במאמרים הרצת דוח חתימה ואימות הלקוח.

מארחים לא מהימנים

כדי לאפשר לכל אפליקציה להטמיע את הפעילויות של האפליקציה שלכם ולשלוט בהצגתן, צריך לציין את המאפיין android:allowUntrustedActivityEmbedding ברכיבים <activity> או <application> במניפסט של האפליקציה, לדוגמה:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

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

אימות מותאם אישית

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

אפשר להשתמש בשיטה ActivityEmbeddingController#isActivityEmbedded() מהספרייה Jetpack WindowManager כדי לבדוק אם מארח הטמיע את הפעילות שלכם, לדוגמה:

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

הגבלת גודל מינימלי

מערכת Android מחילה על פעילויות מוטמעות את הגובה והרוחב המינימליים שצוינו באלמנט <layout> במניפסט של האפליקציה. אם אפליקציה ללא ציון גובה ורוחב מינימליים, ערכי ברירת המחדל של המערכת יחולו sw220dp.

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

<activity-alias>

כדי שהטמעת פעילויות מהימנה או לא מהימנה תפעל עם רכיב <activity-alias>, android:knownActivityEmbeddingCerts או צריך להחיל את android:allowUntrustedActivityEmbedding על פעילות היעד ולא את הכינוי. המדיניות שמאמתת את האבטחה בשרת המערכת מבוססת על הדגלים שהוגדרו ביעד, ולא על הכינוי החלופי.

אפליקציית המארח

אפליקציות מארחות מטמיעות פעילויות חוצות-אפליקציות באותו אופן שבו הן מטמיעות להטמיע פעילות באפליקציה יחידה. SplitPairRule ו- SplitPairFilter או ActivityRule ו-ActivityFilter אובייקטים להגדיר פעילויות מוטמעות ופיצולים של חלונות משימות. כללי הפיצול הוגדרו באופן סטטי ב-XML או בזמן ריצה באמצעות Jetpack קריאות ל-WindManager API.

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

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

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

דוגמאות לפיצולים

פיצול ממסך מלא

איור 15. פעילות א' מתחילה את פעילות ב' בצד.

אין צורך בשיפור מבנה הקוד. אפשר לקבוע את ההגדרות של הפיצול באופן סטטי או בזמן ריצה, ואז מפעילים את הפונקציה Context#startActivity() ללא פרמטרים נוספים.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

פיצול כברירת מחדל

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

איור 16. פיצול שנוצר על ידי פתיחת שתי פעילויות בו-זמנית. פעילות אחת היא placeholder.

כדי ליצור פיצול עם placeholder, צריך ליצור placeholder ולשייך אותו אל הפעילות העיקרית:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

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

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

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

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

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

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

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

במקום זאת, אפשר לסיים את שתי הפעילויות בו-זמנית באמצעות מאפיין finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

פרטים נוספים זמינים בקטע מאפייני הגדרה.

פעילויות מרובות בקונטיינרים מפוצלים

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

איור 18. הפעילות נפתחה בחלונית המשנית של חלון המשימה.

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

הפעילות ברמת הפירוט המשני ממוקמת מעל הפעילות ברמת הפירוט, ומסתירה אותה:

המשתמש יוכל לחזור לרמת הפירוט הקודמת על ידי ניווט חזרה דרך המקבץ:

איור 19. הפעילות הוסרה מהחלק העליון של המקבץ.

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

פעילויות במשימה חדשה

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

איור 20. מתחילים את הפעילות ג' במשימה חדשה מתוך הפעילות ב'.

החלפת פעילות

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

איור 21. פעילות הניווט ברמה העליונה בחלונית הראשית מחליפה את פעילויות היעד בחלונית המשנית.

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

במקרים כאלה, צריך להסיר את מסך א' מהמקבץ האחורי.

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

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

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

מספר פילוחים

אפליקציות יכולות לספק ניווט מעמיק ברמות מרובות על ידי הפעלת פעילויות נוספות בצד.

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

איור 22. פעילות ב' מתחילה את פעילות ג' בצד.

סטאק החזרה מכיל את כל הפעילויות שנפתחו בעבר, כך שמשתמשים יכולים לנווט אל חלוקת ה-A/B אחרי שהם מסיימים את הפעילות C.

פעילויות א&#39;, ב&#39; וג&#39; במקבץ. הפעילויות מקובצות
          בסדר הבא, מלמעלה למטה: C, B, A.

כדי ליצור חלוקה חדשה, מפעילים את הפעילות החדשה לצד התוכן הקיים מאגר משני. להצהיר על התצורות של הפיצולים A/B ו-B/C ומפעילים את פעילות ג' בדרך כלל מ-B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

תגובה לשינויים במצב מפוצל

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

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

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

איור 24. רכיבי ממשק משתמש כפולים בחלוקת הפעילות.

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

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

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

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

חלון מודאלי

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

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

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

סיום הפעילויות

המשתמשים יכולים לסיים את הפעילויות בכל צד של החלוקה על ידי החלקה מהקצה של המסך:

איור 25. תנועת החלקה לסיום פעילות ב'.
איור 26. תנועת החלקה מסיימת את פעילות א'.

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

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

מאפייני תצורה

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

  • window:finishPrimaryWithSecondary — איך סיום כל הפעילויות בקונטיינר המשני משפיע על הפעילויות בקונטיינר הראשי
  • window:finishSecondaryWithPrimary – איך לסיים את כל הפעילויות ב- המאגר הראשי משפיע על הפעילויות במאגר המשני

דוגמאות לערכים אפשריים של המאפיינים:

  • always – תמיד צריך לסיים את הפעילויות בקונטיינר המשויך
  • never – אף פעם לא מסיימים את הפעילויות במאגר התגים המשויך
  • adjacent – מסיימים את הפעילויות במאגר המשויך כאשר שני המאגרים מוצגים בסמוך זה לזה, אבל לא כאשר שני קונטיינרים מוערמים

לדוגמה:

<SplitPairRule
    &lt;!-- Do not finish primary container activities when all secondary container activities finish. --&gt;
    window:finishPrimaryWithSecondary="never"
    &lt;!-- Finish secondary container activities when all primary container activities finish. --&gt;
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

הגדרות ברירת המחדל

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

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

פיצול שמכיל את הפעילויות A ו-B. הפעילות של A מסתיימת, ו-B תופסת את כל החלון.

פיצול שמכיל את הפעילויות A ו-B. ב&#39; מסיים את הפעולה, ומשאירים את A
          תופסת את כל החלון.

מסיימים את הפעילויות ביחד

לסיים את הפעילויות בקונטיינר הראשי באופן אוטומטי כשכל הפעילויות בקונטיינר המשני מסתיימות:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

חלוקה שמכילה את הפעילויות א&#39; ו-ב&#39;. ב&#39; מסתיים, וגם
          מסיים א&#39; ומשאירים את חלון המשימה ריק.

פיצול שמכיל את הפעילויות A ו-B. משימה א&#39; מסתיימת, ומשימה ב&#39; נשארת לבד בחלון המשימות.

השלמת הפעילויות במאגר המשני באופן אוטומטי לאחר פעילויות בסיום המאגר הראשי:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

פיצול שמכיל את הפעילויות A ו-B. המשימה א&#39; מסתיימת, וכך גם המשימה ב&#39;, וחלון המשימות נשאר ריק.

חלוקה שמכילה את הפעילויות א&#39; ו-ב&#39;. ב&#39; הסתיים, א&#39; נשאר ללא שינוי
          בחלון המשימה.

לסיים פעילויות ביחד כשכל הפעילויות בקונטיינר הראשי או המשני מסתיימות:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

חלוקה שמכילה את הפעילויות א&#39; ו-ב&#39;. A מסתיים, וגם
          מסיים ב&#39; ומשאירים את חלון המשימה ריק.

חלוקה שמכילה את הפעילויות א&#39; ו-ב&#39;. ב&#39; מסתיים, וגם
          מסיים א&#39; ומשאירים את חלון המשימה ריק.

סיום פעילויות מרובות בקונטיינרים

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

לדוגמה, אם שתי פעילויות נמצאות במאגר המשני, C מעל B:

רשימת הפעילויות המשנית שמכילה את הפעילות C, שממוקמת מעל הפעילות B, ממוקמת מעל רשימת הפעילויות הראשית שמכילה את הפעילות A.

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

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

סיום הפעילות העליונה שומר על הפיצול.

חלוקה שבה הפעילות א&#39; נמצאת במיכל הראשי והפעילויות ב&#39; ו-ג&#39; נמצאות במיכל המשני, כאשר הפעילות ג&#39; מוערמת מעל הפעילות ב&#39;. C מסיים, A ו-B
          חלוקת פעילות.

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

חלוקה שבה הפעילות א&#39; נמצאת במיכל הראשי והפעילויות ב&#39; ו-ג&#39; נמצאות במיכל המשני, כאשר הפעילות ג&#39; מוערמת מעל הפעילות ב&#39;. משתמש B מסיים, והפעילות מחולקת בין משתמשים A ו-C.

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

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

פיצול עם פעילות א&#39; במאגר הראשי ופעילויות ב&#39; ו-ג&#39; ב
          מאגר משני, C מוערם מעל B. סיום,
          סיום ב&#39; ו-ג&#39;.

כשהחלוקה מוגדרת לסיום של המודעה הראשית והמשנית יחד:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

פיצול עם פעילות א&#39; במאגר הראשי ופעילויות ב&#39; ו-ג&#39; ב
          משנית, C מוערם מעל B. משתמש C מסיים, והפעילות מחולקת בין משתמשים A ו-B.

חלוקה שבה הפעילות א&#39; נמצאת במיכל הראשי והפעילויות ב&#39; ו-ג&#39; נמצאות במיכל המשני, כאשר הפעילות ג&#39; מוערמת מעל הפעילות ב&#39;. ב&#39; מסיים, A ו-C
          חלוקת פעילות.

חלוקה שבה הפעילות א&#39; נמצאת במיכל הראשי והפעילויות ב&#39; ו-ג&#39; נמצאות במיכל המשני, כאשר הפעילות ג&#39; מוערמת מעל הפעילות ב&#39;. סיום, סיום ב&#39;
          סי.

שינוי מאפייני הפיצול בזמן הריצה

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

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

מאפייני חלוקה דינמיים

ב-Android 15 (רמת API 35) ואילך, עם תמיכה ב-Jetpack WindowManager 1.4 ואילך, יש תכונות דינמיות שמאפשרות לקבוע את חלוקת הטמעת הפעילות, כולל:

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

הרחבת חלוניות

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

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

  1. יוצרים מופע של DividerAttributes

  2. מתאימים אישית את מאפייני ההפרדה:

    • color: הצבע של מפריד החלונית שניתן לגרור.

    • widthDp: הרוחב של מפריד החלונית שניתן לגרור. הגדרה לערך WIDTH_SYSTEM_DEFAULT כדי לאפשר למערכת לזהות את המחיצה רוחב.

    • טווח גרירה: האחוז המינימלי של המסך שכל אחד מהחלונות יכול לתפוס. הטווח יכול להיות בין 0.33 ל-0.66. מגדירים את הערך ל-DRAG_RANGE_SYSTEM_DEFAULT כדי לאפשר למערכת לקבוע את טווח הגרירה.

Kotlin

val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)

if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    )
}
val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

Java

SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      new DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(ContextCompat.getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    );
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();

הצמדת פעילות

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

כדי להפעיל את הצמדת הפעילות באפליקציה:

  1. מוסיפים לחצן לקובץ הפריסה של הפעילות שרוצים להצמיד, למשל הפעילות של פרטי הפריסה של רשימה:

    <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/detailActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/white"
     tools:context=".DetailActivity">
    
    <TextView
       android:id="@+id/textViewItemDetail"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="36sp"
       android:textColor="@color/obsidian"
       app:layout_constraintBottom_toTopOf="@id/pinButton"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
    
    <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/pinButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/pin_this_activity"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. בשיטה onCreate() של הפעילות, הגדרת ה-listen onclick ב- לחצן:

    Kotlin

    pinButton = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        val splitAttributes: SplitAttributes = SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build()
    
        val pinSplitRule = SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build()
    
        SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule)
    }

    Java

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) => {
        SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
    
        SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();
    
        SplitController.getInstance(getApplicationContext()).pinTopActivityStack(getTaskId(), pinSplitRule);
    });

עמעום המסך במסך מלא

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

ב-windowManager 1.4 ואילך, חלון האפליקציה כולו מעומעם כברירת מחדל כאשר נפתחת תיבת דו-שיח (ראו EmbeddingConfiguration.DimAreaBehavior.ON_TASK).

כדי להכהות רק את המאגר של הפעילות שפתחה את תיבת הדו-שיח, משתמשים ב-EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

חילוץ פעילות מפיצול לחלון מלא

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

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

יש תמיכה בהטמעת פעילות ב-Android 12L‏ (רמת API 32) ואילך, אבל היא זמינה גם במכשירים מסוימים עם גרסאות פלטפורמה קודמות. כדי לבדוק: בסביבת זמן הריצה של התכונה, השתמשו נכס SplitController.splitSupportStatus או אמצעי תשלום SplitController.getSplitSupportStatus():

Kotlin

if (SplitController.getInstance(this).splitSupportStatus ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Java

if (SplitController.getInstance(this).getSplitSupportStatus() ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

אם אין תמיכה בחלוקות, הפעילויות יופעלו מעל ל-activity stack (בהתאם למודל ההטמעה ללא פעילות).

מניעת שינוי מברירת המחדל של המערכת

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

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

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

שם המאפיין מוגדר ב-Jetpack windowManager WindowProperties לאובייקט. מגדירים את הערך כ-false אם האפליקציה מטמיעה הטמעת פעילות, או אם רוצים למנוע בדרך אחרת את החלת הכללים של הטמעת הפעילות על האפליקציה. מגדירים את הערך כ-true כדי לאפשר למערכת להחיל על האפליקציה הטמעת פעילות שהוגדרה על ידי המערכת.

מגבלות, הגבלות וסייגים

  • רק אפליקציית המארח של המשימה, שזוהתה בתור הבעלים של פעילות הבסיס במשימה, יכולה לארגן ולטמיע פעילויות אחרות במשימה. אם פעילויות שתומכות בהטמעה ובפיצולים פועלות במשימה ששייכת לאפליקציה אחרת, הטמעה ופיצולים לא יפעלו הפעילויות האלה.
  • אפשר לארגן פעילויות רק בתוך משימה אחת. איך מתחילים פעילות במשימה חדשה, היא תמיד מציגה בחלון מורחב חדש בחלקים הקיימים.
  • אפשר לארגן ולחלק רק פעילויות באותו תהליך. קריאה חוזרת (callback) של SplitInfo מדווחת רק על פעילויות ששייכות לאותה כתובת מכיוון שאין דרך לדעת על פעילויות תהליכים.
  • כל כלל פעילות של זוג או יחיד חל רק על הפעלות של פעילות ש מתרחשת אחרי שהכלל נרשם. כרגע אין דרך לעדכן פיצולים קיימים או את המאפיינים החזותיים שלהם.
  • תצורת המסנן של הצמד המפוצל חייבת להתאים לאובייקטים של Intent שמשמשים כאשר ולהשיק את הפעילויות שלו. ההתאמה מתרחשת בנקודה שבה מתחילה פעילות חדשה מתהליך האפליקציה, כך שיכול להיות שהיא לא תדע על שמות הרכיבים שמתקבלים מאוחר יותר בתהליך המערכת כשמשתמשים בכוונות משתמשים מרומזות. אם שם הרכיב לא ידוע בזמן ההשקה, אפשר להשתמש בתו אסימון ("*/*") ולבצע סינון על סמך פעולת הכוונה.
  • בשלב זה אין אפשרות להעביר פעילויות בין קונטיינרים או להעביר אותן לחלוקות או מהן אחרי שהן נוצרו. ספריית WindowManager יוצרת חלוקות רק כשמפעילים פעילויות חדשות עם כללים תואמים, והן נמחקות כשהפעילות האחרונה בקונטיינר המפוצל מסתיימת.
  • אפשר להפעיל מחדש פעילויות כשהגדרות האישיות משתנות. לכן, כשיוצרים או מסירים פיצול וערכי הגבול של הפעילות משתנים, הפעילות יכולה לעבור תהליך של השמדה מוחלטת של המכונה הקודמת ויצירה של מכונה חדשה. כתוצאה מכך, מפתחי אפליקציות צריכים להיזהר מדברים כמו משיקים פעילויות חדשות מקריאות חוזרות (callback) במחזור החיים.
  • כדי לתמוך בפעילות, המכשירים צריכים לכלול את הממשק של תוספי החלונות הטמעה אוטומטית. כמעט כל המכשירים עם מסך גדול שפועלת בהם מערכת Android 12L (רמת API) 32) ומעלה כוללים את הממשק. עם זאת, במכשירים מסוימים עם מסך גדול שלא יכולים להריץ כמה פעילויות, ממשק התוספים של החלונות לא מופיע. אם מכשיר עם מסך גדול לא תומך בריבוי חלונות במצב כזה, ייתכן שהוא לא תומך בהטמעת פעילויות.

מקורות מידע נוספים