Das Starten einer anderen Aktivität innerhalb Ihrer Anwendung oder von einer anderen Anwendung aus muss nicht in eine Richtung erfolgen. Sie können auch eine Aktivität starten und ein Ergebnis erhalten. Ihre App kann beispielsweise eine Kamera-App starten und das aufgenommene Foto erhalten. Oder Sie starten die Kontakte-App, damit der Nutzer einen Kontakt auswählt und dann die Kontaktdetails erhält.
Die zugrunde liegenden APIs startActivityForResult()
und onActivityResult()
sind in der Klasse Activity
auf allen API-Ebenen verfügbar. Google empfiehlt jedoch dringend, die in den AndroidX-Klassen Activity
und Fragment
eingeführten Activity Result APIs zu verwenden.
Die Activity Result APIs bieten Komponenten für die Registrierung für ein Ergebnis, den Start des Ergebnisses und die Verarbeitung des Ergebnisses, sobald es vom System weitergeleitet wird.
Callback für ein Aktivitätsergebnis registrieren
Wenn Sie eine Aktivität für ein Ergebnis starten, ist es möglich – und im Fall von speicherintensiven Vorgängen wie der Kameranutzung – sehr wahrscheinlich, dass Ihr Prozess und Ihre Aktivität aufgrund des geringen Arbeitsspeichers zerstört werden.
Aus diesem Grund entkoppeln die Activity Result APIs den Ergebnis-Callback von der Stelle im Code, an der Sie die andere Aktivität starten. Da der Ergebnis-Callback verfügbar sein muss, wenn Ihr Prozess und Ihre Aktivität neu erstellt werden, muss der Callback bei jeder Erstellung Ihrer Aktivität unbedingt registriert werden, selbst wenn die Logik zum Starten der anderen Aktivität nur auf Grundlage von Nutzereingaben oder einer anderen Geschäftslogik erfolgt.
In einem ComponentActivity
- oder Fragment
-Objekt bieten die Aktivitätsergebnis-APIs eine registerForActivityResult()
API zum Registrieren des Ergebnis-Callbacks. registerForActivityResult()
verwendet ein ActivityResultContract
und ein ActivityResultCallback
und gibt ein ActivityResultLauncher
-Element zurück, mit dem Sie die andere Aktivität starten.
Ein ActivityResultContract
definiert den Eingabetyp, der zum Erstellen eines Ergebnisses zusammen mit dem Ausgabetyp des Ergebnisses erforderlich ist. Die APIs bieten Standardverträge für grundlegende Intent-Aktionen wie das Aufnehmen eines Bildes, das Anfordern von Berechtigungen usw. Außerdem haben Sie die Möglichkeit, einen benutzerdefinierten Vertrag zu erstellen.
ActivityResultCallback
ist eine einzelne Methodenschnittstelle mit der Methode onActivityResult()
, die ein Objekt des Ausgabetyps verwendet, der in ActivityResultContract
definiert ist:
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 } });
Wenn Sie mehrere Aktivitätsergebnisaufrufe haben und entweder unterschiedliche Verträge verwenden oder separate Callbacks wünschen, können Sie registerForActivityResult()
mehrmals aufrufen, um mehrere ActivityResultLauncher
-Instanzen zu registrieren. Sie müssen registerForActivityResult()
bei jeder Erstellung Ihres Fragments oder Ihrer Aktivität in derselben Reihenfolge aufrufen, damit die Inflight-Ergebnisse an den richtigen Callback gesendet werden.
registerForActivityResult()
kann bedenkenlos aufgerufen werden, bevor das Fragment oder die Aktivität erstellt wird, sodass es direkt bei der Deklaration von Mitgliedsvariablen für die zurückgegebenen ActivityResultLauncher
-Instanzen verwendet werden kann.
Aktivität für Ergebnis starten
Während registerForActivityResult()
Ihren Callback registriert, wird die andere Aktivität nicht gestartet und die Anfrage für ein Ergebnis gestartet. Stattdessen liegt dies in der Verantwortung der zurückgegebenen ActivityResultLauncher
-Instanz.
Wenn eine Eingabe vorhanden ist, verwendet der Launcher die Eingabe, die dem Typ von ActivityResultContract
entspricht. Durch Aufrufen von launch()
wird die Ergebniserstellung gestartet. Wenn der Nutzer die nachfolgende Aktivität abgeschlossen hat und zurückkehrt, wird das onActivityResult()
aus dem ActivityResultCallback
ausgeführt, wie im folgenden Beispiel gezeigt:
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/*"); } }); }
Bei einer überlasteten Version von launch()
können Sie zusätzlich zur Eingabe ein ActivityOptionsCompat
-Objekt übergeben.
Ein Aktivitätsergebnis in einem separaten Kurs erhalten
Während die Klassen ComponentActivity
und Fragment
die ActivityResultCaller
-Schnittstelle implementieren, damit Sie die registerForActivityResult()
APIs verwenden können, können Sie das Aktivitätsergebnis auch in einer separaten Klasse empfangen, die ActivityResultCaller
nicht implementiert, indem Sie ActivityResultRegistry
direkt verwenden.
Du kannst beispielsweise einen LifecycleObserver
implementieren, der die Registrierung eines Vertrags sowie das Starten des Launchers übernimmt:
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(); } }); } }
Bei der Verwendung der ActivityResultRegistry
APIs empfiehlt Google dringend, die APIs zu verwenden, die ein LifecycleOwner
verarbeiten, da der LifecycleOwner
den registrierten Launcher automatisch entfernt, wenn der Lifecycle
gelöscht wird. Wenn jedoch keine LifecycleOwner
verfügbar ist, können Sie unregister()
mit jeder ActivityResultLauncher
-Klasse manuell aufrufen.
Test
Standardmäßig verwendet registerForActivityResult()
automatisch den von der Aktivität bereitgestellten ActivityResultRegistry
. Außerdem stellt es eine Überlastung bereit, mit der Sie eine eigene Instanz von ActivityResultRegistry
übergeben können, mit der Sie Ihre Aktivitätsergebnisaufrufe testen können, ohne eine weitere Aktivität zu starten.
Beim Testen der Fragmente Ihrer App stellen Sie eine Test-ActivityResultRegistry
mit einem FragmentFactory
bereit, um die ActivityResultRegistry
an den Konstruktor des Fragments zu übergeben.
Beispielsweise kann ein Fragment, das den Vertrag TakePicturePreview
verwendet, um eine Miniaturansicht des Bildes zu erhalten, in etwa so geschrieben werden:
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; } // ... }
Wenn Sie eine testspezifische ActivityResultRegistry
erstellen, müssen Sie die Methode onLaunch()
implementieren. Anstatt startActivityForResult()
aufzurufen, kann Ihre Testimplementierung dispatchResult()
direkt aufrufen und so genau die Ergebnisse liefern, die Sie in Ihrem Test verwenden möchten:
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
Der vollständige Test erstellt das erwartete Ergebnis, erstellt einen Test-ActivityResultRegistry
, übergibt ihn an das Fragment, löst den Launcher entweder direkt oder mithilfe anderer Test-APIs wie Espresso aus und verifiziert dann die Ergebnisse:
@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)
}
}
}
Benutzerdefinierten Vertrag erstellen
ActivityResultContracts
enthält eine Reihe vordefinierter ActivityResultContract
-Klassen. Sie können aber auch eigene Verträge bereitstellen, um die benötigte typsichere API bereitzustellen.
Für jeden ActivityResultContract
sind definierte Eingabe- und Ausgabeklassen erforderlich. Wenn keine Eingabe erforderlich ist, verwenden Sie Void
als Eingabetyp. Verwenden Sie in Kotlin entweder Void?
oder Unit
.
In jedem Vertrag muss die Methode createIntent()
implementiert werden, die ein Context
und die Eingabe verwendet und die Intent
erstellt, die mit startActivityForResult()
verwendet wird.
In jedem Vertrag muss auch parseResult()
implementiert werden, das die Ausgabe aus dem angegebenen resultCode
, z. B. Activity.RESULT_OK
oder Activity.RESULT_CANCELED
, und dem Intent
-Element erstellt.
In Verträgen kann optional getSynchronousResult()
implementiert werden, wenn das Ergebnis für eine bestimmte Eingabe ermittelt werden kann, ohne createIntent()
aufzurufen, die andere Aktivität zu starten und parseResult()
zum Erstellen des Ergebnisses zu verwenden.
Das folgende Beispiel zeigt, wie ein ActivityResultContract
erstellt wird:
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); } }
Wenn Sie keinen benutzerdefinierten Vertrag benötigen, können Sie den StartActivityForResult
-Vertrag verwenden. Dies ist ein generischer Vertrag, bei dem jedes Intent
als Eingabe verwendet und ein ActivityResult
zurückgegeben wird. So können Sie resultCode
und Intent
als Teil Ihres Callbacks extrahieren, wie im folgenden Beispiel gezeigt:
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)); } }); }