当前位置: 首页 > 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.gsyq.cn/news/1439763.html

相关文章:

  • 【鸿蒙原生应用开发--ArkUI--013】Exercise-tracker 运动记录应用开发教程
  • 中文BERT抽取式问答实战包:PyTorch版知乎数据训练全流程(含预处理、模型、脚本与预训练权重)
  • 山东皇固金属 - 博客万
  • 别再傻傻轮询了!用STM32F1的DMA双缓存接收不定长数据,CPU占用率直降90%
  • 微信小程序单击元素切换元素的显示和隐藏
  • 哈尔滨黄金回收市场现状与六家正规机构实操指南 - 专业黄金回收
  • 越过山丘:35+ Java程序员的破局与重生——从“青春饭”到“长青树”的职业跃迁指南
  • SI9000损耗仿真实操:从FR4到高速板材,你的5英寸走线在10GHz下“掉血”多少?
  • 北京老旧小区黄金变现难?足不出户上门回收成新趋势 - 黄金上门回收
  • 如何用10MB的G-Helper替代臃肿的华硕奥创中心:终极轻量控制指南
  • 智慧树刷课插件:5分钟实现课程自动化学习的高效解决方案
  • 遗传算法调参实战:如何让你的流水车间调度(FSP)求解又快又准?
  • AI时代下Java新兵的“诺曼底登陆”——2026届Java毕业生的全新职业规划
  • 组合计数 + 拓扑序计数问题
  • 护发精油功效对比测评:抚平毛躁哪家强? - 资讯快报
  • 260亿美元估值!Cognition AI融资背后,AI编程赛道机遇与挑战并存
  • 百度网盘下载加速终极指南:3种方法突破限速实现高速下载
  • 2026 乐清黄金回收|铂金钻石 K 金名表名包回收靠谱商家推荐 - 同城好物推荐官
  • 告别Docker Hub抽风:手把手教你用SSH给群晖NAS安装ddns-go动态域名
  • Dictionary的底层原理
  • 极限运动场施工为什么不能只看效果图? - 长华体育
  • 2026年5月邯郸靠谱黄金回收门店实测盘点:余生黄金回收984元/克领跑,全城6家口碑排行 - 余生黄金回收
  • 基于机器学习的智能电表用电异常检测与负荷预测系统实战
  • 吕梁 cppm 培训机构中供国培首选 - 中供国培
  • 2026.5.30 zsh题单
  • 智慧树学习助手:用自动化技术提升在线学习效率
  • 闲管家邀请码折扣码是什么 闲管家智能回复 - 李先生sir
  • Voclosporin伏环孢素作为钙调神经磷酸酶抑制剂治疗活动性狼疮肾炎的蛋白尿降低
  • 余生黄金回收综合实力登顶!2026年5月兰州黄金回收深度解析与服务阶梯指南 - 余生黄金回收
  • 从BibTeX到完美排版:我的Mendeley/Zotero自定义CSL格式踩坑全记录