再见 onActivityResult!你好 Activity Results API

再见 onActivityResult!你好 Activity Results API

八归少年 2,455 2021-08-15

首语

匆匆又秋天⏳,岁月不堪数,故人不如初。

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

背景

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

 /**
     * {@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方法来接收返回的结果。

 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方法的返回值。

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的输出类型。

 private val myActivityLauncher = registerForActivityResult(MyResultContract()) { result ->
        Log.e("yhj", result)
    }

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

  1. 界面跳转

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

myActivityLauncher.launch(ACTION_CODE)

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

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

预定义的Contract

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

  • StartActivityForResult()。通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出。这是最常用的一个Contract。
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()。用于请求单个权限。
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()。用于请求一组权限。
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则保存成功。
  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来实现。

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/*")
        }
    }
 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。源码如下

@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中接收数据中类似。

 @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,保证回调响应的时机正确。

 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来管理。

 final int requestCode = registerKey(key);
 return registry.register(
                "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);

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

 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,小伙伴们快快用起来吧。


Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.yanghujun.com/archives/activityresultsapi