前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再见 onActivityResult!你好 Activity Results API

再见 onActivityResult!你好 Activity Results API

作者头像
八归少年
发布2022-06-29 15:49:58
1.4K0
发布2022-06-29 15:49:58
举报
文章被收录于专栏:program

首语

又忙了好一段时间,秋天是收获的季节啊。是时候总结一波咯。这次带来的是新API的使用。 PS: 关于Android的博客文章,以后都会使用Kotlin来进行展示,还没有学习Kotlin的小伙伴抓紧学习波咯,这是Android的趋势。

背景

在项目开发中,发现startActivityForResultonActivityResult方法已经被废弃了,这是为什么呢?有代码强迫症的我开始了研究。

代码语言:javascript
复制
 /**
     * {@inheritDoc}
     *
     * @deprecated use
     * {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
     * passing in a {@link StartActivityForResult} object for the {@link ActivityResultContract}.
     */
    @Override
    @Deprecated
    public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
            int requestCode) {
        super.startActivityForResult(intent, requestCode);
    }
/**
     * {@inheritDoc}
     *
     * @deprecated use
     * {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
     * with the appropriate {@link ActivityResultContract} and handling the result in the
     * {@link ActivityResultCallback#onActivityResult(Object) callback}.
     */
    @CallSuper
    @Override
    @Deprecated
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

在Android应用程序开发中,比较常见的场景是从启动的Activity获取数据,传统的方式是使用startActivityForResult方法来启动下一个Activity,然后通过onActivityResult方法来接收返回的结果。

代码语言:javascript
复制
 val intent = Intent(this, ResultActivity::class.java)
 startActivityForResult(intent, ACTION_CODE)

 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        
        if (requestCode == ACTION_CODE && resultCode == Activity.RESULT_OK) {
            val title = intent.getStringExtra("title")
            Log.e("yhj", "onActivityResult: " + title)
        }
 }

这种方式不仅能在同一个应用中获取数据,也可以从其它应用中获取数据,例如调用系统相机,相册获取图片,获取系统通讯录等。 但随之产生了许多问题,随着应用功能不断添加迭代,onActivityResult方法会存在各种处理数据的回调,嵌套严重,难以维护,并且还得定义一堆额外的常量REQUEST_CODE,用于判断是哪个请求的回调结果。 Google可能已经到了onActivityResult方法的这些问题,因此在androidx.activity:activity:1.2.0-alpha02androidx.fragment:fragment:1.3.0-alpha02 中,已经废弃了startActivityForResultonActivityResult方法。并且推出了一种新的API👉Activity Results API

介绍

Activity Results API 是 Google官方推荐的Activity、Fragment获取返回结果的方式。 Activity Results API中有两个重要的组件,ActivityResultContractActivityResultLauncher

  • ActivityResultContract。它定义了如何传递数据和如何处理返回的数据。它是一个抽象类,你需要继承它来创建自己的协议,每个 ActivityResultContract 都需要定义输入和输出类,如果您不需要任何输入,默认使用 Void(在 Kotlin 中,使用 Void? 或 Unit)作为输入类型。
  • ActivityResultLauncher。启动器,调用ActivityResultLauncherlaunch方法来启动页面跳转,作用相当于原来的startActivity()

使用

  1. 定义ActivityResultContract

新建一个MyResultContract类,继承自 ActivityResultContract<I, O>,I是输入类型,O是输出类型。实现两个方法,createIntentparseResult。输入类型I作为createIntent方法的参数,输出类型O作为parseResult方法的返回值。

代码语言:javascript
复制
class MyResultContract : ActivityResultContract<Int, String>() {
        override fun createIntent(context: Context, input: Int?): Intent {
            return Intent(context, ResultActivity::class.java)
        }

        override fun parseResult(resultCode: Int, intent: Intent?): String {
            val data = intent?.getStringExtra("title")
            return if (data != null && resultCode == Activity.RESULT_OK) data
            else ""

        }

}

我在createIntent方法里创建了Intent,在parseResult方法里处理返回的数据。

  1. 获取ActivityResultLauncher

使用registerForActivityResult方法,该方法由ComponentActivity或者Fragment提供,接受2个参数,第一个参数就是我们定义的Contract,第二个参数是一个回调ActivityResultCallback<O>,其中O就是前面Contract的输出类型。

代码语言:javascript
复制
 private val myActivityLauncher = registerForActivityResult(MyResultContract()) { result ->
        Log.e("yhj", result)
    }

result就是从上个界面传递回来的数据,,registerForActivityResult方法的返回值是ActivityResultLauncher

  1. 界面跳转

调用ActivityResultLauncherlaunch方法进行界面跳转。

代码语言:javascript
复制
myActivityLauncher.launch(ACTION_CODE)

但是Activity Results API使用起来还是有点麻烦,每次都得定义Contract。

Google也考虑到了这个问题,已经自定义了很多Contract,覆盖了开发中的使用场景。

预定义的Contract

在类ActivityResultContracts中,系统已经定义如下图所示的Contract,具体可查看源码说明。

  • StartActivityForResult()。通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出。这是最常用的一个Contract。
代码语言:javascript
复制
private val activityResultLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
            if (activityResult.resultCode == Activity.RESULT_OK) {
                val result = activityResult.data?.getStringExtra("title")
                result?.let { Log.e("yhj", it) }
            }
        }
        
activityResultLauncher.launch(Intent(this, ResultActivity::class.java))
  • RequestPermission()。用于请求单个权限。
代码语言:javascript
复制
private val singlePermissionLauncher =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        when {
            isGranted -> Log.e("yhj", "申请成功")
            !ActivityCompat.shouldShowRequestPermissionRationale(
                this,
                Manifest.permission.CAMERA
            ) -> Log.e("yhj", "拒绝且不在询问")
            else -> Log.e("yhj", "拒绝")
        }
    }
singlePermissionLauncher.launch(Manifest.permission.CAMERA)
  • RequestMultiplePermissions()。用于请求一组权限。
代码语言:javascript
复制
private val permissionsLauncher =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        permissions.entries.forEach { permission ->
            if (!permission.value) {
                if (!ActivityCompat.shouldShowRequestPermissionRationale(
                        this,
                        permission.key
                    )
                ) {
                    Log.e("yhj", "${permission.key}拒绝且不在询问")
                } else {
                    Log.e("yhj", "${permission.key}拒绝")
                }
            }
        }
    }

permissionsLauncher.launch(
            arrayOf(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.ACCESS_FINE_LOCATION
            )
        )
  • TakePicture()。调用系统相机拍照,并将图片保存到指定Uri地址,返回true则保存成功。
代码语言:javascript
复制
  private val captureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) {
        Toast.makeText(this, "$it", Toast.LENGTH_SHORT).show()
    }

 if (havePermission(Manifest.permission.CAMERA)) {
            val name = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(System.currentTimeMillis())
            val file = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), name)
            val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
            } else {
                Uri.fromFile(file)
            }

            captureLauncher.launch(uri)
        } else {
            singlePermissionLauncher.launch(Manifest.permission.CAMERA)
        }
  • TakePicturePreview()。调用系统相机拍照,返回为Bitmap的图片。
  • TakeVideo()。调用系统录像拍摄视频,保存到给定的Uri地址,返回一张缩略图。
  • PickContact()。从手机通讯录获取联系人。
  • GetContent()。提示用户选择一条内容,返回一个通过ContentResolver.openInputStream(Uri)访问原生数据的Uri地址(content://形式) 。默认情况下,它增加了Intent.CATEGORY_OPENABLE, 返回可以表示流的内容。
  • OpenMultipleDocuments()。提示用户选择文件(可以选择多个),以List的形式,分别返回它们的Uri。
  • OpenDocumentTree()。提示用户选择一个目录,并返回用户选择的作为一个Uri返回,应用程序可以完全管理返回目录中的文档
  • OpenDocument()。提示用户打开一个文件,接收其内容(file:/http:/content:)作为Uri 。输入是要过滤的 mime 类型,例如image/* 。
  • GetMultipleContents()。提示用户选择多个内容,,以List的形式,分别返回它们的Uri。默认情况下,它增加了Intent.CATEGORY_OPENABLE, 返回可以表示流的内容。
  • CreateDocument()。提示用户选择一个文档,返回一个(file:/http:/content:)开头的Uri。
非Activity/Fragment中接收数据

在Activity和Fragment中,我们能直接使用registerForActivityResult方法 ,那是因为ConponentActivity和Fragment基类实现了ActivityResultCaller 接口,在非Activity/Fragment中,如果我们想要接收Activity回传的数据,可以直接使用ActivityResultRegistry来实现。

代码语言:javascript
复制
class MyLifecycleObserver(private val registry: ActivityResultRegistry) : DefaultLifecycleObserver {
        private lateinit var getContent: ActivityResultLauncher<String>
        private lateinit var onUriListener: OnUriListener

        override fun onCreate(owner: LifecycleOwner) {
            getContent =
                registry.register("key", owner, ActivityResultContracts.GetContent()) { uri ->
                    onUriListener.onUri(uri)
                }
        }

        fun selectImage(onUriListener: OnUriListener) {
            this.onUriListener = onUriListener
            getContent.launch("image/*")
        }
    }
代码语言:javascript
复制
 observer = MyLifecycleObserver(activityResultRegistry)
 lifecycle.addObserver(observer)
 
  observer.selectImage(object : OnUriListener {
            override fun onUri(uri: Uri) {
                try {
                    val descriptor = contentResolver.openFileDescriptor(uri, "r")
                    if (descriptor != null) {
                        val bitmap = BitmapFactory.decodeFileDescriptor(descriptor.fileDescriptor)
                        binding.ivContent.setImageBitmap(bitmap)
                    }
                } catch (e: FileNotFoundException) {
                    e.printStackTrace()
                }
            }
        })

为什么要实现LifecycleObserver呢,因为使用生命周期组件,LifecycleOwner 会在 Lifecycle 被销毁时自动移除已注册的启动器。当然你也可以手动调用unregister方法。

Activity和Fragment中为什么不需要手动调用unregister方法呢,因为ComponentActivity和Fragment已经实现了LifecycleObserver。源码如下

代码语言:javascript
复制
@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);
    }
//Fragment
 registerOnPreAttachListener(new OnPreAttachedListener() {
            @Override
            void onPreAttached() {
                final String key = generateActivityResultKey();
                ActivityResultRegistry registry = registryProvider.apply(null);
                ref.set(registry.register(key, Fragment.this, contract, callback));
            }
        });

源码

ComponentActivity中有两个registerForActivityResult方法,区别是有无ActivityResultRegistry,默认情况下使用内部定义的mActivityResultRegistry,然后调用ActivityResultRegistry.register方法。当然可以自定义ActivityResultRegistry,和前面非Activity/Fragment中接收数据中类似。

代码语言:javascript
复制
 @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);
    }

register方法内部会将ActivityResultContract存入一个HashMap中。register方法接受一个LifecycleOwner,在合适的生命周期将回调存入或移除Map,保证回调响应的时机正确。

代码语言:javascript
复制
 Lifecycle lifecycle = lifecycleOwner.getLifecycle();
 
 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);
                }
            }
        };

register方法中还发现requestCode,以前onActivityResult方法中需要通过requestCode来识别是哪个startActivityForResult方法的返回。现在都通过AutoIncrement来管理。

代码语言:javascript
复制
 final int requestCode = registerKey(key);
 return registry.register(
                "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);

onSaveInstanceState方法会自动保存requestCodeActivityResultRegistry对应的key,当onActivityResult方法返回requestCode时,可以通过对应关系找到key,然后对应ActivityResultCallback

代码语言:javascript
复制
 public final void onSaveInstanceState(@NonNull Bundle outState) {
        outState.putIntegerArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_RCS,
                new ArrayList<>(mRcToKey.keySet()));
        outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_REGISTERED_KEYS,
                new ArrayList<>(mRcToKey.values()));
        outState.putStringArrayList(KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS,
                new ArrayList<>(mLaunchedKeys));
        outState.putBundle(KEY_COMPONENT_ACTIVITY_PENDING_RESULTS,
                (Bundle) mPendingResults.clone());
        outState.putSerializable(KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT, mRandom);
    }

源码来自于ComponentActivityActivityResultRegistry,详细说明可查看源码。

总结

新的Activity Result API提供一种简便方法来完成数据接收,例如打开相机、相册,权限的处理(是时候抛弃各种权限框架了RxPermissionEasyPermissionxxx )等等,代码不仅简便,而且降低了代码的耦合,减少了样板代码。是时候跟startActivityForResult方法说再见了,新的Activity Results API,小伙伴们快快用起来吧。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 首语
  • 背景
  • 介绍
  • 使用
    • 预定义的Contract
      • 非Activity/Fragment中接收数据
      • 源码
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档