Uzyskiwanie wyniku z aktywności

Rozpoczęcie innego działania, niezależnie od tego, czy ma ono miejsce w Twojej aplikacji czy w innej, nie musi być działaniem jednokierunkowym. Możesz też rozpocząć aktywność i otrzymać wynik. Na przykład aplikacja może uruchomić aplikację aparatu i otrzymać zrobione zdjęcie. Możesz też uruchomić aplikację Kontakty, aby użytkownik mógł wybrać kontakt, a w rezultacie otrzymać jego szczegóły.

Podstawowe interfejsy API startActivityForResult() i onActivityResult() są dostępne w klasie Activity na wszystkich poziomach interfejsu API, ale zdecydowanie zalecamy korzystanie z interfejsów API wyniku aktywności wprowadzonych w klasach AndroidX Activity i Fragment.

Interfejsy API wyników związanych z aktywnością zapewniają komponenty do rejestrowania wyniku, uruchamiania go i przetwarzania po wysłaniu go przez system.

Rejestrowanie wywołania zwrotnego dla wyniku aktywności

Gdy rozpoczniesz działanie związane z konkretnym wynikiem, istnieje ryzyko, że z powodu zbyt małej ilości pamięci proces i aktywność zostaną zniszczone – a w przypadku operacji wymagających dużej ilości pamięci, takich jak użycie kamery, jest prawie pewne.

Z tego powodu interfejsy API wyników związanych z aktywnością oddzielają wynikowe wywołanie zwrotne od miejsca w kodzie, w którym uruchamiasz inną aktywność. Wynik wywołania zwrotnego musi być dostępny podczas ponownego tworzenia procesu i aktywności, dlatego musi ono być bezwarunkowo rejestrowane za każdym razem, gdy tworzona jest aktywność, nawet wtedy, gdy logika uruchamiania innego działania odbywa się tylko na podstawie danych wejściowych użytkownika lub innej logiki biznesowej.

W ComponentActivity lub Fragment interfejsy Activity Result APIs zapewniają interfejs API registerForActivityResult() służący do rejestracji wywołania zwrotnego. registerForActivityResult() pobiera ActivityResultContract i ActivityResultCallback, a potem zwraca ActivityResultLauncher, które służą do uruchamiania innego działania.

ActivityResultContract określa typ danych wejściowych niezbędny do wygenerowania wyniku wraz z typem wyjściowym. Interfejsy API udostępniają domyślne umowy dotyczące podstawowych działań intencji, takich jak robienie zdjęć, wysyłanie próśb o uprawnienia itp. Możesz też utworzyć niestandardową umowę.

ActivityResultCallback to interfejs pojedynczej metody z metodą onActivityResult(), która pobiera obiekt typu wyjściowego zdefiniowanego w 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
        }
});

Jeśli masz wiele wywołań wyników związanych z aktywnością i używasz różnych umów lub chcesz używać osobnych wywołań zwrotnych, możesz wielokrotnie wywoływać metodę registerForActivityResult(), aby zarejestrować większą liczbę instancji ActivityResultLauncher. Musisz wywoływać metodę registerForActivityResult() w tej samej kolejności przy każdym tworzeniu fragmentu kodu lub działania, aby wyniki wyświetlane były dostarczane do właściwego wywołania zwrotnego.

registerForActivityResult() można bezpiecznie wywołać przed utworzeniem fragmentu lub aktywności, co pozwala używać go bezpośrednio podczas deklarowania zmiennych składowych zwróconych instancji ActivityResultLauncher.

Uruchamianie działania związanego z wynikiem

registerForActivityResult() rejestruje wywołanie zwrotne, ale nie uruchamia innego działania i uruchamia żądania wyniku. Jest to odpowiedzialność zwróconej instancji ActivityResultLauncher.

Jeśli istnieją dane wejściowe, program uruchamiający pobiera dane wejściowe zgodne z typem ActivityResultContract. Wywołanie launch() rozpoczyna proces generowania wyniku. Gdy użytkownik zakończy kolejną aktywność i powróci, następuje wykonanie onActivityResult() z tabeli ActivityResultCallback, jak widać w tym przykładzie:

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/*");
        }
    });
}

Przeciążona wersja launch() umożliwia przekazanie poza danymi wejściowymi ActivityOptionsCompat.

Otrzymuj wynik aktywności na osobnych zajęciach

Klasy ComponentActivity i Fragment implementują interfejs ActivityResultCaller, aby umożliwić korzystanie z interfejsów API registerForActivityResult(), ale wynik działania możesz też otrzymać w osobnej klasie, która nie implementuje ActivityResultCaller, używając bezpośrednio ActivityResultRegistry.

Możesz na przykład zaimplementować LifecycleObserver, który obsługuje rejestrowanie umowy wraz z uruchamianiem programu uruchamiającego:

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();
            }
        });
    }
}

Gdy korzystasz z interfejsów API ActivityResultRegistry, Google zdecydowanie zaleca korzystanie z interfejsów API, które przyjmują LifecycleOwner, ponieważ LifecycleOwner automatycznie usuwa zarejestrowany program uruchamiający po zniszczeniu Lifecycle. Jeśli jednak LifecycleOwner jest niedostępny, każda klasa ActivityResultLauncher umożliwia ręczne wywołanie unregister().

Testowanie

Domyślnie registerForActivityResult() automatycznie używa pola ActivityResultRegistry udostępnionego przez aktywność. Zapewnia też przeciążenie, które umożliwia przekazanie własnej instancji ActivityResultRegistry, której można używać do testowania wywołań wyników aktywności bez uruchamiania innej aktywności.

Podczas testowania fragmentów aplikacji udostępniasz test ActivityResultRegistry za pomocą FragmentFactory, aby przekazać parametr ActivityResultRegistry do konstruktora fragmentu.

Na przykład fragment, który korzysta z umowy TakePicturePreview do uzyskania miniatury obrazu, może wyglądać tak:

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;
    }

    // ...
}

Gdy tworzysz obiekt ActivityResultRegistry dla konkretnego testu, musisz wdrożyć metodę onLaunch(). Zamiast wywoływać startActivityForResult(), implementacja testowa może wywołać metodę dispatchResult() bezpośrednio, podając dokładne wyniki, których chcesz użyć w teście:

val testRegistry = object : ActivityResultRegistry() {
    override fun <I, O> onLaunch(
            requestCode: Int,
            contract: ActivityResultContract<I, O>,
            input: I,
            options: ActivityOptionsCompat?
    ) {
        dispatchResult(requestCode, expectedResult)
    }
}

Pełny test tworzy oczekiwany wynik, tworzy test ActivityResultRegistry, przekazuje go do fragmentu, uruchamia program uruchamiający bezpośrednio lub za pomocą innych interfejsów API, takich jak Espresso, a potem weryfikuje wyniki:

@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)
            }
    }
}

Utwórz niestandardową umowę

ActivityResultContracts zawiera wiele gotowych klas ActivityResultContract do użytku, ale możesz udostępniać własne umowy, które udostępniają dokładny, odpowiedni typ interfejsu API.

Każdy element ActivityResultContract wymaga zdefiniowanych klas danych wejściowych i wyjściowych, a jeśli nie potrzebujesz żadnych danych, użyj Void jako typu danych wejściowych (w Kotlinie użyj Void? lub Unit).

W każdej umowie musi być zaimplementowana metoda createIntent(), która pobiera Context, dane wejściowe i tworzy Intent używany z startActivityForResult().

W ramach każdej umowy musi też być zaimplementowane narzędzie parseResult(), które generuje dane wyjściowe z danego elementu resultCode, np. Activity.RESULT_OK lub Activity.RESULT_CANCELED i Intent.

Umowy mogą opcjonalnie implementować getSynchronousResult(), jeśli można określić wynik dla danych wejściowych bez konieczności wywoływania funkcji createIntent(), uruchamiania innego działania i używania parseResult() do utworzenia wyniku.

Ten przykład pokazuje, jak utworzyć 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);
    }
}

Jeśli nie potrzebujesz niestandardowej umowy, możesz użyć umowy StartActivityForResult. Jest to ogólna umowa, w której dane wejściowe są dowolne Intent i zwracają ActivityResult, co pozwala wyodrębnić resultCode i Intent w ramach wywołania zwrotnego, jak w tym przykładzie:

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));
        }
    });
}