之前在 Jetpack Compose中的导航路由 里简单的提到了从 Compose 导航到其他 Activity 页面的方式,对于不带返回结果的则就是跟以前一样简单的启动Activity的代码,而如果是startActivityForResult方式的,需要使用带回调的方式去启动,那么在以前,我们要么是使用三方库,要么是自己封装一个简单的库来使用 (至于背后原理也不是什么新鲜事了) 。
后来研究了一下,在Compose 中startActivityForResult也有了新的姿势,要理解Compose 中startActivityForResult的姿势,这还得从androidx的startActivityForResult的姿势说起,因为Compose 就是在androidx的基础上利用其API简单封装了一下而已。
倒也不是说以前的不能用了,毕竟有系统自带的,谁还去用第三方的呀,用第三方的还得导入一个依赖库不是,能少依赖三方的就尽量少依赖吧。
androidx之后如何正确的startActivityForResult 如何使用 如果是在Activity或Fragment内部使用的话,直接调用registerForActivityResult方法即可。
例如,选择文件:
1 2 3 4 5 val launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.apply { showToast(uri.toString()) } } launcher.launch("image/*" )
ActivityResultContracts.GetContent()的方式获取文件launch时需要通过指定 mime type来过滤文件类型, 例如 image/* ,这会打开系统自带的一个文件选择器供你选择文件。
录制视频:
1 2 3 4 5 6 7 8 9 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)
拍照:
1 2 3 4 5 6 7 8 9 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之间的跳转:
1 2 3 4 5 6 7 8 9 10 11 12 13 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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以外的类中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 -> } } fun selectImage () { getContent.launch("image/*" ) } }
很简单,将代码移到一个LifecycleObserver的实现类中即可,然后在Activity/Fragment中将这个LifecycleObserver的实现类的观察者对象添加到其本身的lifecycle中即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 { observer.selectImage() } } }
使用总结:
整体来说,终于支持callback回调方式回传结果了,
而且启动时也不用带requestCode了,
并且还支持在Activity/Fragment以外的类中使用。
源码追踪 看一下ComponentActivity中registerForActivityResult 方法的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @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中也有两个类似的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @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() 方法:
1 2 3 4 5 6 7 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方法还有一个重载方法:
1 2 3 4 5 6 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方法内部其实处理好了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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状态之后则会自动帮你反注册。这里的mKeyToCallback是一个Map<String, CallbackAndContract<?>> 类型的对象,以<key, 用户传入的Callback>形式保存回调。
看一下unregister(key)方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 final void unregister(@NonNull String key) { if (!mLaunchedKeys.contains(key)) { Integer rc = mKeyToRc.remove(key); if (rc != null ) { mRcToKey.remove(rc); } } mKeyToCallback.remove(key); ...... LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get (key); if (lifecycleContainer != null ) { lifecycleContainer.clearObservers(); mKeyToLifecycleContainers.remove(key); } }
这里一个关键的地方就是LifecycleEventObserver在感知页面销毁时,就在unregister(key)方法中从mKeyToCallback这个map中删除用户传入的callback回调对象,从而避免了Activity销毁之后匿名内部类还会持有Activity对象的引用导致内存泄漏的问题 。
对于contract参数,在ActivityResultContracts中提供了常用的内置类型,看名字也可以猜出是干嘛的,比如拍视频、选图片等,如果有需要,开发者可以之前从其中选择,如果是普通的Activity之间的跳转就选择StartActivityForResult这个类型即可。
不过这些类型你也可以统统不用,自己创建也可以,只需实现ActivityResultContract类满足它的输入输出类型约束即可。
对于ActivityResultLauncher.launch方法也没有什么神秘的,内部调用了ActivityResultRegistry 的onLaunch方法,而ActivityResultRegistry 是一个抽象类,其实例对象是在ComponentActivity的构造方法(源码一直在变,今天看又直接放到了成员变量中)创建的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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; } }; } }
1 2 3 4 5 6 7 8 9 10 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对象中:
1 2 3 4 5 6 7 8 9 10 public class ComponentActivity { ... @Deprecated @CallSuper protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data ) { if (!this .mActivityResultRegistry.dispatchResult(requestCode, resultCode, data )) { super .onActivityResult(requestCode, resultCode, data ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public abstract class ActivityResultRegistry { ... @MainThread public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data ) { String key = mRcToKey.get (requestCode); if (key == null ) { return false ; } doDispatch(key, resultCode, data , mKeyToCallback.get (key)); return true ; } private <O> void doDispatch(String key, int resultCode, @Nullable Intent data , @Nullable CallbackAndContract<O> callbackAndContract) { if (callbackAndContract != null && callbackAndContract.mCallback != null && mLaunchedKeys.contains(key)) { ActivityResultCallback<O> callback = callbackAndContract.mCallback; ActivityResultContract<?, O> contract = callbackAndContract.mContract; callback.onActivityResult(contract.parseResult(resultCode, data )); mLaunchedKeys.remove(key); } else { mParsedPendingResults.remove(key); mPendingResults.putParcelable(key, new ActivityResult(resultCode, data )); } } }
很明显了,这里根据resultCode得到的key就是当初我们在register方法中传入的key,而传入的key又和传入的callback建立了Map映射关系,因此这里可以拿到我们在register方法中传入的callback进行结果回调。
总而言之一句话,只要官方想玩,源码在手,随时可以改,加个回调也是分分钟的事,就是这件事做的有点晚了,在androidx以前开发者对于startActivityForResult操作还是十分痛苦的(明明一整套的姿势,你非得给我拆开)。但是迟来总比没有好,相信很多公司目前仍有很多老项目有大量的代码甚至迁移到androidx都比较困难。
Jetpack Compose中如何正确的startActivityForResult 简单了解了androidx中如何正确的startActivityForResult后,在Jetpack Compose中使用就非常简单了,在Composable中使用主要通过一个rememberLauncherForActivityResult函数,可以看一下其实现源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Composable public fun <I, O> rememberLauncherForActivityResult ( contract: ActivityResultContract <I , O>, onResult: (O ) -> Unit ) : ManagedActivityResultLauncher<I, O> { val currentContract = rememberUpdatedState(contract) val currentOnResult = rememberUpdatedState(onResult) val key = rememberSaveable { UUID.randomUUID().toString() } val activityResultRegistry = checkNotNull(LocalActivityResultRegistryOwner.current) { "No ActivityResultRegistryOwner was provided via LocalActivityResultRegistryOwner" }.activityResultRegistry val realLauncher = remember { ActivityResultLauncherHolder<I>() } val returnedLauncher = remember { ManagedActivityResultLauncher(realLauncher, currentContract) } DisposableEffect(activityResultRegistry, key, contract) { realLauncher.launcher = activityResultRegistry.register(key, contract) { currentOnResult.value(it) } onDispose { realLauncher.unregister() } } return returnedLauncher }
可以看到,它就是在DisposableEffect这个副作用Api中调用了以前的activityResultRegistry.register而已,返回的是一个ManagedActivityResultLauncher<I, O>类型的对象,这个对象中包装了以前的realLauncher和contract对象。所以说使用跟之前其实是差不多类似的。
选择文件示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Composable fun PickImage () { var uri by remember { mutableStateOf<Uri?>(null ) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri = it } Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { launcher.launch("image/*" ) }) { Text(text = "Pick a picture" ) } if (uri != null ) { Image( painter = rememberAsyncImagePainter(uri) , modifier = Modifier.fillMaxWidth(), contentDescription = null ) } } }
拍照示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Composable fun TakePhotoForResult () { var resultBitmap by remember { mutableStateOf<Bitmap?>(null ) } val scope = rememberCoroutineScope() val outPictureFile = LocalContext.current.getCacheImgFile() val pictureUri = LocalContext.current.getOutUri(outPictureFile) val launcher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { isSuccess -> if (isSuccess) { scope.launch { resultBitmap = BitmapFactory.decodeFile(outPictureFile.path) } } } Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { launcher.launch(pictureUri) }) { Text(text = "Take a picture" ) } resultBitmap?.let { image -> Image(image.asImageBitmap(), null , modifier = Modifier.fillMaxWidth()) } } } private fun Context.getCacheImgFile () : File { return File(externalCacheDir, "/${System.currentTimeMillis()} .jpeg" ) } private fun Context.getOutUri (file: File ) : Uri { return FileProvider.getUriForFile(this , "${packageName} .provider" , file) }
如果只是想获得一张拍照预览图,可以使用下面的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Composable fun TakePhotoForResult2 () { var resultBitmap by remember { mutableStateOf<Bitmap?>(null ) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap -> resultBitmap = bitmap } Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { launcher.launch() }) { Text(text = "Take a picture" ) } resultBitmap?.let { image -> Image(image.asImageBitmap(), null , modifier = Modifier.fillMaxWidth()) } } }
这种方式更加简单,但是这种方式得到的是一个略缩图的尺寸大小,比较模糊。
启动其他的业务Activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Composable fun StartActivityForResultExample () { var result by remember { mutableStateOf("" ) } val target = Intent(LocalContext.current, OtherActivity::class .java).apply { putExtra("name" , "张三" ) putExtra("uid" , 123 ) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult -> activityResult.data ?.apply { result = getStringExtra("name" ).toString() } } Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { launcher.launch(target) }) { Text(text = "StartActivityForResult" ) } Text(text = "result: $result " , fontSize = 22. sp) } }
可见使用方式跟之前几乎是一致的。
注:本文示例代码所有涉及到使用权限的地方需要自己提前申请好。
参考:获取 activity 的结果