activity之間的傳遞不一定是單向的蛛芥,有時候會需要從跳轉過去的activity往前回傳數(shù)據(jù)(例如,您的應用可啟動相機應用并接收拍攝的照片作為結果)宾濒,過去我們一般采用底層 startActivityForResult()
和 onActivityResult()
API腿短,現(xiàn)在谷歌推出了新的Activity Result API為我們解決這類問題。
即ActivityResultContracts
基礎用法
-
在
ComponentActivity
或Fragment
中鼎兽,使用registerForActivityResult()
API答姥,用于注冊結果回調(diào)。此方法傳入兩個參數(shù)谚咬,ActivityResultContract
和ActivityResultCallback
鹦付,該方法返回ActivityResultLauncher
,供您用來啟動另一個 activity择卦。val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { res:ActivityResult ? -> if (result.resultCode == BACTIVITY_RESULT_CODE){ val data = result.data?.extras?.getString("extra_data") data?.let { toast(it) } } }
-
用返回的launcher啟動另一個activity.
val intent = Intent(this, MainActivity::class.java) launcher.launch(intent)
注意:步驟一
registerForActivityResult
要在oncreate
之前調(diào)用(即作為當前activity的屬性聲明)敲长;步驟二
launch
要在oncreate
之后調(diào)用。 -
第二個activity還是照常使用setResult回傳數(shù)據(jù)秉继。
setResult(BACTIVITY_RESULT_CODE, Intent().putExtra("extra-data", "data"))
ActivityResultContract (協(xié)定)
第一步的registerForActivityResult方法傳的第一個參數(shù)是ActivityResultContract祈噪,我們稱之為協(xié)定。里面要約定兩個東西:1. 你啟動launcher時要傳入的對象(本例中為intent)尚辑;2. 返回到當前頁面時帶回來的對象(本例中為ActivityResult)辑鲤。
第二個參數(shù)就是帶著你約定好的對象的回調(diào)了。
示例中用的約定是:ActivityResultContracts.StartActivityForResult()杠茬,這是系統(tǒng)提供的月褥,意思是傳入Intent,返回ActivityResult。
除此之外瓢喉,系統(tǒng)還提供了很多宁赤,如ActivityResultContracts.GetContent(),傳入string,返回UrI,一般是用來獲取系統(tǒng)資源用的栓票,像傳入"image/*",然后返回圖片URI{content://} 之類的决左。
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/*")
}
}
我們用Input<I>表示傳入的約定,Output<O>表示回傳回來的約定走贪。協(xié)定就可以抽象成:
public abstract class ActivityResultContract<I, O> {
/**
* 創(chuàng)建Intent佛猛,不管你約定的輸入是啥,要跳轉最后還是用的Intent跳厉斟,所以在這得自己創(chuàng)建Intent
**/
public abstract Intent createIntent(Context context,I input);
/**
* 解析回調(diào)的結果
**/
public abstract O parseResult(int resultCode, @Nullable Intent intent);
/**
* 可選
**/
public SynchronousResult<O> getSynchronousResult(Context context,I input) {
return null;
}
}
先來看示例中用的這個協(xié)定 - StartActivityForResult
官方提供的協(xié)定
public static final class StartActivityForResult extends ActivityResultContract<Intent, ActivityResult> {
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @NonNull Intent input) {
return input;
}
@NonNull
@Override
public ActivityResult parseResult(
int resultCode, @Nullable Intent intent) {
return new ActivityResult(resultCode, intent);
}
}
很簡單挚躯,傳入的就是intent,返回的就是resultCode + intent的封裝類擦秽;
再看下剛剛提到的傳圖片的那個:
public static class GetContent extends ActivityResultContract<String, Uri> {
//CallSuper是提醒繼承該類的話應該重寫該方法,上面那個是final不能重寫,所以沒有
@CallSuper
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @NonNull String input) {
return new Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(input);
}
@Nullable
@Override
public final SynchronousResult<Uri> getSynchronousResult(@NonNull Context context,
@NonNull String input) {
return null;
}
@Nullable
@Override
public final Uri parseResult(int resultCode, @Nullable Intent intent) {
if (intent == null || resultCode != Activity.RESULT_OK) return null;
return intent.getData();
}
}
傳入特定的Intent感挥,返回的結果先解析下再返回回去缩搅。
理解了這個協(xié)定后,我們也可以自己約定協(xié)定触幼。
創(chuàng)建自定義協(xié)定
比如說一個時間選擇的Activity硼瓣,它的返回到上一頁面的時候要攜帶當前界面選擇的時間。因為進入到該界面的時候Intent是固定的置谦,所以Input可以傳入void堂鲤,意思是不用傳。
class TimeResultContract : ActivityResultContract<Unit,String>(){
override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context,TimeSelectActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String {
if (resultCode != RESULT_OK){
return "error"
}
return intent?.getStringExtra(TimeSelectActivity_EXTRA_DATE).toString()
}
}
用的時候只需要在需要跳轉的activity中聲明即可媒峡。
val timeLauncher = registerForActivityResult(TimeResultContract()){ time ->
toast(time)
viewBinding.tvTime.text = time
}
相比以前需要把requestCode判斷的邏輯寫到各個Activity中的寫法瘟栖,這樣封裝起來看起來是不是干凈多了?
如果你還想把registerForActivityResult這個方法也提到activity外面去谅阿,也是可以做到的半哟。
進階
在單獨的類中接收 activity 結果
之所以能在ComponentActivity
和 Fragment
類中直接調(diào)用registerForActivityResult()
方法是因為它們實現(xiàn)了ActivityResultCaller
接口。如果你想在未實現(xiàn)ActivityResultCaller
接口的類中獲取launcher签餐,那么就需要用到ActivityResultRegistry
類了寓涨。
例如,您可能需要實現(xiàn)一個 LifecycleObserver
氯檐,用于處理協(xié)定的注冊和啟動器的啟動:
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()
}
}
}
源碼解析
從上面的用法可以看出戒良,實現(xiàn)這個功能的核心其實是傳進來的registry:ActivityResultRegistry。 而在ComponentActivity
和Fragment
中都維護了有一個registry,以activity為例:
private final ActivityResultRegistry mActivityResultRegistry = new ActivityResultRegistry() {
@Override
public <I, O> void onLaunch( final int requestCode, @NonNull ActivityResultContract<I, O> contract,
I input, @Nullable ActivityOptionsCompat options) {
ComponentActivity activity = ComponentActivity.this;
// Immediate result path
final ActivityResultContract.SynchronousResult<O> synchronousResult =
contract.getSynchronousResult(activity, input);
if (synchronousResult != null) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
dispatchResult(requestCode, synchronousResult.getValue());
}
});
return;
}
// Start activity path
Intent intent = contract.createIntent(activity, input);
Bundle optionsBundle = null;
// If there are any extras, we should defensively set the classLoader
if (intent.getExtras() != null && intent.getExtras().getClassLoader() == null) {
intent.setExtrasClassLoader(activity.getClassLoader());
}
if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE)) {
optionsBundle = intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE);
intent.removeExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE);
} else if (options != null) {
optionsBundle = options.toBundle();
}
if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
// requestPermissions path
String[] permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS);
if (permissions == null) {
permissions = new String[0];
}
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else if (ACTION_INTENT_SENDER_REQUEST.equals(intent.getAction())) {
IntentSenderRequest request =
intent.getParcelableExtra(EXTRA_INTENT_SENDER_REQUEST);
try {
// startIntentSenderForResult path
ActivityCompat.startIntentSenderForResult(activity, request.getIntentSender(),
requestCode, request.getFillInIntent(), request.getFlagsMask(),
request.getFlagsValues(), 0, optionsBundle);
} catch (final IntentSender.SendIntentException e) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
dispatchResult(requestCode, RESULT_CANCELED,
new Intent().setAction(ACTION_INTENT_SENDER_REQUEST)
.putExtra(EXTRA_SEND_INTENT_EXCEPTION, e));
}
});
}
} else {
// startActivityForResult path
ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
}
}
};
然后實際調(diào)用的是registry.register()方法(這是個抽象類冠摄,該方法寫在抽象類中)
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback) {
Lifecycle lifecycle = lifecycleOwner.getLifecycle();
if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "
+ "attempting to register while current state is "
+ lifecycle.getCurrentState() + ". LifecycleOwners must call register before "
+ "they are STARTED.");
}
final int requestCode = registerKey(key);
LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
if (lifecycleContainer == null) {
lifecycleContainer = new LifecycleContainer(lifecycle);
}
LifecycleEventObserver observer = new LifecycleEventObserver() {
@Override
public void onStateChanged(
@NonNull LifecycleOwner lifecycleOwner,
@NonNull Lifecycle.Event event) {
if (Lifecycle.Event.ON_START.equals(event)) {
mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
if (mParsedPendingResults.containsKey(key)) {
@SuppressWarnings("unchecked")
final O parsedPendingResult = (O) mParsedPendingResults.get(key);
mParsedPendingResults.remove(key);
callback.onActivityResult(parsedPendingResult);
}
final ActivityResult pendingResult = mPendingResults.getParcelable(key);
if (pendingResult != null) {
mPendingResults.remove(key);
callback.onActivityResult(contract.parseResult(
pendingResult.getResultCode(),
pendingResult.getData()));
}
} else if (Lifecycle.Event.ON_STOP.equals(event)) {
mKeyToCallback.remove(key);
} else if (Lifecycle.Event.ON_DESTROY.equals(event)) {
unregister(key);
}
}
};
lifecycleContainer.addObserver(observer);
mKeyToLifecycleContainers.put(key, lifecycleContainer);
return new ActivityResultLauncher<I>() {
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options) {
mLaunchedKeys.add(key);
onLaunch(requestCode, contract, input, options);
}
@Override
public void unregister() {
ActivityResultRegistry.this.unregister(key);
}
@NonNull
@Override
public ActivityResultContract<I, ?> getContract() {
return contract;
}
};
}
可以看到這里對生命周期所在狀態(tài)進行了很多判斷糯崎,可以防止聲明周期外的內(nèi)存泄漏的問題。
調(diào)用launch方法的時候實際走的是onLaunch()耗拓,也就是第一段代碼里面的內(nèi)容拇颅,最終調(diào)用的還是startActivityForResult方法,所以這個新的用法還是基于startActivityForResult的乔询,但是卻很好的將回傳的數(shù)據(jù)的判斷相關功能封裝到了協(xié)定類中樟插,以后就算有多個回傳數(shù)據(jù),用起來也會很清晰竿刁,代碼就不會臃腫到onActivityResult中了黄锤。
示例-獲取權限
request_permission.setOnClickListener {
requestPermission.launch(permission.BLUETOOTH)
}
request_multiple_permission.setOnClickListener {
requestMultiplePermissions.launch(
arrayOf(
permission.BLUETOOTH,
permission.NFC,
permission.ACCESS_FINE_LOCATION
)
)
}
// Request permission contract
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
// Do something if permission granted
if (isGranted) toast("Permission is granted")
else toast("Permission is denied")
}
// Request multiple permissions contract
private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
// Do something if some permissions granted or denied
permissions.entries.forEach {
// Do checking here
}
}
相較原來的獲取權限的寫法清晰多了。