Avvio di un'altra attività, all'interno dell'app o da un'altra app, non deve essere necessariamente un'operazione unidirezionale. Puoi anche avviare un'attività e ricevere un risultato. Ad esempio, l'app può avviare un'app Fotocamera e ricevere la foto acquisita di conseguenza. In alternativa, puoi avviare l'app Contatti per consentire all'utente di selezionare un contatto e di riceverlo dettagli.
Anche se la parte
startActivityForResult()
e
onActivityResult()
Le API sono disponibili nel corso Activity
a tutti i livelli API, Google
consiglia di utilizzare le API Activity Result introdotte in AndroidX
Activity
e Fragment
corsi.
Le API Activity Result forniscono i componenti per la registrazione di un risultato, avviando l'attività che produce il risultato e gestendolo una volta viene inviato dal sistema.
Registrare un callback per un risultato di attività
Quando si avvia un'attività per un risultato, è possibile e, in caso di per le operazioni che richiedono molta memoria, come l'utilizzo della fotocamera, e l'attività verrà eliminata a causa della memoria insufficiente.
Per questo motivo, le API Activity Result disaccoppiano il risultato dal punto del codice in cui avvii l'altra attività. Poiché il callback dei risultati deve essere disponibile quando il processo e l'attività il callback deve essere registrato incondizionatamente ogni volta che viene creata un'attività, anche se la logica di avviare solo l'altra attività avviene in base all'input dell'utente o a un'altra logica di business.
Quando si trova in una
ComponentActivity
o un
Fragment
, il risultato dell'attività
Le API offrono un
registerForActivityResult()
API per la registrazione del callback dei risultati. registerForActivityResult()
prende un
ActivityResultContract
e un
ActivityResultCallback
e restituisce un
ActivityResultLauncher
,
che usi per avviare l'altra attività.
Un'istruzione ActivityResultContract
definisce il tipo di input necessario per produrre un risultato
insieme al tipo di output del risultato. Le API offrono
contratti predefiniti
per le azioni per intent di base come scattare una foto, richiedere autorizzazioni e così via.
attiva. Puoi anche
creare un contratto personalizzato.
ActivityResultCallback
è un'interfaccia a singolo metodo con un
onActivityResult()
che prende un oggetto del tipo di output definito nel
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 } });
Se hai più chiamate sui risultati di attività e utilizzi
contratti
o se vuoi ricevere callback separati, puoi chiamare registerForActivityResult()
per registrare più istanze ActivityResultLauncher
. Devi
chiama registerForActivityResult()
nello stesso ordine per ogni creazione dei tuoi
frammento o attività in modo che i risultati in transito vengano inviati
il callback corretto.
registerForActivityResult()
può essere chiamato in sicurezza prima del frammento o dell'attività
viene creato, in modo che possa essere utilizzato direttamente durante la dichiarazione delle variabili dei membri
per le istanze ActivityResultLauncher
restituite.
Avvia un'attività per il risultato
Mentre registerForActivityResult()
registra il callback, non
avvia l'altra attività e avvia la richiesta per un risultato. Invece,
è responsabilità dell'istanza ActivityResultLauncher
restituita.
Se esiste un input, Avvio app accetta l'input che corrisponde al tipo di
ActivityResultContract
. Chiamata in corso
launch()
avvia il processo di produzione del risultato. Quando l'utente ha terminato
l'attività successiva e i resi, il onActivityResult()
del
Viene quindi eseguito ActivityResultCallback
, come mostrato nell'esempio seguente:
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/*"); } }); }
Una versione sovraccarica
launch()
ti permette di passare
ActivityOptionsCompat
oltre all'input.
Ricevere un risultato dell'attività in un corso separato
Mentre le classi ComponentActivity
e Fragment
implementano il metodo
ActivityResultCaller
:
per consentirti di usare le API registerForActivityResult()
, puoi anche
ricevere il risultato dell'attività in una classe separata che non implementa
ActivityResultCaller
utilizzando
ActivityResultRegistry
.
Ad esempio, potresti voler implementare una
LifecycleObserver
che gestisce la registrazione di un contratto e il lancio di Avvio app:
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(); } }); } }
Quando utilizzi le API ActivityResultRegistry
, Google consiglia vivamente di utilizzare
le API che utilizzano LifecycleOwner
, come LifecycleOwner
rimuove l'Avvio app registrato quando viene eliminato Lifecycle
. Tuttavia,
nei casi in cui non sia disponibile LifecycleOwner
, ogni
Il corso ActivityResultLauncher
ti consente di chiamare manualmente
unregister()
:
in alternativa.
Test
Per impostazione predefinita, registerForActivityResult()
utilizza automaticamente
ActivityResultRegistry
forniti dall'attività. Fornisce anche un sovraccarico che ti consente di passare
nella tua istanza di ActivityResultRegistry
che puoi utilizzare per testare
risultati di attività chiamati senza avviare effettivamente un'altra attività.
Quando testi i frammenti della tua app,
fornisci un test ActivityResultRegistry
utilizzando un
FragmentFactory
per superare
nel ActivityResultRegistry
al costruttore del frammento.
Ad esempio, un frammento che utilizza il contratto TakePicturePreview
per ottenere un
miniatura
dell'immagine potrebbe essere scritto in modo simile al seguente:
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; } // ... }
Quando crei un elemento ActivityResultRegistry
specifico per il test, devi implementare
il
onLaunch()
. Invece di chiamare startActivityForResult()
, il test
di implementazione può chiamare
dispatchResult()
fornendo i risultati esatti che vuoi usare nel test:
val testRegistry = object : ActivityResultRegistry() {
override fun <I, O> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
dispatchResult(requestCode, expectedResult)
}
}
Il test completo crea il risultato previsto e genera un test
ActivityResultRegistry
, la passa al frammento, attiva l'Avvio app
direttamente o utilizzando altre API di test, come Espresso, e verifica
i risultati:
@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)
}
}
}
Crea un contratto personalizzato
Mentre ActivityResultContracts
contiene una serie di classi ActivityResultContract
predefinite da utilizzare,
i tuoi contratti che forniscono l'API precisa e sicura per i tipi di cui hai bisogno.
Ogni ActivityResultContract
richiede classi di input e output definite.
usando Void
come tipo di input se
non richiedono alcun input (in Kotlin, utilizza Void?
o Unit
).
Ogni contratto deve implementare
createIntent()
che prende Context
e l'input e genera il valore Intent
che
viene utilizzato
con startActivityForResult()
.
Ogni contratto deve inoltre implementare
parseResult()
,
che produce l'output dall'resultCode
specificato, come
Activity.RESULT_OK
o Activity.RESULT_CANCELED
e i Intent
.
I contratti possono implementare
getSynchronousResult()
se è possibile determinare il risultato per un determinato input senza
di dover chiamare createIntent()
, iniziare l'altra attività e usare
parseResult()
per creare il risultato.
L'esempio seguente mostra come creare un 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); } }
Se non hai bisogno di un contratto personalizzato, puoi utilizzare
StartActivityForResult
contratto. Questo è un contratto generico che prende qualsiasi Intent
come input e
restituisce un
ActivityResult
,
che ti consente di estrarre resultCode
e Intent
come parte del callback,
come mostrato nell'esempio seguente:
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)); } }); }