Android开发的华丽转身:从onActivityResult到ActivityResultLauncher的完美蜕变
ActivityResultLauncher是Android Jetpack引入的新API,用于替代传统的startActivityForResult(),解决了内存泄漏、代码分散和类型不安全等问题。它通过泛型提供类型安全,自动处理生命周期,将启动和回调逻辑集中管理。使用时需在Activity/Fragment中注册,支持图片选择、权限请求等常见场景。内置多种Contract如GetContent
概述
ActivityResultLauncher 是 Android Jetpack Activity 1.2.0 版本引入的新 API,用于替代传统的 startActivityForResult() 和 onActivityResult() 方法。它提供了更加类型安全、简洁和易于测试的方式来处理 Activity 间的数据传递和结果回调。
为什么需要 ActivityResultLauncher?
传统方式的问题
- 内存泄漏风险:
onActivityResult()方法可能在 Activity 销毁后仍被调用 - 代码分散:启动逻辑和结果处理逻辑分散在不同方法中
- 类型不安全:通过
Intent传递数据缺乏类型检查 - 测试困难:难以进行单元测试
- 请求码管理:需要手动管理 requestCode,容易冲突
ActivityResultLauncher 的优势
- 类型安全:通过泛型提供编译时类型检查
- 生命周期感知:自动处理生命周期,避免内存泄漏
- 代码集中:启动和结果处理逻辑在同一位置
- 易于测试:支持单元测试
- 简化API:无需管理请求码
基本使用方法
1. 基础设置
首先确保项目中包含正确的依赖:
dependencies {
implementation "androidx.activity:activity-ktx:1.7.2"
implementation "androidx.fragment:fragment-ktx:1.6.1"
}
2. 注册 ActivityResultLauncher
在 Activity 或 Fragment 中注册 launcher:
class MainActivity : AppCompatActivity() {
// 注册launcher - 必须在onCreate之前或onCreate中注册
private val imagePickerLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
// 处理结果
uri?.let {
handleSelectedImage(it)
}
}
private val customActivityLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data?.getStringExtra("result")
// 处理返回数据
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupViews()
}
private fun setupViews() {
findViewById<Button>(R.id.btnPickImage).setOnClickListener {
// 启动图片选择
imagePickerLauncher.launch("image/*")
}
findViewById<Button>(R.id.btnStartActivity).setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
customActivityLauncher.launch(intent)
}
}
private fun handleSelectedImage(uri: Uri) {
// 处理选中的图片
findViewById<ImageView>(R.id.imageView).setImageURI(uri)
}
}
常用的 ActivityResultContract
1. 内置 Contract
class ExampleActivity : AppCompatActivity() {
// 获取单个内容
private val getContentLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> /* 处理结果 */ }
// 获取多个内容
private val getMultipleContentsLauncher = registerForActivityResult(
ActivityResultContracts.GetMultipleContents()
) { uris -> /* 处理结果 */ }
// 拍照
private val takePictureLauncher = registerForActivityResult(
ActivityResultContracts.TakePicture()
) { success -> /* 处理结果 */ }
// 请求权限
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted -> /* 处理权限结果 */ }
// 请求多个权限
private val requestMultiplePermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions -> /* 处理权限结果 */ }
// 创建文档
private val createDocumentLauncher = registerForActivityResult(
ActivityResultContracts.CreateDocument("text/plain")
) { uri -> /* 处理结果 */ }
// 打开文档
private val openDocumentLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri -> /* 处理结果 */ }
}
2. 自定义 Contract
class CustomContract : ActivityResultContract<String, String?>() {
override fun createIntent(context: Context, input: String): Intent {
return Intent(context, CustomActivity::class.java).apply {
putExtra("input_data", input)
}
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
return when (resultCode) {
Activity.RESULT_OK -> intent?.getStringExtra("result_data")
else -> null
}
}
}
// 使用自定义Contract
class MainActivity : AppCompatActivity() {
private val customLauncher = registerForActivityResult(CustomContract()) { result ->
result?.let {
// 处理自定义结果
Toast.makeText(this, "结果: $it", Toast.LENGTH_SHORT).show()
}
}
private fun launchCustomActivity() {
customLauncher.launch("输入数据")
}
}
完整示例:文件选择器
这里是使用 ActivityResultLauncher 重构文件选择功能:
class ImprovedMainWebViewActivity : AppCompatActivity() {
// 注册文件选择器
private val filePickerLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
uri?.let { handleSelectedFile(it) }
}
// 注册文档选择器(推荐用于文件选择)
private val documentPickerLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocument()
) { uri ->
uri?.let { handleSelectedFile(it) }
}
// 注册多文件选择器
private val multipleDocumentsLauncher = registerForActivityResult(
ActivityResultContracts.OpenMultipleDocuments()
) { uris ->
uris.forEach { handleSelectedFile(it) }
}
// 注册存储权限请求
private val storagePermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val allGranted = permissions.values.all { it }
if (allGranted) {
openFileSelector()
} else {
Toast.makeText(this, "需要存储权限才能选择文件", Toast.LENGTH_SHORT).show()
}
}
private var currentFileType = 3 // 默认所有文件
private var currentScene = 10 // 默认场景
fun takeFiles(scene: Int, type: Int = 3) {
LogUtils.d("=== takeFiles 使用 ActivityResultLauncher ===")
LogUtils.d("scene: $scene, type: $type")
currentScene = scene
currentFileType = type
// 检查权限
if (hasStoragePermission()) {
openFileSelector()
} else {
requestStoragePermission()
}
}
private fun hasStoragePermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
private fun requestStoragePermission() {
storagePermissionLauncher.launch(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
}
private fun openFileSelector() {
val mimeType = when (currentFileType) {
3 -> "*/*"
4 -> "video/*"
5 -> "application/pdf"
6 -> "application/vnd.ms-excel"
7 -> "application/msword"
else -> "*/*"
}
try {
// 使用 OpenDocument,更适合文件选择
documentPickerLauncher.launch(arrayOf(mimeType))
} catch (e: Exception) {
LogUtils.e("文件选择器启动失败", e)
// 降级到基础的 GetContent
try {
filePickerLauncher.launch(mimeType)
} catch (e2: Exception) {
LogUtils.e("降级方案也失败", e2)
Toast.makeText(this, "无法打开文件选择器", Toast.LENGTH_SHORT).show()
}
}
}
private fun handleSelectedFile(uri: Uri) {
// 文件处理逻辑...
LogUtils.d("处理选中的文件: $uri")
// 其他处理逻辑保持不变
}
}
重要注意事项
1. 注册时机
class WrongExample : AppCompatActivity() {
private lateinit var launcher: ActivityResultLauncher<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ❌ 错误:在条件语句中注册
if (someCondition) {
launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { }
}
// ❌ 错误:在点击事件中注册
button.setOnClickListener {
launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { }
}
}
}
class CorrectExample : AppCompatActivity() {
// ✅ 正确:在类级别声明和注册
private val launcher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
// 处理结果
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ✅ 正确:在onCreate中注册也可以
val anotherLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
// 处理结果
}
}
}
2. 生命周期管理
class LifecycleExample : AppCompatActivity() {
private val launcher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
// ✅ 这里会自动检查生命周期状态
// 只有在STARTED状态之后才会执行
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
handleResult(uri)
}
}
private fun handleResult(uri: Uri?) {
// 处理结果时确保Activity仍然活跃
if (!isFinishing && !isDestroyed) {
// 安全地更新UI
}
}
}
3. Fragment 中的使用
class ExampleFragment : Fragment() {
// ✅ 正确:在Fragment中注册
private val launcher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
// 处理结果
if (isAdded && context != null) {
handleResult(uri)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.button).setOnClickListener {
launcher.launch("image/*")
}
}
}
4. 内存泄漏防护
class MemoryLeakSafeExample : AppCompatActivity() {
private val launcher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
// 使用弱引用避免内存泄漏
weakReference.get()?.let { activity ->
if (!activity.isFinishing) {
activity.handleResult(uri)
}
}
}
private val weakReference = WeakReference(this)
private fun handleResult(uri: Uri?) {
// 处理结果
}
}
最佳实践
1. 错误处理
class ErrorHandlingExample : AppCompatActivity() {
private val launcher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
try {
if (uri != null) {
handleFile(uri)
} else {
// 用户取消选择
Toast.makeText(this, "未选择文件", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
LogUtils.e("处理文件失败", e)
Toast.makeText(this, "文件处理失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
private fun launchFilePicker() {
try {
launcher.launch("*/*")
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "设备不支持文件选择", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
LogUtils.e("启动文件选择器失败", e)
Toast.makeText(this, "无法打开文件选择器", Toast.LENGTH_SHORT).show()
}
}
}
2. 链式权限请求
class PermissionChainExample : AppCompatActivity() {
private val cameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// 权限获得,请求存储权限
storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else {
Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show()
}
}
private val storagePermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// 所有权限都获得,启动相机
openCamera()
} else {
Toast.makeText(this, "需要存储权限", Toast.LENGTH_SHORT).show()
}
}
private val cameraLauncher = registerForActivityResult(
ActivityResultContracts.TakePicture()
) { success ->
if (success) {
Toast.makeText(this, "拍照成功", Toast.LENGTH_SHORT).show()
}
}
private fun requestPermissionsAndTakePhoto() {
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
private fun openCamera() {
val photoFile = createImageFile()
val photoURI = FileProvider.getUriForFile(
this,
"${applicationContext.packageName}.provider",
photoFile
)
cameraLauncher.launch(photoURI)
}
}
3. 测试支持
class TestableActivity : AppCompatActivity() {
// 使用接口来支持测试
interface FilePicker {
fun pickFile(callback: (Uri?) -> Unit)
}
class RealFilePicker(private val activity: AppCompatActivity) : FilePicker {
private val launcher = activity.registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri ->
callback?.invoke(uri)
}
private var callback: ((Uri?) -> Unit)? = null
override fun pickFile(callback: (Uri?) -> Unit) {
this.callback = callback
launcher.launch("*/*")
}
}
private val filePicker: FilePicker = RealFilePicker(this)
fun selectFile() {
filePicker.pickFile { uri ->
handleSelectedFile(uri)
}
}
private fun handleSelectedFile(uri: Uri?) {
// 处理文件
}
}
// 测试类
class TestableActivityTest {
class MockFilePicker : TestableActivity.FilePicker {
override fun pickFile(callback: (Uri?) -> Unit) {
// 模拟文件选择
callback.invoke(mockUri)
}
}
// 测试方法...
}
性能优化建议
1. 避免重复注册
// ❌ 错误:每次都创建新的launcher
class BadExample : AppCompatActivity() {
fun pickImage() {
val launcher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> /* 处理 */ }
launcher.launch("image/*")
}
}
// ✅ 正确:复用launcher
class GoodExample : AppCompatActivity() {
private val imageLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> /* 处理 */ }
fun pickImage() {
imageLauncher.launch("image/*")
}
}
2. 合理选择Contract类型
class OptimizedExample : AppCompatActivity() {
// 针对不同场景使用不同的Contract
private val imageLauncher = registerForActivityResult(
ActivityResultContracts.GetContent() // 快速图片选择
) { uri -> handleImage(uri) }
private val documentLauncher = registerForActivityResult(
ActivityResultContracts.OpenDocument() // 文档选择,支持持久化权限
) { uri -> handleDocument(uri) }
private val multipleLauncher = registerForActivityResult(
ActivityResultContracts.GetMultipleContents() // 多文件选择
) { uris -> handleMultipleFiles(uris) }
fun chooseFile(type: String) {
when (type) {
"image" -> imageLauncher.launch("image/*")
"document" -> documentLauncher.launch(arrayOf("application/pdf"))
"multiple" -> multipleLauncher.launch("*/*")
}
}
}
迁移指南
从 startActivityForResult 迁移
// 旧代码
class OldWay : AppCompatActivity() {
companion object {
const val REQUEST_IMAGE = 1001
const val REQUEST_DOCUMENT = 1002
}
fun pickImage() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(intent, REQUEST_IMAGE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_IMAGE -> {
if (resultCode == RESULT_OK) {
val uri = data?.data
handleImage(uri)
}
}
REQUEST_DOCUMENT -> {
if (resultCode == RESULT_OK) {
val uri = data?.data
handleDocument(uri)
}
}
}
}
}
// 新代码
class NewWay : AppCompatActivity() {
private val imageLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleImage(uri) }
private val documentLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri -> handleDocument(uri) }
fun pickImage() {
imageLauncher.launch("image/*")
}
fun pickDocument() {
documentLauncher.launch("application/pdf")
}
}
总结
ActivityResultLauncher 是 Android 现代化开发的重要组成部分,它提供了:
- 更好的类型安全性
- 自动的生命周期管理
- 简化的API设计
- 更好的测试支持
- 避免内存泄漏
在新项目中建议直接使用 ActivityResultLauncher,在现有项目中建议逐步迁移。遵循本文提到的最佳实践,可以写出更加健壮和可维护的代码。
关键记住点
- 必须在 onCreate 之前或onCreate中注册
- 回调会自动处理生命周期
- 选择合适的 Contract 类型
- 做好错误处理
- 支持单元测试
ActivityResultLauncher 让 Android 开发变得更加现代化和安全,是每个 Android 开发者都应该掌握的重要工具。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)