当前位置: 首页 > news >正文

安卓ActivityResultContracts实战:除了StartActivityForResult,GetContent和TakePicture怎么用?

安卓ActivityResultContracts高阶实战:解锁GetContent与TakePicture的隐藏技巧

在安卓开发中,处理Activity之间的数据回传一直是高频需求。随着onActivityResult的废弃,registerForActivityResult配合预设的ActivityResultContracts成为了更优雅的解决方案。本文将深入探讨如何利用这些"开箱即用"的Contract简化常见场景开发,特别是GetContentTakePicture的高级用法。

1. 从传统到现代:为何需要ActivityResultContracts

过去十年间,安卓开发者处理Activity结果的标准方式一直是重写onActivityResult方法。这种方式虽然简单直接,但存在几个明显缺陷:

  • 代码臃肿:所有结果处理逻辑都集中在一个方法内
  • 类型不安全:结果数据需要手动解析和类型转换
  • 请求码管理复杂:需要开发者自行维护请求码常量
// 传统方式示例 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when(requestCode) { REQUEST_IMAGE_CAPTURE -> if(resultCode == RESULT_OK) { val imageBitmap = data?.extras?.get("data") as Bitmap // 处理图片 } REQUEST_PICK_IMAGE -> if(resultCode == RESULT_OK) { val imageUri = data?.data // 处理URI } // 更多case... } }

新的API通过类型安全的Contract和分离的回调解决了这些问题。核心优势包括:

  • 类型安全:每个Contract明确定义输入输出类型
  • 逻辑解耦:不同操作有独立的结果处理器
  • 代码简洁:消除了请求码管理
  • 内置常用Contract:覆盖80%常见场景

2. 核心Contract深度解析

2.1 GetContent:不只是选择图片

GetContent是最常用的Contract之一,主要用于从系统应用获取内容。典型用法是选择图片:

val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { imageView.setImageURI(it) } } // 触发选择图片 getContent.launch("image/*")

但它的能力远不止于此:

  • 支持多种MIME类型

    • "image/*":所有图片类型
    • "video/*":视频文件
    • "application/pdf":PDF文档
    • "*/*":任意文件类型
  • 高级配置技巧

    • 使用Intent.createChooser提供自定义选择器标题
    • 结合ClipData处理多选场景(需自定义Contract)
val getContent = registerForActivityResult( object : ActivityResultContract<String, Uri?>() { override fun createIntent(context: Context, input: String): Intent { return Intent.createChooser( Intent(Intent.ACTION_GET_CONTENT).apply { type = input putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) }, "选择您的文件" ) } override fun parseResult(resultCode: Int, intent: Intent?): Uri? { return intent?.data } } ) { uri -> /* 处理结果 */ }

2.2 TakePicture:拍照功能的最佳实践

TakePictureContract简化了拍照功能实现,但有几个关键细节需要注意:

  • URI管理:必须提供文件URI来保存照片
  • 权限处理:需要CAMERA权限和存储权限
  • 图片方向:可能需要处理EXIF旋转信息

完整实现示例:

// 在Activity/Fragment中 private var photoUri: Uri? = null private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success -> if(success && photoUri != null) { imageView.setImageURI(photoUri) } } fun capturePhoto() { // 确保有权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { // 创建临时文件 photoUri = FileProvider.getUriForFile( this, "${packageName}.fileprovider", File.createTempFile("photo_", ".jpg", externalCacheDir) ) takePicture.launch(photoUri) } else { // 请求权限 requestPermissionLauncher.launch(Manifest.permission.CAMERA) } }

提示:考虑使用TakePicturePreviewContract直接返回Bitmap,但注意大图可能导致内存问题

3. 实战组合应用:头像选择功能

结合多个Contract实现完整的头像选择功能:

  1. 提供三种方式:拍照、相册选择、取消
  2. 对选择的图片进行裁剪
  3. 处理权限和异常情况
class AvatarPickerActivity : AppCompatActivity() { private lateinit var takePicture: ActivityResultLauncher<Uri> private lateinit var getContent: ActivityResultLauncher<String> private lateinit var cropImage: ActivityResultLauncher<Uri> private var tempPhotoUri: Uri? = null private var selectedImageUri: Uri? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 初始化拍照Contract takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success -> if(success) tempPhotoUri?.let { cropImage.launch(it) } } // 初始化选择图片Contract getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { cropImage.launch(it) } } // 初始化裁剪Contract cropImage = registerForActivityResult(CropImageContract()) { uri -> uri?.let { selectedImageUri = it avatarImageView.setImageURI(it) } } } fun onCameraClick() { tempPhotoUri = createTempImageUri() takePicture.launch(tempPhotoUri) } fun onGalleryClick() { getContent.launch("image/*") } private fun createTempImageUri(): Uri { return FileProvider.getUriForFile( this, "${packageName}.fileprovider", File.createTempFile("avatar_", ".jpg", externalCacheDir) ) } } // 自定义图片裁剪Contract class CropImageContract : ActivityResultContract<Uri, Uri?>() { override fun createIntent(context: Context, input: Uri): Intent { return Intent("com.android.camera.action.CROP").apply { setDataAndType(input, "image/*") putExtra("crop", "true") putExtra("aspectX", 1) putExtra("aspectY", 1) putExtra("outputX", 300) putExtra("outputY", 300) putExtra("return-data", false) putExtra(MediaStore.EXTRA_OUTPUT, createTempFileUri(context)) } } override fun parseResult(resultCode: Int, intent: Intent?): Uri? { return if(resultCode == Activity.RESULT_OK) intent?.data else null } private fun createTempFileUri(context: Context): Uri { // 实现创建临时文件并返回URI } }

4. 进阶技巧与性能优化

4.1 自定义Contract的最佳实践

当内置Contract不能满足需求时,可以创建自定义Contract。关键考虑因素:

  • 输入输出类型:明确Contract的输入和输出类型
  • 生命周期感知:确保Contract不持有Context引用
  • 异常处理:考虑各种失败场景

文件下载Contract示例:

class DownloadFileContract : ActivityResultContract<String, File?>() { override fun createIntent(context: Context, url: String): Intent { return Intent(context, DownloadService::class.java).apply { putExtra("url", url) } } override fun parseResult(resultCode: Int, intent: Intent?): File? { if(resultCode != Activity.RESULT_OK) return null val filePath = intent?.getStringExtra("file_path") ?: return null return File(filePath) } }

4.2 内存与性能优化

使用这些Contract时需要注意:

  • 大图处理:避免直接加载大图到内存
  • URI权限:使用FileProvider并管理URI权限
  • 生命周期:在onCreate中注册,避免重复注册

推荐的做法:

// 使用Glide加载大图 getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { Glide.with(this) .load(uri) .override(1024, 1024) .into(imageView) } } // 及时释放资源 override fun onDestroy() { super.onDestroy() tempPhotoUri?.let { uri -> contentResolver.delete(uri, null, null) } }

4.3 测试策略

为Activity结果处理编写测试:

@Test fun testGetContentContract() { // 准备测试数据 val testUri = Uri.parse("content://test/image") val intent = Intent().apply { data = testUri } // 创建Contract实例 val contract = ActivityResultContracts.GetContent() // 验证parseResult val result = contract.parseResult(Activity.RESULT_OK, intent) assertEquals(testUri, result) // 验证createIntent val createdIntent = contract.createIntent(ApplicationProvider.getApplicationContext(), "image/*") assertEquals(Intent.ACTION_GET_CONTENT, createdIntent.action) assertEquals("image/*", createdIntent.type) }

在实际项目中,我们发现合理使用这些Contract可以使代码量减少40%以上,同时提高可维护性。特别是在需要处理多种数据源(如相机、相册、文件管理器等)的场景下,新API的优势更加明显。

http://www.jsqmd.com/news/928429/

相关文章:

  • 中文BERT抽取式问答实战包:PyTorch版知乎数据训练全流程(含预处理、模型、脚本与预训练权重)
  • 深入STM32定时器与ADC联动:FOC三电阻采样的时序逻辑全解析
  • STM32H7片上DAC性能压榨实战:DMA双缓冲+大容量RAM波表实现超低失真DDS
  • 家用人工智能实用功能揭秘:包裹识别、漏水检测等让生活更便捷!
  • 告别手写轮播!用vue3-scroll-seamless插件5分钟搞定列表无缝滚动(含Vue2/Vue3配置差异)
  • 别再只用DataParallel了!PyTorch DDP分布式训练保姆级配置指南(含launch命令详解)
  • LLM隐藏听觉知识如何预测音频语言模型性能:从文本基准到多模态系统设计
  • 深入浅出聊ARM Cortex-M:DMIPS和CoreMark这两个性能指标,到底该怎么看?
  • 山东皇固金属 - 博客万
  • 5月AI行业大事件:阿里“卖AI”装进收银台,字节“做AI”关进实验室
  • 越过山丘:35+ Java程序员的破局与重生——从“青春饭”到“长青树”的职业跃迁指南
  • CSS网页布局
  • 微信小程序单击元素切换元素的显示和隐藏
  • 别再傻傻轮询了!用STM32F1的DMA双缓存接收不定长数据,CPU占用率直降90%
  • Unity 2020 + EasyAR 4.2 保姆级教程:从导入SDK到打包APK,手把手教你做个图像识别AR App
  • 哈尔滨黄金回收市场现状与六家正规机构实操指南 - 专业黄金回收
  • 官方权威排名|2026年6月青海旅行社TOP5推荐(高口碑0购物、纯玩首选,来青海旅游必看!) - 寻茫精选
  • 北京老旧小区黄金变现难?足不出户上门回收成新趋势 - 黄金上门回收
  • 告别卡死!用这招彻底解决Win11上VMware Player/Workstation的CPU占用率爆满问题
  • SI9000损耗仿真实操:从FR4到高速板材,你的5英寸走线在10GHz下“掉血”多少?
  • 如何用10MB的G-Helper替代臃肿的华硕奥创中心:终极轻量控制指南
  • 智慧树刷课插件:5分钟实现课程自动化学习的高效解决方案
  • HALCON图像处理进阶:从均值滤波到冲击滤波,如何为你的二维码识别选择最佳‘美颜’算子?
  • 基于PLC的自动洗车机控制系统设计(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)_文章底部可以扫码
  • 遗传算法调参实战:如何让你的流水车间调度(FSP)求解又快又准?
  • NVIDIA Profile Inspector终极显卡调优指南:3步解决游戏卡顿与画面撕裂
  • 兰州金价高位震荡,市民卖金变现,上门回收各区报价流程详解 - 黄金上门回收
  • 安卓端摄像头实时推流到Java后台的完整监控源码(含Socket传输与JPEG帧处理)
  • 2026年4月AI应用下载量增速分层,豆包、ChatGPT等表现各不同!
  • PLC电梯控制系(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)_文章底部可以扫码