安卓拍照、裁切、选取图片实践

前言

最近项目里面有用到裁切功能,没弄多复杂,就是系统自带的,顺便就总结了一下系统拍照、裁切、选取的使用。网上的资料说实话真是没什么营养,但是Android官网上的说明也有点太简单了,真就要实践出真理。

拍照

本来拍照是没什么难度的,不就是调用intent去系统相机拍照么,但是由于文件权限问题,Uri这东西就能把人很头疼。下面是代码(onActivityResult见后文):

    private fun openCamera() {val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)// 应用外部私有目录:files-Picturesval picFile = createFile("Camera")val photoUri = getUriForFile(picFile)// 保存路径,不要uri,读取bitmap时麻烦picturePath = picFile.absolutePath// 给目标应用一个临时授权intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)//android11以后强制分区存储,外部资源无法访问,所以添加一个输出保存位置,然后取值操作intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)startActivityForResult(intent, REQUEST_CAMERA_CODE)}private fun createFile(type: String): File {// 在相册创建一个临时文件val picFile = File(requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),"${type}_${System.currentTimeMillis()}.jpg")try {if (picFile.exists()) {picFile.delete()}picFile.createNewFile()} catch (e: IOException) {e.printStackTrace()}// 临时文件,后面会加long型随机数
//        return File.createTempFile(
//            type,
//            ".jpg",
//            requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
//        )return picFile}private fun getUriForFile(file: File): Uri {// 转换为urireturn if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//适配Android 7.0文件权限,通过FileProvider创建一个content类型的UriFileProvider.getUriForFile(requireActivity(),"com.xxx.xxx.fileProvider", file)} else {Uri.fromFile(file)}}

简单说明

这里的file是使用getExternalFilesDir和Environment.DIRECTORY_PICTURES生成的,它的文件保存在应用外部私有目录:files-Pictures里面。这里要注意不能存放在内部的私有目录里面,不然是无法访问的,外部私有目录虽然也是私有的,但是外面是可以访问的,这里拿官网上的说明:

在搭载 Android 9(API 级别 28)或更低版本的设备上,只要您的应用具有适当的存储权限,就可以访问属于其他应用的应用专用文件。为了让用户更好地管理自己的文件并减少混乱,以 Android 10(API 级别 29)及更高版本为目标平台的应用在默认情况下被授予了对外部存储空间的分区访问权限(即分区存储)。启用分区存储后,应用将无法访问属于其他应用的应用专属目录。

Uri的获取

再一个比较麻烦的就是Uri的获取了,网上有一大堆资料,不过我这也贴一下,网上的可能有问题。

manifest.xml

        <providerandroid:name="androidx.core.content.FileProvider"android:authorities="com.xxx.xxx.fileProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths"/></provider>

res -> xml -> file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><!--1、对应内部内存卡根目录:Context.getFileDir()--><files-pathname="int_root"path="/" /><!--2、对应应用默认缓存根目录:Context.getCacheDir()--><cache-pathname="app_cache"path="/" /><!--3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()--><external-pathname="ext_root"path="/" /><!--4、对应外部内存卡根目录下的APP公共目录:Context.getExternalFileDir(String)--><external-files-pathname="ext_pub"path="/" /><!--5、对应外部内存卡根目录下的APP缓存目录:Context.getExternalCacheDir()--><external-cache-pathname="ext_cache"path="/" />
</paths>

ps. 注意authorities这个最好填自己的包名,不然有两个应用用了同样的authorities,后面的应用会安装不上。

打开相册

这里打开相册用的是SAF框架,使用intent去选取(onActivityResult见后文)。

    private fun openAlbum() {val intent = Intent()intent.type = "image/*"intent.action = "android.intent.action.GET_CONTENT"intent.addCategory("android.intent.category.OPENABLE")startActivityForResult(intent, REQUEST_ALBUM_CODE)}

裁切

裁切这里比较麻烦,参数比较多,而且Uri那里有坑,不能使用provider,再一个就是图片传递那因为安卓版本变更,不会传略缩图了,很坑。

    private fun cropImage(path: String) {cropImage(getUriForFile(File(path)))}private fun cropImage(uri: Uri) {val intent = Intent("com.android.camera.action.CROP")// Android 7.0需要临时添加读取Url的权限intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
//        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)intent.setDataAndType(uri, "image/*")// 使图片处于可裁剪状态intent.putExtra("crop", "true")// 裁剪框的比例(根据需要显示的图片比例进行设置)
//        if (Build.MANUFACTURER.contains("HUAWEI")) {//            //硬件厂商为华为的,默认是圆形裁剪框,这里让它无法成圆形
//            intent.putExtra("aspectX", 9999)
//            intent.putExtra("aspectY", 9998)
//        } else {//            //其他手机一般默认为方形
//            intent.putExtra("aspectX", 1)
//            intent.putExtra("aspectY", 1)
//        }// 设置裁剪区域的形状,默认为矩形,也可设置为圆形,可能无效// intent.putExtra("circleCrop", true);// 让裁剪框支持缩放intent.putExtra("scale", true)// 属性控制裁剪完毕,保存的图片的大小格式。太大会OOM(return-data)
//        intent.putExtra("outputX", 400)
//        intent.putExtra("outputY", 400)// 生成临时文件val cropFile = createFile("Crop")// 裁切图片时不能使用provider的uri,否则无法保存
//        val cropUri = getUriForFile(cropFile)val cropUri = Uri.fromFile(cropFile)intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri)// 记录临时位置cropPicPath = cropFile.absolutePath// 设置图片的输出格式intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())// return-data=true传递的为缩略图,小米手机默认传递大图, Android 11以上设置为true会闪退intent.putExtra("return-data", false)startActivityForResult(intent, REQUEST_CROP_CODE)}

回调处理

下面是对上面三个操作的回调处理,一开始我觉得uri没什么用,还制造麻烦,后面发现可以通过流打开uri,再去获取bitmap,好像又不是那么麻烦了。

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (resultCode == RESULT_OK) {when(requestCode) {REQUEST_CAMERA_CODE -> {// 通知系统文件更新
//                    requireContext().sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
//                        Uri.fromFile(File(picturePath))))if (!enableCrop) {val bitmap = getBitmap(picturePath)bitmap?.let {// 显示图片binding.image.setImageBitmap(it)}}else {cropImage(picturePath)}}REQUEST_ALBUM_CODE -> {data?.data?.let { uri ->if (!enableCrop) {val bitmap = getBitmap("", uri)bitmap?.let {// 显示图片binding.image.setImageBitmap(it)}}else {cropImage(uri)}}}REQUEST_CROP_CODE -> {val bitmap = getBitmap(cropPicPath)bitmap?.let {// 显示图片binding.image.setImageBitmap(it)}}}}}private fun getBitmap(path: String, uri: Uri? = null): Bitmap? {var bitmap: Bitmap?val options = BitmapFactory.Options()// 先不读取,仅获取信息options.inJustDecodeBounds = trueif (uri == null) {BitmapFactory.decodeFile(path, options)}else {val input = requireContext().contentResolver.openInputStream(uri)BitmapFactory.decodeStream(input, null, options)}// 预获取信息,大图压缩后加载val width = options.outWidthval height = options.outHeightLog.d("TAG", "before compress: width = " +options.outWidth + ", height = " + options.outHeight)// 尺寸压缩var size = 1while (width / size >= MAX_WIDTH || height / size >= MAX_HEIGHT) {size *= 2}options.inSampleSize = sizeoptions.inJustDecodeBounds = falsebitmap = if (uri == null) {BitmapFactory.decodeFile(path, options)}else {val input = requireContext().contentResolver.openInputStream(uri)BitmapFactory.decodeStream(input, null, options)}Log.d("TAG", "after compress: width = " +options.outWidth + ", height = " + options.outHeight)// 质量压缩val baos = ByteArrayOutputStream()bitmap!!.compress(Bitmap.CompressFormat.JPEG, 80, baos)val bais = ByteArrayInputStream(baos.toByteArray())options.inSampleSize = 1bitmap = BitmapFactory.decodeStream(bais, null, options)return bitmap}

这里还做了一个图片的质量压缩和采样压缩,需要注意的是采样压缩的采样率只能是2的倍数,如果需要按任意比例采样,需要用到Matrix,不是很难,读者可以研究下。

权限问题

如果你发现你没有申请权限,那你的去申请一下相机权限;如果你发现你还申请了储存权限,那你可以试一下去掉储存权限,实际还是可以使用的,因为这里并没有用到外部储存,都是应用的私有储存内,具体关于储存的适配,可以看我转载的这几篇文章,我觉得写的非常好:

Android 存储基础

Android 10、11 存储完全适配(上)

Android 10、11 存储完全适配(下)

结语

以上代码都经过我这里实践了,确认了可用,可能写法不是最优,可以避免使用绝对路径,只使用Uri。至于请求码、布局什么的,读者自己改一下加一个就行,核心部分已经在这了。如果需要完整代码,可以看下篇文章末尾!

Android 不申请权限储存、删除相册图片

安卓拍照、裁切、选取图片实践相关推荐

  1. android 实现自动拍照,Android:调用系统相机实现拍照+裁切(兼容7.0以上系统)

    android.jpg 前言 在平时的工作中,我们会经常遇到这样的需求:比如更改用户头像,我们就需要打开相机或相册,选择照片后进行裁剪,最后上传给后台... 相信这样的功能对小伙伴们来说可能早已司空见 ...

  2. 做自己的安卓拍照应用,其实很简单

    看着自己手机上的自带的拍照APP,感觉人家做的好精美啊,所以就心血来潮的想要做一个自己的安卓拍照app.于是在网上找啊找啊找的终于做出了一款还算能用的拍照设备. (*^__^*) 嘻嘻-- 首先是一些 ...

  3. 使用Zxing实现拍照,选取图片识别二维码

    转载请注明出处 作者:AboutJoke ( http://blog.csdn.net/u013200308 ) 原文链接:http://blog.csdn.net/u013200308/articl ...

  4. 安卓拍照上传php服务器,Android拍照上传至PHP服务器并写入MySql数据库(下)

    Android实现 调用系统相机,拍照: Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); getFileUri(); inte ...

  5. android --拍照相册选取图片[兼容小米等其他手机]

    前几天做项目中选择图片的过程中遇到小米手机出现不能够选取把图片放入到view中,这里是因为小米采用的是树形结构,而其他手机普遍是图形结构,本质就是url结构有些不同,小米的是绝对路径 1.定义常量: ...

  6. 未root手机 ida动态调试安卓so文件—— 学习实践 《教我兄弟学Android逆向09 IDA动态破解登陆验证》

    参考: https://www.52pojie.cn/thread-742686-1-1.html https://www.pianshen.com/article/6759779793 本文基本为h ...

  7. 干货:在Flutter项目下安卓flavor打包配置实践

    ????????关注后回复 "进群" ,拉你进程序员交流群???????? 作者丨狐友技术团队 来源丨搜狐技术产品(ID:sohu-tech) 本文字数:3894 字 预计阅读时间 ...

  8. 干货: 在 Flutter 项目下安卓 flavor 打包配置实践 | 开发者说·DTalk

    本文原作者: 狐友技术团队,原文发布于搜狐技术产品:  https://mp.weixin.qq.com/s/uOMxhc8xnFPmi7j_Z5JqVg 1.前言 Flutter 是 Google ...

  9. java交叉编译安卓可用,Android开发实践:Android交叉编译工具链的使用

    前面2篇文章分别介绍了Android NDK编译的命令行参数,以及如何在任意目录使用Android.mk来编译本地c/c++代码,Andriod.mk和ndk-build只不过是Android官方提供 ...

最新文章

  1. Watson AI遭遇逆风 IBM否认停售:只是转移了重心
  2. [Hadoop][Zookeeper]Cluster + HA
  3. Java集合查找Map,java:使用hashmap或其他一些java集合创建查找...
  4. 【30集iCore3_ADP出厂源代码(ARM部分)讲解视频】30-10底层驱动之I2C
  5. 25个实用编程小技巧
  6. python 函数式编程包_python 函数支持函数式编程的包operator partial
  7. Zookeeper常用命令行及API
  8. Linux下的段错误产生的原因及调试方法-转
  9. ASP.NET MVC 实现页落网资源分享网站+充值管理+后台管理(15)之前台网站页面
  10. uva1673(后缀自动机)
  11. 输出保留3位小数的浮点数
  12. 夺旗赛 CTF 六大方向基础工具简介集合(MISC,WEB,Crypto,Reverse,Pwn,Mobile)
  13. ApowerREC v1.5.6.2 最佳屏幕录像机
  14. MATLAB:数值微积分
  15. OpenKG开源系列 | 中文高中地理知识图谱CKGG(南京大学)
  16. 2021年电子设计大赛预测--球形机器人设计方案
  17. 相关性扫描匹配CSM与分支限界
  18. 对比分析冯诺依曼结构和哈佛结构。
  19. matlab实现线性函数逼近,1基于MATLAB的科学计算—函数逼近1.doc
  20. codeforces--Build a Contest

热门文章

  1. 微信H5中禁止分享好友及分享到朋友圈的方法
  2. Day52_Flume
  3. mysql复合索引(联合索引)用法以及最左原则
  4. 时尚的3D镂空文字效果实例教程
  5. mysql ibdata损坏_mysql ibdata1损坏
  6. linux系统mkfs工具介绍
  7. 益高机器人_开面馆选择奥特曼削面机投资小受益高
  8. adb对mumu模拟器查看游戏的log
  9. 小学六年级 计算机演讲稿,小学六年级演讲稿大全
  10. 《看监守自盗——2008年金融危机》有感