התחלת פעילות אחרת, בפעילות בתוך האפליקציה או בפעילות אחרת יישום, לא חייבת להיות פעולה חד-כיוונית. אפשר גם להתחיל פעילות ולקבל תוצאה בחזרה. לדוגמה, האפליקציה שלך יכולה להפעיל אפליקציית מצלמה מקבלים את התמונה שצולמה. לחלופין, תוכל להפעיל את אפליקציית 'אנשי קשר' שהמשתמש יוכל לבחור איש קשר, ואז לקבל את איש הקשר כתוצאה מכך.
אומנם הערכים הבסיסיים
startActivityForResult()
וגם
onActivityResult()
ממשקי API זמינים במחלקה Activity
בכל רמות ה-API,
מומלץ להשתמש בממשקי ה-API של תוצאות הפעילות שנוספו ב-AndroidX
Activity
ו-Fragment
כיתות.
ממשקי ה-API של תוצאות הפעילות מספקים רכיבים לרישום תוצאה, הפעלת הפעילות שיוצרת את התוצאה, וטיפול בתוצאה לאחר נשלחת על ידי המערכת.
רישום קריאה חוזרת (callback) לתוצאת פעילות
כשמתחילים פעילות לקבלת תוצאה מסוימת, זה אפשרי – וגם במקרים של הפעולות שדורשות צריכת זיכרון, כמו שימוש במצלמה, כמעט בטוחות — התהליך כולו והפעילות שלך תושמד בגלל מחסור בזיכרון.
לכן, ממשקי ה-API של תוצאות הפעילות מפרידים את התוצאה הקריאה החוזרת (callback) מהמקום שבו בקוד שלך הפעלת את הפעילות האחרת. כי הקריאה החוזרת של התוצאה צריכה להיות זמינה כשהתהליך והפעילות שלכם הקריאה החוזרת (callback) צריכה להירשם מחדש בכל פעם נוצרת פעילות, גם אם הלוגיקה של הפעלת הפעילות השנייה בלבד קורה על סמך קלט של משתמשים או לוגיקה עסקית אחרת.
במסגרת
ComponentActivity
או
Fragment
, תוצאת הפעילות
ממשקי API מספקים
registerForActivityResult()
ממשק API לרישום הקריאה החוזרת של התוצאה. registerForActivityResult()
לוקח
ActivityResultContract
וגם
ActivityResultCallback
ומחזירה
ActivityResultLauncher
,
שבו אתם משתמשים כדי להפעיל פעילות אחרת.
ActivityResultContract
מגדיר את סוג הקלט שנדרש כדי להפיק תוצאה
יחד עם סוג הפלט של התוצאה. ממשקי ה-API מספקים
חוזי ברירת מחדל
לפעולות בסיסיות מתוך כוונות, כמו צילום תמונה, בקשת הרשאות וכן הלאה
מופעלת. אפשר גם
ליצור חוזה בהתאמה אישית.
ActivityResultCallback
הוא ממשק method אחד עם
onActivityResult()
שלוקחת אובייקט מסוג הפלט שמוגדר
ActivityResultContract
:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri }
Java
// GetContent creates an ActivityResultLauncher<String> to let you pass // in the mime type you want to let the user select ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } });
אם יש לך מספר קריאות לתוצאות של פעילויות, וברצונך להשתמש
חוזים
או שרוצים להתקשר חזרה (callbacks) נפרדים, אפשר להפעיל את registerForActivityResult()
כמה פעמים
כדי לרשום כמה מופעים של ActivityResultLauncher
. צריך
קוראים לפונקציה registerForActivityResult()
באותו סדר עבור כל יצירה של
של חלק או פעילות כדי שתוצאות הבדיקה יישלחו אל
הקריאה החוזרת (callback) הנכונה.
ניתן להפעיל את registerForActivityResult()
לפני המקטע או הפעילות שלך
נוצר, שמאפשר להשתמש בו באופן ישיר כשאתם מצהירים על משתני חבר
למופעים של ActivityResultLauncher
שהוחזרו.
התחלת פעילות לקבלת תוצאה
אמנם registerForActivityResult()
רושם את הקריאה החוזרת, אבל לא
להשיק את הפעילות הנוספת ולהתחיל את הבקשה לקבלת תוצאה. במקום זאת,
הוא באחריות המכונה של ActivityResultLauncher
שהוחזרה.
אם קיים קלט, מרכז האפליקציות מקבל את הקלט שתואם לסוג
ActivityResultContract
השיחה תכף תחובר
launch()
מתחיל את התהליך של הפקת התוצאה. כשהמשתמש מסיים לבצע
פעילות לאחר מכן והחזרות, את onActivityResult()
לאחר מכן מבצעים את הפקודה ActivityResultCallback
, כפי שמוצג בדוגמה הבאה:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri } override fun onCreate(savedInstanceState: Bundle?) { // ... val selectButton = findViewById<Button>(R.id.select_button) selectButton.setOnClickListener { // Pass in the mime type you want to let the user select // as the input getContent.launch("image/*") } }
Java
ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } }); @Override public void onCreate(@Nullable Bundle savedInstanceState) { // ... Button selectButton = findViewById(R.id.select_button); selectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // Pass in the mime type you want to let the user select // as the input mGetContent.launch("image/*"); } }); }
גרסה עמוסה של
launch()
מאפשרת להעביר
ActivityOptionsCompat
נוסף לקלט.
קבלת תוצאת פעילות בכיתה נפרדת
המחלקות ComponentActivity
ו-Fragment
מטמיעות את
ActivityResultCaller
כדי לאפשר לך להשתמש בממשקי ה-API של registerForActivityResult()
, אפשר גם
לקבל את תוצאת הפעילות בקטגוריה נפרדת שלא מיישם
ActivityResultCaller
באמצעות
ActivityResultRegistry
ישירות.
לדוגמה, ייתכן שתרצו להטמיע
LifecycleObserver
שמטפל ברישום חוזה יחד עם הפעלת מרכז האפליקציות:
Kotlin
class MyLifecycleObserver(private val registry : ActivityResultRegistry) : DefaultLifecycleObserver { lateinit var getContent : ActivityResultLauncher<String> override fun onCreate(owner: LifecycleOwner) { getContent = registry.register("key", owner, GetContent()) { uri -> // Handle the returned Uri } } fun selectImage() { getContent.launch("image/*") } } class MyFragment : Fragment() { lateinit var observer : MyLifecycleObserver override fun onCreate(savedInstanceState: Bundle?) { // ... observer = MyLifecycleObserver(requireActivity().activityResultRegistry) lifecycle.addObserver(observer) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val selectButton = view.findViewById<Button>(R.id.select_button) selectButton.setOnClickListener { // Open the activity to select an image observer.selectImage() } } }
Java
class MyLifecycleObserver implements DefaultLifecycleObserver { private final ActivityResultRegistry mRegistry; private ActivityResultLauncher<String> mGetContent; MyLifecycleObserver(@NonNull ActivityResultRegistry registry) { mRegistry = registry; } public void onCreate(@NonNull LifecycleOwner owner) { // ... mGetContent = mRegistry.register(“key”, owner, new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } }); } public void selectImage() { // Open the activity to select an image mGetContent.launch("image/*"); } } class MyFragment extends Fragment { private MyLifecycleObserver mObserver; @Override void onCreate(Bundle savedInstanceState) { // ... mObserver = new MyLifecycleObserver(requireActivity().getActivityResultRegistry()); getLifecycle().addObserver(mObserver); } @Override void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { Button selectButton = findViewById(R.id.select_button); selectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mObserver.selectImage(); } }); } }
כשמשתמשים בממשקי ה-API של ActivityResultRegistry
, Google ממליצה מאוד להשתמש
את ממשקי ה-API שלוקחים LifecycleOwner
, בתור ה-LifecycleOwner
באופן אוטומטי
מסירה את מרכז האפליקציות הרשום כש-Lifecycle
מושמד. אבל, לפעמים
במקרים שבהם LifecycleOwner
לא זמין, כל אחד
בכיתה ActivityResultLauncher
אפשר להתקשר ידנית
unregister()
כחלופה.
בדיקה
כברירת מחדל, registerForActivityResult()
משתמש באופן אוטומטי
ActivityResultRegistry
שסופקה על ידי הפעילות. הוא גם מספק עומס יתר שמאפשר
במופע שלכם של ActivityResultRegistry
, שבו תוכלו להשתמש כדי לבדוק
קריאות לתוצאות פעילות מבלי להפעיל פעילות נוספת בפועל.
בזמן בדיקת מקטעי האפליקציה,
ייתן בדיקת ActivityResultRegistry
באמצעות
FragmentFactory
כדי לעבור
ב-ActivityResultRegistry
ל-constructor של המקטע.
לדוגמה, מקטע שמשתמש בחוזה TakePicturePreview
כדי לקבל
תמונה ממוזערת
של התמונה עשויה להיות כתובה כך:
Kotlin
class MyFragment( private val registry: ActivityResultRegistry ) : Fragment() { val thumbnailLiveData = MutableLiveData<Bitmap?> val takePicture = registerForActivityResult(TakePicturePreview(), registry) { bitmap: Bitmap? -> thumbnailLiveData.setValue(bitmap) } // ... }
Java
public class MyFragment extends Fragment { private final ActivityResultRegistry mRegistry; private final MutableLiveData<Bitmap> mThumbnailLiveData = new MutableLiveData(); private final ActivityResultLauncher<Void> mTakePicture = registerForActivityResult(new TakePicturePreview(), mRegistry, new ActivityResultCallback<Bitmap>() { @Override public void onActivityResult(Bitmap thumbnail) { mThumbnailLiveData.setValue(thumbnail); } }); public MyFragment(@NonNull ActivityResultRegistry registry) { super(); mRegistry = registry; } @VisibleForTesting @NonNull ActivityResultLauncher<Void> getTakePicture() { return mTakePicture; } @VisibleForTesting @NonNull LiveData<Bitmap> getThumbnailLiveData() { return mThumbnailLiveData; } // ... }
כשיוצרים ActivityResultRegistry
ספציפי לבדיקה, צריך להטמיע
ה
onLaunch()
. במקום להתקשר אל startActivityForResult()
,
יכול לקרוא
dispatchResult()
ולספק את התוצאות המדויקות שברצונך להשתמש בהן בבדיקה:
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
הבדיקה המלאה יוצרת את התוצאה הצפויה, ובונה בדיקה
ActivityResultRegistry
, מעביר אותו למקטע, מפעיל את מרכז האפליקציות
באופן ישיר או באמצעות ממשקי API אחרים לבדיקה כגון Espresso, ולאחר מכן מאמת
התוצאות:
@Test
fun activityResultTest {
// Create an expected result Bitmap
val expectedResult = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16)
// Create the test ActivityResultRegistry
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
// Use the launchFragmentInContainer method that takes a
// lambda to construct the Fragment with the testRegistry
with(launchFragmentInContainer { MyFragment(testRegistry) }) {
onFragment { fragment ->
// Trigger the ActivityResultLauncher
fragment.takePicture()
// Verify the result is set
assertThat(fragment.thumbnailLiveData.value)
.isSameInstanceAs(expectedResult)
}
}
}
יצירת חוזה בהתאמה אישית
בזמן ActivityResultContracts
מכיל מספר מחלקות ActivityResultContract
מוכנות מראש לשימוש, אפשר
לספק חוזים משלכם שמספקים את ממשק ה-API המדויק, הבטוח מהסוג הדרוש לכם.
כל ActivityResultContract
דורש סיווגי קלט ופלט מוגדרים,
אנחנו משתמשים ב-Void
כסוג הקלט,
לא דורשים קלט (ב-Kotlin צריך להשתמש ב-Void?
או ב-Unit
).
בכל חוזה יש להטמיע
createIntent()
שלוקחת את Context
ואת הקלט ובונה את ה-Intent
בשימוש
עם startActivityForResult()
.
בכל חוזה צריך ליישם גם את
parseResult()
שמפיק את הפלט מ-resultCode
הנתון, כמו
Activity.RESULT_OK
או Activity.RESULT_CANCELED
, וגם Intent
.
ניתן ליישם חוזים באופן אופציונלי
getSynchronousResult()
אם ניתן לקבוע את התוצאה של קלט נתון בלי
צריך לקרוא ל-createIntent()
, להתחיל את הפעילות האחרת ולהשתמש
parseResult()
כדי ליצור את התוצאה.
הדוגמה הבאה מראה איך ליצור ActivityResultContract
:
Kotlin
class PickRingtone : ActivityResultContract<Int, Uri?>() { override fun createIntent(context: Context, ringtoneType: Int) = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply { putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType) } override fun parseResult(resultCode: Int, result: Intent?) : Uri? { if (resultCode != Activity.RESULT_OK) { return null } return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) } }
Java
public class PickRingtone extends ActivityResultContract<Integer, Uri> { @NonNull @Override public Intent createIntent(@NonNull Context context, @NonNull Integer ringtoneType) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType.intValue()); return intent; } @Override public Uri parseResult(int resultCode, @Nullable Intent result) { if (resultCode != Activity.RESULT_OK || result == null) { return null; } return result.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); } }
אם אתם לא צריכים חוזה מותאם אישית, תוכלו להשתמש
StartActivityForResult
חוזה. זהו חוזה כללי שמקבל כל Intent
כקלט,
מחזירה
ActivityResult
,
וכך לחלץ את resultCode
ו-Intent
כחלק מהקריאה החוזרת,
כפי שאפשר לראות בדוגמה הבאה:
Kotlin
val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult -> if (result.resultCode == Activity.RESULT_OK) { val intent = result.data // Handle the Intent } } override fun onCreate(savedInstanceState: Bundle) { // ... val startButton = findViewById(R.id.start_button) startButton.setOnClickListener { // Use the Kotlin extension in activity-ktx // passing it the Intent you want to start startForResult.launch(Intent(this, ResultProducingActivity::class.java)) } }
Java
ActivityResultLauncher<Intent> mStartForResult = registerForActivityResult(new StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); // Handle the Intent } } }); @Override public void onCreate(@Nullable savedInstanceState: Bundle) { // ... Button startButton = findViewById(R.id.start_button); startButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // The launcher with the Intent you want to start mStartForResult.launch(new Intent(this, ResultProducingActivity.class)); } }); }