מבוא לפעילויות

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

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

הקונספט של פעילויות

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

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

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

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

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

הגדרת קובץ המניפסט

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

הצהרה על פעילויות

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

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

מאפיין החובה היחיד של הרכיב הזה הוא android:name, שבו מציינים את שם המחלקה של הפעילות. אפשר גם להוסיף מאפיינים שמגדירים מאפיינים של פעילות, כמו תווית, סמל או ערכת נושא של ממשק המשתמש. מידע נוסף על המאפיינים האלה ועל מאפיינים אחרים מופיע במאמרי העזרה בנושא רכיב <activity>.

הצהרה על מסנני Intent

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

כדי להשתמש בתכונה הזו, צריך להצהיר על מאפיין <intent-filter> באלמנט <activity>. ההגדרה של הרכיב הזה כוללת רכיב <action>, ואפשרותית רכיב <category> או רכיב <data>. השילוב של הרכיבים האלה מאפשר לציין את סוג הכוונה שהפעילות יכולה להגיב לה. לדוגמה, בקטע הקוד הבא אפשר לראות איך להגדיר פעילות ששולחת נתוני טקסט ואימייל, ומקבלת בקשות מפעילויות אחרות לעשות זאת:

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
   <intent-filter>
        <action android:name="android.intent.action.SENDTO" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="mailto" />
    </intent-filter>
</activity>

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

fun composeEmail(addresses: Array<String>, subject: String) {
    val intent = Intent(Intent.ACTION_SENDTO).apply {
        data = Uri.parse("mailto:") // Only email apps handle this.
        putExtra(Intent.EXTRA_EMAIL, addresses)
        putExtra(Intent.EXTRA_SUBJECT, subject)
    }
    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    }
}

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

טיפול בכוונות נכנסות

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

class ExampleActivity : ComponentActivity() {
  private val viewModel: MyViewModel by viewModels()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleIntent(intent)
    setContent {
      ComposeApp(viewModel)
    }
  }

  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    handleIntent(intent)
  }

  private fun handleIntent(intent: Intent?) {
    when (intent?.action) {
      Intent.ACTION_SEND -> {
        if ("text/plain" == intent.type) {
          intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
            viewModel.handleText(it) // Update UI to reflect text being shared
          }
        } else if (intent.type?.startsWith("image/") == true) {
          (intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))?.let {
            viewModel.handleImage(it) // Update UI to reflect image being shared
          }
        }
      }

      Intent.ACTION_SEND_MULTIPLE -> {
          if (intent.type?.startsWith("image/") == true) {
              intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri::class.java)?.let {
                  viewModel.handleMultipleImages(it) // Update UI to reflect multiple images being shared
              }
          } else {
              // Handle other types
          }
      }

      else -> {
          // Handle other intents
      }
    }
  }
}

הצהרה על הרשאות

אפשר להשתמש בתג <activity> במניפסט כדי לקבוע אילו אפליקציות יכולות להתחיל פעילות מסוימת. פעילות אב לא יכולה להפעיל פעילות צאצא אלא אם לשתי הפעילויות יש את אותן הרשאות במניפסט שלהן. אם מגדירים רכיב <uses-permission> לפעילות אב, לכל פעילות צאצא צריך להיות רכיב <uses-permission> תואם.

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

<manifest>
<activity android:name="...."
   android:permission="com.google.socialapp.permission.SHARE_POST"

/>

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

<manifest>
   <uses-permission android:name="com.google.socialapp.permission.SHARE_POST" />
</manifest>

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

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

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

onCreate

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

באפליקציית Compose, משתמשים בקריאה החוזרת הזו כדי להגדיר את רכיב ה-Composable המארח באמצעות setContent, כמו שמוצג בהמשך:

class MyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text(text = stringResource(id = R.string.greeting))
        }
    }
}

כש-onCreate מסתיים, תמיד מתקבלת קריאה חוזרת ל-onStart.

onStart

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

onResume

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

הקריאה החוזרת onPause תמיד מתבצעת אחרי onResume.

onPause

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

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

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

אחרי ש-onPause מסיים את ההרצה, הקריאה החוזרת הבאה היא onStop או onResume, בהתאם למה שקורה אחרי שהפעילות עוברת למצב מושהה.

onStop

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

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

onRestart

המערכת מפעילה את הקריאה החוזרת הזו כשפעילות במצב Stopped עומדת להתחיל מחדש. ‫onRestart משחזר את מצב הפעילות מהרגע שבו היא הופסקה.

אחרי הקריאה החוזרת הזו תמיד תהיה קריאה חוזרת של onStart.

onDestroy

המערכת מפעילה את הקריאה החוזרת הזו לפני שהפעילות מושמדת.

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

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