Android 通过相机和系统相册获取图片,压缩,结果回调

一、需求背景

在常规的App开发中,很多时候需要用户上传图片来进行一些业务上的实现,例如用户反馈,图片凭证等。

二、实现功能

1.选择弹窗(即选择拍照或者相册)

2.申请权限(相机权限)

3.相机拍照回调,图片处理

4.相册选择回调,图片处理

5.图片压缩,上传服务器

三、实现步骤

1.选择弹窗

1.1 弹窗选择

Kotlin 复制代码
  PhotoSelectDialog.Builder(this)
            .setCallClickListener(object : PhotoSelectDialog.PhotoSelectListener {
                override fun clickPhoto(dialog: Dialog) {
                    toCheckPermission()
                    dialog.dismiss()
                }

                override fun clickAlbum(dialog: Dialog) {
                    dispatchChoosePictureIntent()
                    dialog.dismiss()
                }

            }).create().show()

1.2 弹窗的代码

Kotlin 复制代码
class PhotoSelectDialog(context: Context, themeStyle: Int) : Dialog(context, themeStyle) {
    init {
        initView()
    }

    private fun initView() {
        setContentView(R.layout.iamge_select_dialog)
    }

    class Builder(private val context: Context) {
        private var photoSelectListener: PhotoSelectListener? = null

        fun setCallClickListener(photoSelectListener: PhotoSelectListener): Builder {
            this.photoSelectListener = photoSelectListener
            return this
        }

        fun create(): PhotoSelectDialog {
            val dialog = PhotoSelectDialog(context, R.style.CustomDialogStyle)
            dialog.setCancelable(true)
            dialog.setCanceledOnTouchOutside(true)
            if (photoSelectListener != null) {
                dialog.findViewById<AppCompatImageView>(R.id.toSelectImage).setOnClickListener {
                    photoSelectListener?.clickAlbum(dialog)
                }
                dialog.findViewById<AppCompatImageView>(R.id.toTakePhoto).setOnClickListener {
                    photoSelectListener?.clickPhoto(dialog)
                }
                dialog.findViewById<AppCompatImageView>(R.id.dialog_close).setOnClickListener {
                    dialog.dismiss()
                }
            }
            return dialog
        }

    }

    interface PhotoSelectListener {
        fun clickPhoto(dialog: Dialog)
        fun clickAlbum(dialog: Dialog)
    }
}

1.3 弹窗的展示

2.申请相机权限

众所周知,像调用相机是必须要有权限。

2.1在manifest进行声明

XML 复制代码
<uses-feature
        android:name="android.hardware.camera"
        android:required="false" />

<uses-permission android:name="android.permission.CAMERA" />

2.2 申请权限

我使用的是EasyPermission框架,也可以自己写。

Kotlin 复制代码
  private fun toCheckPermission() {
        if (!EasyPermissions.hasPermissions(  this,android.Manifest.permission.CAMERA,) ) {
            EasyPermissions.requestPermissions(
                this,
                getString(R.string.permission_tips),
                AppConstant.PER_CAMERA,
                android.Manifest.permission.CAMERA,
            )
        } else {
            dispatchTakePictureIntent()//打开相机
        }
    }

相关权限回调处理,可以去看我的另一篇博客

Android Permission 权限申请,EasyPermission和其他三方库-CSDN博客

3.相机拍照回调,图片处理

3.1 调起相机

Kotlin 复制代码
private val REQUEST_IMAGE_CAPTURE = 1//请求码

// 启动相机并捕获照片的函数
private fun dispatchTakePictureIntent() {
    // 创建一个用于调用系统相机的 Intent
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        // 检查是否有相机应用能够处理该 Intent
        takePictureIntent.resolveActivity(packageManager)?.also {
            // 尝试创建用于存储照片的文件
            val photoFile: File? = try {
                createImageFile() // 创建图片文件
            } catch (ex: IOException) {
                null // 处理文件创建过程中可能出现的异常
            }

            // 如果创建成功,继续执行
            photoFile?.also {
                // 获取文件的 URI,使用 FileProvider 来确保文件访问权限正确
                photoURI = FileProvider.getUriForFile(this, "com.uz.cashloanuzi.fileprovider", it)

                // 将照片文件的 URI 传递给相机应用,确保照片被保存到正确的位置
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)

                // 启动相机应用并等待结果,结果会回调到 onActivityResult
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
}

// 创建用于保存图片的文件
@Throws(IOException::class)
private fun createImageFile(): File {
    // 生成一个带有时间戳的文件名
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
    
    // 获取用于存储图片的目录路径,使用应用专属的外部存储
    val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)

    // 创建临时文件,文件名以 "JPEG_时间戳_" 开头,扩展名为 .jpg
    return File.createTempFile(
        "JPEG_${timeStamp}_", /* 文件前缀 */
        ".jpg", /* 文件后缀 */
        storageDir /* 存储目录 */
    ).apply {
        // 保存文件的绝对路径,供其他用途使用(这儿最开始我在用,后面没用也没注释,可以不管)
        currentPhotoPath = absolutePath
    }
}

流程请看代码注释,其中的fileprovider,需要自己在manifest中声明

Kotlin 复制代码
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.uz.cashloanuzi.fileprovider" <!-- 指定与代码中的 `FileProvider.getUriForFile` 相同的 authorities -->
    android:exported="false" <!-- 防止其他应用直接访问你的 FileProvider,增加安全性 -->
    android:grantUriPermissions="true"> <!-- 允许你临时授予其他应用对文件的访问权限 -->
    
    <!-- FileProvider 的路径配置 -->
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS" <!-- 配置文件的路径信息 -->
        android:resource="@xml/file_paths" /> <!-- 引用 XML 文件,定义哪些目录可以被共享 -->
</provider>

file_paths文件

Kotlin 复制代码
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 共享外部存储图片目录 -->
    <external-files-path
        name="my_images"
        path="Pictures/" />
</paths>

tips:@xml/file_paths 指的是一个 XML 文件,你需要在 res/xml 目录下创建这个文件,告诉 FileProvider 你允许分享的文件路径。通常这个文件包含一个 <paths> 标签,并定义了可共享的目录。

3.2 图片回调处理

Kotlin 复制代码
@SuppressLint("SuspiciousIndentation")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                REQUEST_IMAGE_CAPTURE -> {//拍照
                    val bitmap: Bitmap =
                        MediaStore.Images.Media.getBitmap(this.contentResolver, photoURI)
                    val file = createFileFromBitmap(bitmap)//创建一个文件,且压缩
                    val fileSizeInKB = file.length().div(1024)
                    val fileSizeInMB = fileSizeInKB / 1024
                    LogUtils.e("file Size", fileSizeInMB.toString())
                    uploadImage(file) { isSus, result ->//接口上传
                        if (isSus) {
                            toShowPic(bitmap)//展示
                        } else {
                            ToastUtils.makeText(getString(R.string.upload_failed))
                        }
                    }


                }

                REQUEST_IMAGE_PICK -> {//相册
                    data?.data?.let { uri ->
                        val bitmap: Bitmap =
                            MediaStore.Images.Media.getBitmap(this.contentResolver, uri)
                        val file = createFileFromBitmap(bitmap)//创建一个文件,且压缩
                        uploadImage(file) { isSus, result ->//接口上传
                            if (isSus) {
                                toShowPic(bitmap)//展示到UI上
                            } else {
                                ToastUtils.makeText(getString(R.string.upload_failed))
                            }
                        }

                    }
                }
            }
        } else if (resultCode == Activity.RESULT_CANCELED) {
            if (requestCode == REQUEST_IMAGE_CAPTURE) {
                 ToastUtils.makeText("拍照取消"))
            } else if (requestCode == REQUEST_IMAGE_PICK) {                                                 
             ToastUtils.makeText("相册获取图片取消")
            }
        }

    }

4.相册选择回调,图片处理

4.1 打开相册

Kotlin 复制代码
private val REQUEST_IMAGE_PICK = 2
private fun dispatchChoosePictureIntent() {//ACTION_PICK 相册选择
        Intent(
            Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        ).also { choosePictureIntent ->
            choosePictureIntent.type = "image/*"
            startActivityForResult(choosePictureIntent, REQUEST_IMAGE_PICK)
        }
    }

4.2 相册回调

即上面onActivityResult里的方法

5.图片压缩,上传服务器

5.1 图片压缩(如果后端需要对图片有要求,得压缩)

Kotlin 复制代码
// 将 Bitmap 转换为文件并进行压缩,保证文件大小不超过 2MB
private fun createFileFromBitmap(bitmap: Bitmap): File {
    // 使用当前时间戳生成唯一的文件名
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
    
    // 获取应用的外部图片存储目录
    val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    
    // 创建文件对象,文件名为 JPEG_加上时间戳,并且以 .jpg 结尾
    val file = File(storageDir, "JPEG_${timeStamp}_.jpg")
    
    // 创建一个文件输出流,准备将字节数组写入文件
    val fos = FileOutputStream(file)
    
    // 初始化图片的质量为 100(最高质量)
    var quality = 100
    
    // 将 Bitmap 转换为字节数组,并指定初始的质量
    var byteArray = convertBitmapToByteArray(bitmap, quality)
    
    // 输出初始的图片大小
    LogUtils.e("image size", byteArray.size.toString())
    
    // 如果图片大小超过 2MB,并且质量大于 10,就继续压缩
    while (byteArray.size > 2 * 1024 * 1024 && quality > 10) {
        quality -= 10  // 每次减少 10% 的质量
        byteArray = convertBitmapToByteArray(bitmap, quality)  // 重新生成字节数组
    }
    
    // 输出压缩后的图片大小
    LogUtils.e("deal image size", byteArray.size.toString())
    
    // 将最终的字节数组写入文件
    fos.write(byteArray)
    
    // 刷新并关闭文件输出流,确保数据写入完成
    fos.flush()
    fos.close()
    
    // 返回创建的文件对象
    return file
}

5.2 图片上传

我的Retrofit请求自己又封了一下,这儿就不粘贴。传文件和普通接口会有些不同,注意一下就好了

相关推荐
众拾达人1 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌2 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
_Shirley3 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei5 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng5 小时前
安卓多渠道apk配置不同签名
android
枫_feng5 小时前
AOSP开发环境配置
android·安卓
叶羽西6 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio
qq_171538857 小时前
利用Spring Cloud Gateway Predicate优化微服务路由策略
android·javascript·微服务
Vincent(朱志强)9 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
mmsx9 小时前
android 登录界面编写
android·登录界面