startActivityForResult被标记为弃用后,如何优雅的启动Activity?

startActivityForResult被标记为弃用后,如何优雅的启动Activity?startActivityForResult标记为弃用后,如何优雅的启动Activity?ActivityResultAPI已经出来有一段时间了,但是还是有很多朋友对这个API感到使用不便或疑惑,今天尽量用一篇简短的文章简述下registerForActivityResult的使用方法。

大家好,又见面了,我是你们的朋友全栈君。

转载请注明出处:https://blog.csdn.net/hx7013/article/details/120916287

Activity Result API已经出来有一段时间了,但是还是有很多朋友对这个API感到使用不便或疑惑,今天尽量用一篇简短的文章简述下registerForActivityResult的使用方法。

一、如何解决 startActivityForResult 被弃用?

ManageStartActivity

可以明显的看到,在androidx.activity1.2.0-alpha04时开始,Android中这位你调用过无数次的startActivityForResultonActivityResult,已经被官方标记为弃用了,继而推出了名为Activity Result API的组件。

弃用原因也许是onActivityResult里需要处理的各种判断、嵌套,也许是既要处理requestCode也要处理resultCode这种高耦合难以维护的Id判断模式。但其原因已不重要了,因为既然Android里已提供了更好的方案并把startActivityForResult标记为了弃用,那么我们就应该开始了解一下位于 ComponentActivityFragment 中的registerForActivityResult了。

这里先做一个简单的对比,来了解下registerForActivityResult的简单及清爽

// startActivityForResult
override fun onCreate(savedInstanceState: Bundle?) { 
   
    super.onCreate(savedInstanceState)

    startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 
   
    super.onActivityResult(requestCode, resultCode, data)

    when (requestCode) { 
   
        REQUEST_CODE -> { 
   
           val code = resultCode
           val data = data
        }
    }
}

companion object { 
   
    private const val REQUEST_CODE = 1024
}

// registerForActivityResult
private lateinit var resultLauncher: ActivityResultLauncher<Intent>

private val launcherCallback = ActivityResultCallback<ActivityResult> { 
    result ->
    val code = result.resultCode
    val data = result.data
}

override fun onCreate(savedInstanceState: Bundle?) { 
   
    super.onCreate(savedInstanceState)
    resultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult(),
        launcherCallback)

    resultLauncher.launch(Intent(this,SecondActivity::class.java))
}

代码一看完,是不是第一感觉不对啊,怎么感觉比之前还复杂了,其实这里是为了让你更直观的了解到这个registerForActivityResult到底是什么东西,所以对载体、定义协定、回调3个类分别定义写出来。其实大部分情况我们像以下代码其实这么写就可以了

private val launcherActivity = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()) { 
   
    val code = it.resultCode
    val data = it.data
}

override fun onCreate(savedInstanceState: Bundle?) { 
   
    super.onCreate(savedInstanceState)
    launcherActivity.launch(Intent(this, SecondActivity::class.java))
}

是不是瞬间清爽了许多,但是…你还是觉得比使用startActivityForResult更复杂?其实不然,因为上面代码的需求是一个单一的回调,所以看着似乎startActivityForResult更便于维护和使用。但倘若编写一个稍复杂的页面,需要同时请求相册、需要在其它Activity选择数据并回调、需要判断权限等等时,继续使用startActivityForResult,会导致onActivityResult里掺杂各种嵌套及判断,导致代码难以维护。而使用registerForActivityResult()可以多次调用以注册多个 ActivityResultLauncher 实例,用来处理不同的Activity结果,让代码更便于维护。

优势了解到了,但既然需要使用新的功能,那么我们就必须要先了解以下,刚说到的ActivityResultLauncherActivityResultContractActivityResultCallback到底是些什么东西

  • ActivityResultLauncher 从字面意思其实就能很好理解,可以理解它就是一个Activity的启动器,它的作用就是承载启动对象与返回对象,通过registerForActivityResult返回该对象,这时并不会立即启动另一个Activity。
  • ActivityResultContract 是用来协定所需的输入类型以及结果的输出类型,Android默认提供了一些常用的定义,例如上面所使用到到ActivityResultContracts.StartActivityForResult()。当然这里你也可以通过继承ActivityResultContract实现自己的定义。
  • ActivityResultCallback 通过名字就可以了解到这是启动Activity并返回到当前Activity时的结果回调。

对于这3个类,其实只需重点了解ActivityResultContract,就能很轻松的理解并使用好Activity Result API了。

同时,引用一个官方文档的警告 ↓

注意:虽然在 fragment 或 activity 创建完毕之前可安全地调用 registerForActivityResult(),但在 fragment 或 activity 的 Lifecycle 变为 CREATED 状态之前,您无法启动 ActivityResultLauncher

二、ActivityResultContract 该如何使用?

刚才的例子中,其实已经简单的使用到Android提供的一个默认协定ActivityResultContracts.StartActivityForResult()来启动了一个Activity并获得想要的返回值。除了StartActivityForResult(),Android还提供了以下的默认协定以便于开发者的使用

ActivityResultContracts.* 说明 参数 回调
StartActivityForResult 可以理解为startActivityForResult Intent ActivityResult(code,data)
TakePicture 通过MediaStore.ACTION_IMAGE_CAPTURE拍照并保存 保存文件的Uri 是否保存成功
TakePicturePreview 通过MediaStore.ACTION_IMAGE_CAPTURE拍照 null(Void) 图片的Bitmap
CaptureVideo 通过MediaStore.ACTION_VIDEO_CAPTURE拍摄视频并保存(androidx.activity 1.3.0-alpha08后提供,androidx.appcompat好像还没提供该类) 保存文件的Uri 是否保存成功。
RequestPermission 请求单个权限 Manifest.permission.* 用户是否授予该权限
RequestMultiplePermissions 请求多个权限 Array<Manifest.permission.*> 回调为map, key为请求的权限,value为用户是否授予该权限
CreateDocument 通过Intent.ACTION_CREATE_DOCUMENT创建一个文件 默认文件名 选择目录后返回该文件的Uri
GetContent 通过Intent.ACTION_GET_CONTENT获取一个文件(这个方法可以通过android.content.ContentResolver.openInputStream获取到文件的原始数据) MIME类型 文件Uri
GetMultipleContents 通过Intent.ACTION_GET_CONTENTIntent.EXTRA_ALLOW_MULTIPLE获取一个或多个文件(这个方法可以通过android.content.ContentResolver.openInputStream获取到文件的原始数据) MIME类型 文件List
OpenDocument 通过Intent.ACTION_OPEN_DOCUMENT选择文件 MIME类型 文件Uri
OpenDocumentTree 通过Intent.ACTION_OPEN_DOCUMENT_TREE选择一个目录,返回一个Uri并得到该目录下全部文档的管理权 目录初始位置Uri 选择目录Uri
OpenMultipleDocuments 通过Intent.ACTION_OPEN_DOCUMENTIntent.EXTRA_ALLOW_MULTIPLE获取一个或多个文件 MIME类型 文件List
PickContact 通过Intent.ACTION_PICK从系统通讯录中获取联系人 null(Void) 联系人Uri
StartIntentSenderForResult 构建IntentSenderPendingIntent 使用IntentSenderRequest.Builder构建 ActivityResult(code,data)
TakeVideo 通过MediaStore.ACTION_VIDEO_CAPTURE拍摄视频并保存(弃用了,官方解释是缩略图的Bitmap的返回不稳定,替换为上面的CaptureVideo即可) 保存文件的Uri 视频缩略图Bitmap

以上全部ActivityResultContracts可在GitHub查看完整示例源码

OK,到此是不是慢慢开始感觉到Activity Result API的便捷了。
虽然Android提供的默认协定ActivityResultContracts已经很丰富了,但是为了自己应用内Activity类型安全的传递或是解耦,有时我们需要自己创建一个ActivityResultContract。其实查看ActivityResultContracts任意一个类的源码,发现自己实现ActivityResultContract并不复杂,只需继承ActivityResultContract,即可实现类型安全的Activity启动与数据返回。

class ContractActivity : AppCompatActivity() { 
   

......

    /** * 继承[ActivityResultContract] * * 泛型第一个类型为传入到 [ContractActivity] 的参数类型 * 第二个类型为 [ContractActivity] 返回给启动Activity的返回值类型 */
    class Contract : ActivityResultContract<String, String>() { 
   
        /** * 创建启动Intent * @param context [Context] * @param input 当前类的第一个泛型参, 这里自己实现传递过程 */
        override fun createIntent(context: Context, input: String): Intent =
            Intent(context, ContractActivity::class.java).apply { 
   
                putExtra(EXTRA_NAME, "$input - ${ 
     System.currentTimeMillis()}")
            }

        /** * 解析结果 * @param resultCode [Activity.setResult] 的 resultCode * @param intent [Activity.setResult] 的 intent * 其实这里类似 [Activity.onActivityResult] 处理过程,只是由于返回是明确的,所以少了requestCode */
        override fun parseResult(resultCode: Int, intent: Intent?): String { 
   
            return if (resultCode == Activity.RESULT_OK && intent != null) { 
   
                "${ 
     intent.getStringExtra(EXTRA_RESULT)} - ${ 
     System.currentTimeMillis()}"
            } else { 
   
                "error"
            }
        }

        companion object { 
   
            /** EXTRA */
            const val EXTRA_NAME = "extra_name"
            const val EXTRA_RESULT = "extra_result"
        }
    }
}

定义好一个Contract后,在使用Activity Result API启动ContractActivity时,只需调用

val launcherContractActivity = registerForActivityResult(ContractActivity.Contract()) { 
    result: String -> }

launcherContractActivity.launch("hi, Activity Result API!")

怎么样,是不是瞬间觉得特别方便?而且这种方式让启动Activity解耦得很彻底,启动方能明确的知道该传什么值给被启动的Activity,也能明确的知道被启动Activity会返回什么数据。

以上全部ActivityResultContracts可在GitHub查看完整示例源码

三、但是…我就想简单的使用startActivityForResult怎么办?

虽然Activity Result API非常强大与便捷,但在国内各厂商深度定制系统的情况下,权限申请操作一般我们还是会使用到第三方框架,拍照、视频录制大部分情况使用系统界面操作肯定也不适用。所以Activity Result API里,我的刚需似乎只是一个startActivityForResult那么简单, 那有更便捷的方法吗?

首先,尝试一个直接在按钮点击时创建一个ActivityResultLauncher并启动

viewBinding.btnStartActivityForResult.setOnClickListener { 
   
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { 
    result->
       val code = result.resultCode
       val data = result.data
     }.launch(Intent(this@ActivityResultContractsActivity, SecondActivity::class.java))
}

看着似乎很方便,又不需要提前注册,即用即回调。但是很不幸,这样使用你会收获一个IllegalStateException,异常里也说得很明白LifecycleOwner xxx is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.,必须在STARTED前调用registerForActivityResult

看来我们必须每次都必须通过registerForActivityResult预定一个ActivityResultLauncher,在需要的时候再去launch,这…似乎还是比较麻烦。那么来看看一个ManageStartActivity小框架吧。

? ManageStartActivity – Github 一个基于Activity Result API搭建的纯粹启动Activity的小框架。

何为纯粹?就是只负责启动Acitivity,权限、拍照、录像等等虽然Activity Result API都支持,但是该框架都不去实现这些功能。原因一,刚也描述过,这些预定的Contract可能在国内大环境下兼容不一定好,或者UI及功能上过于简单不能满足实际功能需求。原因二,是若想自己调用,使用其实也很简单,只需调用Android提供的这些默认协定即可。所以ManageStartActivity只是纯粹的为了减轻使用Activity Result API启动Activity时过于繁琐的那些步骤。

只需两步,轻松实现startActivityForResult

  1. ComponentActivityFragment 实现 IMangeStartActivity接口,并委托给MangeStartActivity处理
  2. onCreate时调用initManageStartActivity()即可
abstract class BaseActivity : AppCompatActivity(), IMsa by msa() { 
   
    override fun onCreate(savedInstanceState: Bundle?) { 
   
        super.onCreate(savedInstanceState)
        initManageStartActivity()
    }
}

推荐在BaseActivityBaseFragment里实现 IMangeStartActivity接口, 这样就能愉快的在任意继承Base的页面中使用startActivityForResult

// 注意实现 `IMsa by msa()`
class SampleActivity : AppCompatActivity(), IMsa by msa() { 
   

    override fun onCreate(savedInstanceState: Bundle?) { 
   
        super.onCreate(savedInstanceState)
        // 必须在super.onCreate()后初始化 initMangeStartActivity
        // 因为需要使用 SavedStateRegistry 来保存回调状态
        initManageStartActivity()
    }

    /** * 直接启动Activity * 3 种方式 */
    fun startActivity() { 
   
        // Android习惯模式,传入KClass即可
        startActivity(MainActivity::class) { 
   
            putExtra("key", "value")
        }

        // KClass扩展方法模式
        MainActivity::class.start { 
   
            putExtra("key", "value")
        }

        // Intent扩展方法模式
        Intent(this, MainActivity::class.java).apply { 
   
            putExtra("key", "value")
        }.start()
    }

    /** * 启动Activity并需要回调结果 * 4 种方法 */
    fun startActivityForResult() { 
   
        // Android习惯模式,传入KClass即可
        startActivityForResult(MainActivity::class, { 
   
            putExtra("key", "value")
        }) { 
    code: Int, data: Intent? ->
            // code = resultCode
        }

        // KClass扩展方法模式
        MainActivity::class.startForResult({ 
   
            putExtra("key", "value")
        }) { 
   code: Int, data: Intent? ->
            // code = resultCode
        }

        // Android习惯模式,传入Intent即可
        startActivityForResult(Intent(this, MainActivity::class.java).apply { 
   
            putExtra("key", "value")
        }){ 
    code: Int, data: Intent? ->
            // code = resultCode
        }

        // Intent扩展方法模式
        Intent(this, MainActivity::class.java).apply { 
   
            putExtra("key", "value")
        }.startForResult { 
    code: Int, data: Intent? ->
            // code = resultCode
        }
    }

    /** * Kotlin协程挂起函数 * 4 种方式 */
    fun startActivityForResultCoroutine() { 
   
        lifecycleScope.launch { 
   
            // Android习惯模式,传入KClass即可
           val (code1: Int, data1: Intent?) = startActivityForResultSync(MainActivity::class) { 
   
               putExtra("key", "value")
           }

            // KClass扩展方法模式
            val (code2: Int, data2: Intent?) = MainActivity::class.startForResultSync { 
   
                putExtra("key", "value")
            }

            // Android习惯模式,传入Intent即可
            val (code3: Int, data3: Intent?) = startActivityForResultSync(Intent(this@SampleActivity, MainActivity::class.java).apply { 
   
                putExtra("key", "value")
            })

            // Intent扩展方法模式
            val (code4: Int, data4: Intent?) = Intent(this@SampleActivity, MainActivity::class.java).apply { 
   
                putExtra("key", "value")
            }.startForResultSync()
        }
    }
}

对的,要的就是这种纯粹的startActivityForResult的感觉。

四、总结

Activity Result API和更优雅的使用startActivityForResult现在想必你都已经会使用了,是不是比你想象的更简单?
如果有需要,我会再写一篇使用较少的在单独的类中接收 Activity 结果Activity Result API的源码解析。技术有限,若文中有错误遗漏之处,尽情谅解,也欢迎指正共同进步。

转载请注明出处:https://blog.csdn.net/hx7013/article/details/120916287

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/157214.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • idea中选中一行的快捷键_idea撤销快捷键恢复

    idea中选中一行的快捷键_idea撤销快捷键恢复之前前端开发一直使用VSCode,常用快捷键删除一行或者当前选中的几行代码,使用idea的时候发现快捷键并不相同,查看发现idea的快捷是:Ctrl+Y,比手动删除代码方便很多。通过File->Setttings->Keymap可以查看已经设置好的快捷键:…

    2025年9月27日
    4
  • java 日期格式化– SimpleDateFormat 的使用。字符串转日期,日期转字符串

    java 日期格式化– SimpleDateFormat 的使用。字符串转日期,日期转字符串日期和时间格式由日期和时间模式字符串指定。在日期和时间模式字符串中,未加引号的字母’A’到’Z’和’a’到’z’被解释为模式字母,用来表示日期或时间字符串元素。文本可以使用单引号(‘)引起来,以免进行解释。所有其他字符均不解释;只是在格式化时将它们简单复制到输出字符串白话文的讲:这些A——Z,a——z这些字母(不被单引号包围的)会被特殊处理替换为对应的日期时间,其他的字…

    2022年6月12日
    53
  • java 自定义的类加载器_Java如何自定义类加载器[通俗易懂]

    java 自定义的类加载器_Java如何自定义类加载器[通俗易懂]我们可以编写自己的用于特殊目的的类加载器,这使得我们可以在向虚拟机传递字节码之前执行定制的检查。如何自定义类加载器如果想要编写自己的类加载器,只需要两步:继承ClassLoader类覆盖findClass(StringclassName)方法ClassLoader超类的loadClass方法用于将类的加载操作委托给其父类加载器去进行,只有当该类尚未加载并且父类加载器也无法加载该类时,才调用fin…

    2025年9月21日
    9
  • 基于STC89C51/2的的超声波测距(1602A显示)「建议收藏」

    基于STC89C51/2的的超声波测距(1602A显示)「建议收藏」基于STC89C51/2的的超声波测距(1602A显示) 楼主在做学校单片机设计的时候为了测试超声波模块的功能与精度,动手DIY一款能用的超声波测距仪器,用1602液晶显示屏动态显示,精度在0.5cm左右,粗略测距,可用于避障,检测距离等的实际用途中。 我的超声波模块使用的是HC-SR04,板子使用的是STC89C52RC学习版(理论上51和52均可使 用)。得到距离之后可用于扩展功能,之后有空可以把我的经验分享出来,欢迎讨论。我的引脚接口如下ECHO=P2^4;

    2025年12月2日
    8
  • 在idea中配置 gitignore忽略文件(一)

    在idea中配置 gitignore忽略文件(一)针对一些不用每次提交的文件 设置不让其提交到 git 的本地仓库中 先在 idea 中安装 gitignore 插件点击 File gt Settings 选择 plugs 在右边搜索 ignore 点击 Install 安装完成后就可以愉快的使用了 不过在此之前得重启 IDEA 现在项目中生成模板在项目上右键 gt New gt ignorefile gt gitign

    2026年1月30日
    1
  • Java的输入输出语句_c语言有没有输入输出语句

    Java的输入输出语句_c语言有没有输入输出语句一、概述  输入输出可以说是计算机的基本功能。作为一种语言体系,java中主要按照流(stream)的模式来实现。其中数据的流向是按照计算机的方向确定的,流入计算机的数据流叫做输入流(inputStream),由计算机发出的数据流叫做输出流(outputStream)。Java语言体系中,对数据流的主要操作都封装在java.io包中,通过java.io包中的类可以实现计算机对数据的输入、输出操作…

    2022年4月19日
    41

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号