無論是從開發人員的應用程式,或是從他人的應用程式開始活動,都不需是單向作業。開發人員也可以啟動其他活動並取得結果。舉例來說,開發人員的應用程式可以啟動相機應用程式,並取得拍攝的相片作為結果。或者,開發人員也可以啟動「聯絡人」應用程式,讓使用者選取聯絡人,藉此取得聯絡人詳細資料作為結果。
雖然基礎 startActivityForResult()
和 onActivityResult()
API,都能在 Activity
類別使用所有 API 級別,對開發人員仍強烈建議使用 AndroidX Activity 和 Fragment 導入的 Activity Result API。
當系統完成活動調度,Activity Result API 會提供用來登錄、啟動及處理結果的元件。
登錄活動結果的回呼
系統開始執行活動以取得結果時,可能會因為記憶體不足,而遺失程序和活動 (比如使用相機的情形,必定造成記憶體不足)。
因此,Activity Result API 會將結果回呼,自另一個活動的啟動程式碼位置分開。因為需要重建程序和活動時取得結果回呼,因此開發人員每次建立活動時,都必須無條件登錄回呼,即使其他活動邏輯的啟動時機是根據使用者輸入內容或其他商業邏輯而定。
只要是使用 ComponentActivity
或 Fragment
,Activity Result API 會提供 registerForActivityResult()
登錄結果回呼的 API。registerForActivityResult()
會取得 ActivityResultContract
和 ActivityResultCallback
,然後傳回 ActivityResultLauncher
,以便用於啟動其他活動。
ActivityResultContract
會定義產生結果所需的輸入類型,以及結果的輸出類型。這些 API 針對基本意圖操作,提供預設合約,例如拍照、要求權限等。開發人員也可以建立自訂合約。
ActivityResultCallback
是單一方法介面,採用了 onActivityResult()
方法,該方法會使用 ActivityResultContract
定義的輸出類型物件:
Kotlin
val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri }
Java
// GetContent creates an ActivityResultLauncher<String> to allow you to pass // in the mime type you'd like to allow the user to select ActivityResultLauncher<String> mGetContent = registerForActivityResult(new GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { // Handle the returned Uri } });
如果有多個活動結果呼叫使用不同合約,或想要分別回呼,則可以多次呼叫 registerForActivityResult()
,以登錄多個 ActivityResultLauncher
執行個體。建立片段或活動時,都必須以相同的順序呼叫 registerForActivityResult()
,以確保活動結果會傳送到正確的回呼。
請在建立片段或活動前呼叫 registerForActivityResult()
較為妥當,在傳回的 ActivityResultLauncher
執行個體中宣告成員變數時,就可以直接使用它。
對應結果啟動活動
儘管 registerForActivityResult()
會登錄回呼,但不會啟動其他活動,並發起要求來取得結果。相反的,這個動作是由傳回的 ActivityResultLauncher
執行個體負責。
只有符合 ActivityResultContract
類型的既有輸入內容,啟動器才會接受。呼叫 launch()
,才會開始要求結果的程序。以下範例說明的是,使用者完成後續活動並傳回後,系統就會從 ActivityResultCallback
執行 onActivityResult()
:
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'd like to allow the user to 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 savedInstanceState: Bundle) { // ... Button selectButton = findViewById(R.id.select_button); selectButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // Pass in the mime type you'd like to allow the user to select // as the input mGetContent.launch("image/*"); } }); }
超載的 launch()
可以傳遞輸入內容以及 ActivityOptionsCompat
。
接收活動產生另一個類別
儘管 ComponentActivity
和 Fragment
類別能實作 ActivityResultCaller
介面來提供 registerForActivityResult()
API,但直接使用 ActivityResultRegistry
來執行活動的話,其產生的類別不會再實作 ActivityResultCaller
。
如要實作 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(); } }); } }
需要使用 ActivityResultRegistry
API 時,我們強烈建議採用能接受 LifecycleOwner
的 API,因為 LifecycleOwner
會在 Lifecycle
銷毀時自動移除已登錄的啟動器。然而,要是遇上 API 不接受 LifecycleOwner
的情況,開發人員也可改用每個 ActivityResultLauncher
類別手動呼叫 unregister()
。
測試
根據預設,registerForActivityResult()
會自動使用活動提供的 ActivityResultRegistry
。這個方法也可超載,讓開發人員不需啟動其他活動,只需傳送自己的 ActivityResultRegistry
執行個體,就能測試活動結果呼叫。
如要「測試應用程式的片段」,可使用 FragmentFactory
將 ActivityResultRegistry
傳入片段的建構函式,即可得到測試用的 ActivityResultRegistry
。
如要使用 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
,將其傳送至片段,觸發啟動器,然後驗證結果,開發人員也可直接執行做為測試,或透過 Espresso 等其他測試 API 進行驗證:
@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
及輸入內容,並建構會用到 startActivityForResult()
的 Intent
。
所有合約也須實作 parseResult()
,以產生指定 resultCode
的輸出內容 (例如 Activity.RESULT_OK
或 Activity.RESULT_CANCELED
) 和 Intent
。
如果合約可以判別特定輸入的結果,並且不需呼叫 createIntent()
,則開發人員可以選擇性實作 getSynchronousResult()
以啟動其他活動,並使用 parseResult()
來建構結果。
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)); } }); }