MediaStore.Images.Media.insertImage 已弃用
MediaStore.Images.Media.insertImage deprecated
我曾经使用 MediaStore.Images.Media.insertImage
保存图像,但现在不推荐使用 insertImage
方法。 docs 说:
This method was deprecated in API level 29. inserting of images should
be performed using MediaColumns#IS_PENDING, which offers richer
control over lifecycle.
我不太明白,因为 MediaColumns.IS_PENDING
只是一个标志,我应该如何使用它?
我应该使用 ContentValues
吗?
SOLVED
@CommonsWare 建议的代码没有问题,除了如果你使用 targetSdkVersion 29
编程,你必须添加条件:
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis().toString())
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //this one
put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation)
put(MediaStore.MediaColumns.IS_PENDING, 1)
}
}
This method was deprecated in API level 29. inserting of images should
be performed using MediaColumns#IS_PENDING, which offers richer
control over the lifecycle.
要么是我太笨,看不懂文档,要么 Google 团队真的需要重构文档。
无论如何,张贴来自 links provided by CommonsWare and coroutineDispatcher
的完整答案
第 1 步:确定您处于哪个 API 级别
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) saveImageInQ(imageBitMap)
else saveImageInLegacy(imageBitMap)
第二步: 将图片保存为Q风格
//Make sure to call this function on a worker thread, else it will block main thread
fun saveImageInQ(bitmap: Bitmap):Uri {
val filename = "IMG_${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
val imageUri: Uri? = null
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
put(MediaStore.Video.Media.IS_PENDING, 1)
}
//use application context to get contentResolver
val contentResolver = application.contentResolver
contentResolver.also { resolver ->
imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
}
fos?.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 70, it) }
contentValues.clear()
contentValues.put(MediaStore.Video.Media.IS_PENDING, 0)
resolver.update(imageUri, contentValues, null, null)
return imageUri
}
第 3 步: 如果不在 Q 中,以旧式保存图像
//Make sure to call this function on a worker thread, else it will block main thread
fun saveTheImageLegacyStyle(bitmap:Bitmap){
val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
fos?.use {bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)}
}
这应该让你开始!
感谢您对 iCantC 的贡献
第二步:将图片保存为Q风格.
我 运行 遇到了 Android Studio 中内存使用的一些问题,我不得不打开 Sublime 来修复。要修复此错误:
e: java.lang.OutOfMemoryError: Java heap space
这是我使用的代码,因为我的用例是 PNG 图像,任何 bitmap.compress
小于 100 的值都可能没有用。
以前的版本在 API 30 上不起作用,所以我将 contentValues RELATIVE_PATH
更新为 DIRECTORY_DCIM
也 contentResolver.insert(EXTERNAL_CONTENT_URI, ...
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z", Locale.getDefault()
)
private val legacyOrQ: (Bitmap) -> Uri = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
saveImageInQ(it) else legacySave(it) }
private fun saveImageInQ(bitmap: Bitmap): Uri {
val filename = "${title}_of_${dateFormatter.format(Date())}.png"
val fos: OutputStream?
val contentValues = ContentValues().apply {
put(DISPLAY_NAME, filename)
put(MIME_TYPE, "image/png")
put(RELATIVE_PATH, DIRECTORY_DCIM)
put(IS_PENDING, 1)
}
//use application context to get contentResolver
val contentResolver = applicationContext.contentResolver
val uri = contentResolver.insert(EXTERNAL_CONTENT_URI, contentValues)
uri?.let { contentResolver.openOutputStream(it) }.also { fos = it }
fos?.use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
fos?.flush()
fos?.close()
contentValues.clear()
contentValues.put(IS_PENDING, 0)
uri?.let {
contentResolver.update(it, contentValues, null, null)
}
return uri!!
}
第 3 步:如果不在 Q 上,则以旧样式保存图像
private fun legacySave(bitmap: Bitmap): Uri {
val appContext = applicationContext
val filename = "${title}_of_${dateFormatter.format(Date())}.png"
val directory = getExternalStoragePublicDirectory(DIRECTORY_PICTURES)
val file = File(directory, filename)
val outStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream)
outStream.flush()
outStream.close()
MediaScannerConnection.scanFile(appContext, arrayOf(file.absolutePath),
null, null)
return FileProvider.getUriForFile(appContext, "${appContext.packageName}.provider",
file)
}
第 4 步:创建自定义 FileProvider
package com.example.background.workers.provider
import androidx.core.content.FileProvider
class WorkerFileProvider : FileProvider() {
}
第 5 步:更新您的 AndroidManifest.xml
更改自
<activity android:name=".MyActivity" />
至
<activity android:name=".MyActivity">
<intent-filter>
<action android:name="android.intent.action.PICK"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.OPENABLE"/>
<data android:mimeType="image/png"/>
</intent-filter>
</activity>
<provider
android:name=".workers.provider.WorkerFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
</intent-filter>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
第 6 步:在 xml 下为 FILE_PROVIDER_PATHS 添加资源
就我而言,我需要图片文件夹
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="pictures" path="Pictures"/>
</paths>
我正在添加第二个答案,因为我不确定是否有人关心版本检查,但如果您执行他们的更多步骤,嗯...
从
开始
第 5 步:更新您的 AndroidManifest.xml
变化自
<activity android:name=".MyActivity" />
到
<activity android:name=".legacy.LegacyMyActivity"/>
<activity android:name=".MyActivity" />
<provider
android:name=".workers.provider.WorkerFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
android:enabled="@bool/atMostKitkat"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
</intent-filter>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity-alias android:name=".legacy.LegacyMyActivity"
android:targetActivity=".MyActivity"
android:enabled="@bool/atMostJellyBeanMR2">
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.OPENABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/png" />
</intent-filter>
</activity-alias>
第 6 步:在 xml 下添加资源 FILE_PROVIDER_PATHS 与我之前的回答相同
第 7 步:在 res/values 下为 bool.xml
添加资源
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="atMostJellyBeanMR2">true</bool>
<bool name="atMostKitkat">false</bool>
</resources>
第8步:还有res/values-v19
下的另一个
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="atMostJellyBeanMR2">false</bool>
<bool name="atMostKitkat">true</bool>
</resources>
第9步:最后如果需要查看保存的文件
所以重要的变化是 actionView.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
binding.seeFileButton.setOnClickListener {
viewModel.outputUri?.let { currentUri ->
val actionView = Intent(Intent.ACTION_VIEW, currentUri)
actionView.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
actionView.resolveActivity(packageManager)?.run {
startActivity(actionView)
}
}
}
我曾经使用 MediaStore.Images.Media.insertImage
保存图像,但现在不推荐使用 insertImage
方法。 docs 说:
This method was deprecated in API level 29. inserting of images should be performed using MediaColumns#IS_PENDING, which offers richer control over lifecycle.
我不太明白,因为 MediaColumns.IS_PENDING
只是一个标志,我应该如何使用它?
我应该使用 ContentValues
吗?
SOLVED
@CommonsWare 建议的代码没有问题,除了如果你使用 targetSdkVersion 29
编程,你必须添加条件:
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis().toString())
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //this one
put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation)
put(MediaStore.MediaColumns.IS_PENDING, 1)
}
}
This method was deprecated in API level 29. inserting of images should be performed using MediaColumns#IS_PENDING, which offers richer control over the lifecycle.
要么是我太笨,看不懂文档,要么 Google 团队真的需要重构文档。
无论如何,张贴来自 links provided by CommonsWare and coroutineDispatcher
的完整答案第 1 步:确定您处于哪个 API 级别
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) saveImageInQ(imageBitMap)
else saveImageInLegacy(imageBitMap)
第二步: 将图片保存为Q风格
//Make sure to call this function on a worker thread, else it will block main thread
fun saveImageInQ(bitmap: Bitmap):Uri {
val filename = "IMG_${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
val imageUri: Uri? = null
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
put(MediaStore.Video.Media.IS_PENDING, 1)
}
//use application context to get contentResolver
val contentResolver = application.contentResolver
contentResolver.also { resolver ->
imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
}
fos?.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 70, it) }
contentValues.clear()
contentValues.put(MediaStore.Video.Media.IS_PENDING, 0)
resolver.update(imageUri, contentValues, null, null)
return imageUri
}
第 3 步: 如果不在 Q 中,以旧式保存图像
//Make sure to call this function on a worker thread, else it will block main thread
fun saveTheImageLegacyStyle(bitmap:Bitmap){
val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
fos?.use {bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)}
}
这应该让你开始!
感谢您对 iCantC 的贡献 第二步:将图片保存为Q风格.
我 运行 遇到了 Android Studio 中内存使用的一些问题,我不得不打开 Sublime 来修复。要修复此错误:
e: java.lang.OutOfMemoryError: Java heap space
这是我使用的代码,因为我的用例是 PNG 图像,任何 bitmap.compress
小于 100 的值都可能没有用。
以前的版本在 API 30 上不起作用,所以我将 contentValues RELATIVE_PATH
更新为 DIRECTORY_DCIM
也 contentResolver.insert(EXTERNAL_CONTENT_URI, ...
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z", Locale.getDefault()
)
private val legacyOrQ: (Bitmap) -> Uri = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
saveImageInQ(it) else legacySave(it) }
private fun saveImageInQ(bitmap: Bitmap): Uri {
val filename = "${title}_of_${dateFormatter.format(Date())}.png"
val fos: OutputStream?
val contentValues = ContentValues().apply {
put(DISPLAY_NAME, filename)
put(MIME_TYPE, "image/png")
put(RELATIVE_PATH, DIRECTORY_DCIM)
put(IS_PENDING, 1)
}
//use application context to get contentResolver
val contentResolver = applicationContext.contentResolver
val uri = contentResolver.insert(EXTERNAL_CONTENT_URI, contentValues)
uri?.let { contentResolver.openOutputStream(it) }.also { fos = it }
fos?.use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
fos?.flush()
fos?.close()
contentValues.clear()
contentValues.put(IS_PENDING, 0)
uri?.let {
contentResolver.update(it, contentValues, null, null)
}
return uri!!
}
第 3 步:如果不在 Q 上,则以旧样式保存图像
private fun legacySave(bitmap: Bitmap): Uri {
val appContext = applicationContext
val filename = "${title}_of_${dateFormatter.format(Date())}.png"
val directory = getExternalStoragePublicDirectory(DIRECTORY_PICTURES)
val file = File(directory, filename)
val outStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream)
outStream.flush()
outStream.close()
MediaScannerConnection.scanFile(appContext, arrayOf(file.absolutePath),
null, null)
return FileProvider.getUriForFile(appContext, "${appContext.packageName}.provider",
file)
}
第 4 步:创建自定义 FileProvider
package com.example.background.workers.provider
import androidx.core.content.FileProvider
class WorkerFileProvider : FileProvider() {
}
第 5 步:更新您的 AndroidManifest.xml
更改自
<activity android:name=".MyActivity" />
至
<activity android:name=".MyActivity">
<intent-filter>
<action android:name="android.intent.action.PICK"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.OPENABLE"/>
<data android:mimeType="image/png"/>
</intent-filter>
</activity>
<provider
android:name=".workers.provider.WorkerFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
</intent-filter>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
第 6 步:在 xml 下为 FILE_PROVIDER_PATHS 添加资源 就我而言,我需要图片文件夹
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="pictures" path="Pictures"/>
</paths>
我正在添加第二个答案,因为我不确定是否有人关心版本检查,但如果您执行他们的更多步骤,嗯... 从
开始第 5 步:更新您的 AndroidManifest.xml
变化自
<activity android:name=".MyActivity" />
到
<activity android:name=".legacy.LegacyMyActivity"/>
<activity android:name=".MyActivity" />
<provider
android:name=".workers.provider.WorkerFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
android:enabled="@bool/atMostKitkat"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
</intent-filter>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity-alias android:name=".legacy.LegacyMyActivity"
android:targetActivity=".MyActivity"
android:enabled="@bool/atMostJellyBeanMR2">
<intent-filter>
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.OPENABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/png" />
</intent-filter>
</activity-alias>
第 6 步:在 xml 下添加资源 FILE_PROVIDER_PATHS 与我之前的回答相同
第 7 步:在 res/values 下为 bool.xml
添加资源<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="atMostJellyBeanMR2">true</bool>
<bool name="atMostKitkat">false</bool>
</resources>
第8步:还有res/values-v19
下的另一个<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="atMostJellyBeanMR2">false</bool>
<bool name="atMostKitkat">true</bool>
</resources>
第9步:最后如果需要查看保存的文件
所以重要的变化是 actionView.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
binding.seeFileButton.setOnClickListener {
viewModel.outputUri?.let { currentUri ->
val actionView = Intent(Intent.ACTION_VIEW, currentUri)
actionView.addFlags(FLAG_GRANT_READ_URI_PERMISSION)
actionView.resolveActivity(packageManager)?.run {
startActivity(actionView)
}
}
}