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

相关推荐
Python私教13 分钟前
JavaScript 基于生成器的异步编程方案相关代码分享
android·javascript·okhttp
文 丰24 分钟前
【Android Studio】app:compileDebugJavaWithJavac FAILED解决办法
android·ide·android studio
寰宇软件1 小时前
Android横竖屏 mdpi hdpi xhdpi xxhdpi xxxhdpi
android
文 丰1 小时前
【Android Studio】2024.1.1最新版本AS调试老项目(老版AS项目文件、旧gradle)导入其他人的项目
android·ide·android studio
Yongqiang Cheng2 小时前
在线查看 Android 系统源代码 Android Code Search
android·在线查看·android 系统源代码·code search
CYRUS STUDIO2 小时前
LineageOS源码下载和编译(Xiaomi Mi 6X,wayne)
android·刷机·lineageos·android源码编译
竹等寒3 小时前
中间件常见漏洞
android·web安全·网络安全·中间件
zeruns8025 小时前
如何用安卓玩Java版Minecraft,安卓手机安装我的世界Java版游戏的教程
android·java·智能手机·minecraft·mc
我命由我123457 小时前
ADB 之 logcat 极简小抄(过滤日志、保存日志到文件)
android·运维·adb·android studio·安卓·运维开发·android-studio
不吃饭的猪7 小时前
mysql一主2从部署
android·mysql·adb