随着Android系统的不断升级 从最初的第一个版本 更新到如今 Android 11.0 Beta
都出炉了 Android 11.0
也即将面试 系统的不断更新完善 用户体验也是蹭蹭蹭的 隐私安全方面也是愈来愈给力了 这对用户固然是一级棒 对于开发者 简直无力吐槽 由于碎片化问题 加上版本更新迭代 废弃淘汰一堆 API
脑瓜子疼。android
这几天刚刚须要保存视频到相册 发现 我去 之前的方法好像不太给力了,磨了 我很久。。由于我手机是Android 10
版本 从用户隐私增强了 最大的变化就是 存储权限web
Android 10
在外部存储设备中为每一个应用提供了一个“隔离存储沙盒”(例如 /sdcard
)。任何其余应用都没法直接访问您应用的沙盒文件。因为文件是您应用的私有文件,所以您再也不须要任何权限便可在外部存储设备中访问和保存本身的文件。此变动可以让您更轻松地保证用户文件的隐私性,并有助于减小应用所需的权限数量。面试
因此就是说咱们不能直接访问 根目录 了? 其实 在Android 10
上还不是彻底杜绝你使用的 你仍是能够兼容低版本 那怎么作呢安全
<application ... android:requestLegacyExternalStorage="true"> </application> 复制代码
只须要在 你的AndroidManifest.xml
文件中 加入这行代码 意思就是申请旧版本的外部存储 那么你仍是可与愉快玩耍的app
但是旧版本的外部存储权限都已经废弃了 这样作是能够解决当前的问题 可是在Android 11
上 讲严格执行沙盒存储方式 也就是说 这样的代码在Android 11
上已经没法兼容了 而且的 10 的系统兼容也不够编辑器
那么开始撸代码把ui
/** *保存bitmap */ fun saveBitmap2Gallery(context: Context, bitmap: Bitmap): Boolean { val name=System.currentTimeMillis().toString() val photoPath=Environment.DIRECTORY_DCIM + "/Camera" val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME,name ) put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") put(MediaStore.MediaColumns.RELATIVE_PATH, photoPath)//保存路径 put(MediaStore.MediaColumns.IS_PENDING, true) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //返回出一个URI val insert = context.contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) ?: return false //为空的话 直接失败返回了 //这个打开了输出流 直接保存图片就行了 context.contentResolver.openOutputStream(insert).use { it ?: return false bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) } return true } else { MediaStore.Images.Media.insertImage(context.contentResolver, bitmap, "title", "desc") return true } } 复制代码
就上面的代码 能够直接保存了 若是不挑剔 应该是可使用的了 可是发现一个问题 除了Android 10
以上的 用 MediaStore
提示是废弃的 蒙蔽 为何废弃呢 咱们进入文档看看url
inserting of images should be performed using {@link MediaColumns#IS_PENDING}, which offers richer control over lifecycle.
复制代码
一看文档一脸懵逼 没看懂什么 各类查阅资料 百度一堆都是废弃的API
。。。Android
这么难吗 保存个图片 都没有一个兼容的 完美的解决方案 或者 API
通过漫长的查询 种算看到眉目了 改良后的代码是这样的spa
fun saveBitmap2Gallery2(context: Context, bitmap: Bitmap): Boolean {
val name = System.currentTimeMillis().toString() val photoPath = Environment.DIRECTORY_DCIM + "/Camera" val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.MediaColumns.RELATIVE_PATH, photoPath)//保存路径 put(MediaStore.MediaColumns.IS_PENDING, true) } } val insert = context.contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) ?: return false //为空的话 直接失败返回了 //这个打开了输出流 直接保存图片就行了 context.contentResolver.openOutputStream(insert).use { it ?: return false bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { contentValues.put(MediaStore.MediaColumns.IS_PENDING, false) } return true } 复制代码
是简单了好多 发现 在Android 10
一下 只须要屏蔽 RELATIVE_PATH
和 IS_PENDING
就能够了code
可是发现一个问题 在Android 默认是保持在 /sdCard/Pictures/
虽然说也存放图片的地方 可是不是 /sdCard/DCIM/Camera
在小米 OV
等手机上 不能直接显示在照片里 而是在相册 中的 Pictures里 或者在所有照片也能够查到 这个问题我仍是不知道怎么解决 由于 在使用 ContentValues
时 10.0如下的系统是不能设置路径的 那怎么办 可能仍是的用废弃的 API
了 这里方法提供了 2种 自行选择最适合本身的 若是知道怎么保存到DCIM
欢迎评论区解答下
最后须要注意的是 在Android 10
中 保存到相册是不须要存储权限的 在6 - 9
的版本中 须要存储权限
整合放出代码
object PhotoUtils {
fun saveBitmap2Gallery(context: Context, bitmap: Bitmap): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //返回出一个URI val insert = context.contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, /* 这里若是不写的话 默认是保存在 /sdCard/DCIM/Pictures */ ContentValues()//这里能够啥也不设置 保存图片默认就行了 ) ?: return false //为空的话 直接失败返回了 //这个打开了输出流 直接保存图片就行了 context.contentResolver.openOutputStream(insert).use { it ?: return false bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) } return true } else { MediaStore.Images.Media.insertImage(context.contentResolver, bitmap, "title", "desc") return true } } fun saveFile2Gallery(context: Context, url: String): Boolean { if (true) { //返回出一个URI val insert = context.contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, /* 这里能够默认不写 默认保存在 */ ContentValues() ) ?: return false //为空的话 直接失败返回了 //这个打开了输出流 直接保存图片就行了 context.contentResolver.openOutputStream(insert).use { os -> os ?: return false var x = download(url, os) return x } return false } else { val externalFilesDir = context.getExternalFilesDir(Environment.DIRECTORY_DCIM) ?: return false var name = "${System.currentTimeMillis()}.jpg" val file = File(externalFilesDir, name) //下载文件到应用目录 download(url, file.outputStream()) MediaStore.Images.Media.insertImage( context.contentResolver, file.absolutePath, name, "desc" ) //刷新相册 context.sendBroadcast( Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(File(file.getPath())) ) ) return true } } fun saveFile2Gallery2(context: Context, url: String): Boolean { val name = System.currentTimeMillis().toString() val photoPath = Environment.DIRECTORY_DCIM + "/Camera" val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.MediaColumns.RELATIVE_PATH, photoPath)//保存路径 put(MediaStore.MediaColumns.IS_PENDING, true) } } //返回出一个URI val insert = context.contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) ?: return false //这个打开了输出流 直接保存图片就行了 context.contentResolver.openOutputStream(insert).use { os -> os ?: return false var x = download(url, os) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { contentValues.put(MediaStore.MediaColumns.IS_PENDING, false) } return x } return false } fun saveBitmap2Gallery2(context: Context, bitmap: Bitmap): Boolean { val name = System.currentTimeMillis().toString() val photoPath = Environment.DIRECTORY_DCIM + "/Camera" val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.MediaColumns.RELATIVE_PATH, photoPath)//保存路径 put(MediaStore.MediaColumns.IS_PENDING, true) } } val insert = context.contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) ?: return false //为空的话 直接失败返回了 //这个打开了输出流 直接保存图片就行了 context.contentResolver.openOutputStream(insert).use { it ?: return false bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { contentValues.put(MediaStore.MediaColumns.IS_PENDING, false) } return true } private fun download(url: String, os: OutputStream): Boolean { val url = URL(url) (url.openConnection() as HttpURLConnection).also { conn -> conn.requestMethod = "GET" conn.connectTimeout = 5 * 1000 if (conn.responseCode == 200) { conn.inputStream.use { ins -> val buf = ByteArray(2048) var len: Int while (ins.read(buf).also { len = it } != -1) { os.write(buf, 0, len) } os.flush() } return true } else { return false } } } } 复制代码