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请求自己又封了一下,这儿就不粘贴。传文件和普通接口会有些不同,注意一下就好了

相关推荐
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_5 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存7 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子7 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑9 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch11 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
pixle013 小时前
Three.js 快速入门教程【二】透视投影相机
开发语言·javascript·数码相机
人民的石头15 小时前
Android系统开发 给system/app传包报错
android
yujunlong391915 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup