Jetpack Compose中的startActivityForResult的正确姿势
Posted 川峰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose中的startActivityForResult的正确姿势相关的知识,希望对你有一定的参考价值。
之前在 Jetpack Compose中的导航路由 里简单的提到了从 Compose
导航到其他 Activity
页面的方式,对于不带返回结果的则就是跟以前一样简单的启动Activity
的代码,而如果是startActivityForResult
方式的,需要使用带回调的方式去启动,那么在以前,我们要么是使用三方库,要么是自己封装一个简单的库来使用 (至于背后原理也不是什么新鲜事了) 。
后来研究了一下,在Compose
中startActivityForResult
也有了新的姿势,要理解Compose
中startActivityForResult
的姿势,这还得从androidx
的startActivityForResult
的姿势说起,因为Compose
就是在androidx
的基础上利用其API简单封装了一下而已。
倒也不是说以前的不能用了,毕竟有系统自带的,谁还去用第三方的呀,用第三方的还得导入一个依赖库不是,能少依赖三方的就尽量少依赖吧。
androidx之后如何正确的startActivityForResult
如何使用
如果是在Activity
或Fragment
内部使用的话,直接调用registerForActivityResult
方法即可。
例如,选择文件:
val launcher = registerForActivityResult(ActivityResultContracts.GetContent()) uri ->
uri?.apply showToast(uri.toString())
launcher.launch("image/*")
ActivityResultContracts.GetContent()
的方式获取文件launch
时需要通过指定 mime type
来过滤文件类型, 例如 image/*
,这会打开系统自带的一个文件选择器供你选择文件。
录制视频:
val outVideoFile = File(externalCacheDir, "/$System.currentTimeMillis().mp4")
val videoUri = FileProvider.getUriForFile(this, "$packageName.provider", outVideoFile)
val launcher = registerForActivityResult(ActivityResultContracts.CaptureVideo()) isSuccess ->
if (isSuccess)
showToast(outVideoFile.path)
launcher.launch(videoUri)
拍照:
val outPictureFile = File(externalCacheDir, "/$System.currentTimeMillis().jpeg")
val pictureUri = FileProvider.getUriForFile(this, "$packageName.provider", outPictureFile)
val launcher = registerForActivityResult(ActivityResultContracts.TakePicture()) isSuccess ->
if (isSuccess)
showToast(outPictureFile.path)
launcher.launch(pictureUri)
这两种方式需要指定Uri
, 这个Uri
获取有点费劲,需要先进行FileProvider配置,不过配置好就很方便了。(如果你的minSdk配置的是29,那么需要另外配置,可自行查阅相关资料,不过国内基本不会兼容这么高的版本,一般minSdk配置的会是21)
自己的内部业务Activity之间的跳转:
// MainActivity.kt
val target = Intent(this, OtherActivity::class.java).apply
putExtra("name", "张三")
putExtra("uid", 123)
val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) activityResult ->
activityResult.data?.apply
val name = getStringExtra("name")
name?.let showToast(it)
launcher.launch(target)
// OtherActivity.kt
class OtherActivity: ComponentActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
val name = intent.getStringExtra("name")
val uid = intent.getIntExtra("uid", -1)
setContent
MyComposeApplicationTheme
Surface(modifier = Modifier.fillMaxSize())
Column
Text("name: $name fromCompose: $fromCompose uid: $uid", fontSize = 20.sp)
Button(onClick =
val data = Intent().apply putExtra("name", "小明")
setResult(RESULT_OK, data)
finish()
) Text("back with result")
在registerForActivityResult
的回调接口lambda中,仍然可以像以前那样使用activityResult.resultCode == RESULT_OK
来判断是正常返回了,还是取消操作。
在Activity/Fragment以外的类中使用:
class MyLifecycleObserver(private val registry : ActivityResultRegistry) : DefaultLifecycleObserver
lateinit var getContent : ActivityResultLauncher<String>
override fun onCreate(owner: LifecycleOwner)
getContent = registry.register("key", owner, ActivityResultContracts.GetContent()) uri ->
// Handle the returned Uri
fun selectImage()
getContent.launch("image/*")
很简单,将代码移到一个LifecycleObserver
的实现类中即可,然后在Activity/Fragment
中将这个LifecycleObserver
的实现类的观察者对象添加到其本身的lifecycle
中即可。
class MyFragment : Fragment()
lateinit var observer : MyLifecycleObserver
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
// ...
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()
使用总结:
- 整体来说,终于支持
callback
回调方式回传结果了, - 而且启动时也不用带
requestCode
了, - 并且还支持在
Activity/Fragment
以外的类中使用。
源码追踪
看一下ComponentActivity中registerForActivityResult
方法的实现:
// androidx.activity.ComponentActivity.java 1.6.1
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultRegistry registry,
@NonNull final ActivityResultCallback<O> callback)
return registry.register(
"activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback)
return registerForActivityResult(contract, mActivityResultRegistry, callback);
这里2个重载方法,下面的方法其实是调用了上面的方法,所以本质就是上面的三个参数的方法,也就是 registry.register()
方法,而在Fragment
中也有两个类似的方法:
// androidx.fragment.app.Fragment.java 1.5.4
@MainThread
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback)
return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>()
@Override
public ActivityResultRegistry apply(Void input)
if (mHost instanceof ActivityResultRegistryOwner)
return ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();
return requireActivity().getActivityResultRegistry();
, callback);
@MainThread
@NonNull
@Override
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultRegistry registry,
@NonNull final ActivityResultCallback<O> callback)
return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>()
@Override
public ActivityResultRegistry apply(Void input)
return registry;
, callback);
如果继续跟踪prepareCallInternal
方法会发现,Fragment
中最终还是依托mHost
宿主的ActivityResultRegistry
对象来执行registry.register()
方法的。
也就是说其实在androidx
以后,任意Activity
对象中调用getActivityResultRegistry()
方法,它都会返回一个ActivityResultRegistry
对象,然后调用该对象的register
方法来注册回调接口,而在Fragment
中自然能获取到宿主Activity
对象也能拿到这个ActivityResultRegistry
对象。
着重看一下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)
......
可以看出register
方法它需要四个参数:
key
: 唯一标识字符串,代替以前的requestCode
,其实内部还是使用的整型的requestCode
,只不过是根据传入的key
生成随机数然后存入一个Map
中,用的时候再从Map
中获取。lifecycleOwner
:lifecycle
持有者,我们知道在androidx
以后Activity
和Fragment
默认就是LifecycleOwner
接口的实现者contract
:ActivityResultContract<I, O>
类型,契约类,它有两个泛型,一个输入类型,一个输出类型callback
:回调接口,代替以前的onActivityResult
方法,内部会将前面的key
参数和callback
保存到一个Map
中,以便后面需要的时候方便获取
register
方法的返回值是一个 ActivityResultLauncher<I>
类型的对象,它就是最终真正用来执行启动动作的对象,通过执行它的launch
方法进行启动。
register
方法还有一个重载方法:
public final <I, O> ActivityResultLauncher<I> register(
@NonNull final String key,
@NonNull final ActivityResultContract<I, O> contract,
@NonNull final ActivityResultCallback<O> callback)
......
这个方法与上面那个方法的区别就是少了一个lifecycleOwner
参数,但是少了这个参数也就意味着你需要自己的onDestroy
方法中执行对应的 ActivityResultLauncher.unregister()
方法,不如上面那个方便了。因为带lifecycleOwner
参数的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.");
registerKey(key); // 注册
...
LifecycleEventObserver observer = new LifecycleEventObserver()
@Override
@SuppressWarnings("deprecation")
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); // 反注册
;
这里借助生命周期感知那一套,确保了register
方法在调用的时候必须在STARTED
之前注册,而在ON_DESTROY
状态之后则会自动帮你反注册。
对于contract
参数,在ActivityResultContracts
中提供了常用的内置类型,看名字也可以猜出是干嘛的,比如拍视频、选图片等,如果有需要,开发者可以之前从其中选择,如果是普通的Activity之间的跳转就选择StartActivityForResult
这个类型即可。
不过这些类型你也可以统统不用,自己创建也可以,只需实现ActivityResultContract
类满足它的输入输出类型约束即可。
对于ActivityResultLauncher.launch
方法也没有什么神秘的,内部调用了ActivityResultRegistry
的onLaunch
方法,而ActivityResultRegistry
是一个抽象类,其实例对象是在ComponentActivity
的构造方法创建的:
public abstract class ActivityResultRegistry
...
@MainThread
public abstract <I, O> void onLaunch(
int requestCode,
@NonNull ActivityResultContract<I, O> contract,
@SuppressLint("UnknownNullness") I input,
@Nullable ActivityOptionsCompat options);
....
@NonNull
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)
.......
return new ActivityResultLauncher<I>()
@Override
public void launch(I input, @Nullable ActivityOptionsCompat options)
Integer innerCode = mKeyToRc.get(key);
if (innerCode == null)
throw new IllegalStateException("Attempting to launch an unregistered "
+ "ActivityResultLauncher with contract " + contract + " and input "
+ input + ". You must ensure the ActivityResultLauncher is registered "
+ "before calling launch().");
mLaunchedKeys.add(key);
try
onLaunch(innerCode, contract, input, options);
catch (Exception e)
mLaunchedKeys.remove(key);
throw e;
@Override
public void unregister()
ActivityResultRegistry.this.unregister(key);
@NonNull
@Override
public ActivityResultContract<I, ?> getContract()
return contract;
;
public ComponentActivity()
this.mActivityResultRegistry = new ActivityResultRegistry()
public <I, O> void onLaunch(final int requestCode, @NonNull ActivityResultContract<I, O> contract, I input, @Nullable ActivityOptionsCompat options)
ComponentActivity activity = ComponentActivity.this;
...
ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
;
这里最终还是执行的Activity的startActivityForResult
方法,而在onActivityResult
方法中又将结果转发回mActivityResultRegistry
对象中:
public class ComponentActivity
...
@Deprecated
@CallSuper
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data)
以上是关于Jetpack Compose中的startActivityForResult的正确姿势的主要内容,如果未能解决你的问题,请参考以下文章
JetPack Compose 基础(3)Compose 中的主题
没有从 jetpack compose 中的 rememberLauncherForActivityResult() 获取结果