android 不建议使用MediaStore.Images.Media. insert图像

e1xvtsh3  于 2022-12-09  发布在  Android
关注(0)|答案(5)|浏览(542)

我曾经使用MediaStore.Images.Media.insertImage保存图像,但是insertImage方法现在被弃用了。
此方法在API级别29中已弃用。应使用MediaColumns#IS_PENDING执行图像插入,它可提供对生命周期更丰富的控制。
我真的不明白,既然MediaColumns.IS_PENDING只是一个标志,我应该如何使用它?
我应该使用ContentValues吗?

vxf3dgd4

vxf3dgd41#

已解决
@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)
            }
        }
r9f1avp5

r9f1avp52#

此方法在API级别29中已弃用。应使用MediaColumns#IS_PENDING执行图像插入,它可对生命周期提供更丰富的控制。
要么是我太笨了,看不懂这些文档,要么是Google团队真的需要重构文档。
无论如何,从CommonsWarecoroutineDispatcher提供的链接发布完整的答案

**步骤1:**确定您所处的API级别

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) saveImageInQ(imageBitMap)
    else saveImageInLegacy(imageBitMap)

步骤2: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)}
}

这应该能让你爽起来!

hyrbngr7

hyrbngr73#

Thanks for contributing iCantC on Step 2: Save the image in Q style.
I ran into some issues with memory usage in Android Studio, which I had to open Sublime to fix. To fix this error:

e: java.lang.OutOfMemoryError: Java heap space

This is the code I used as my use case is for PNG images, any value of bitmap.compress less than 100 is likely not useful. Previous version would not work on API 30 so I updated contentValues RELATIVE_PATH to DIRECTORY_DCIM also 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!!
    }

Step 3: If not on Q save the image in legacy style

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)
    }

Step 4: Create a custom FileProvider

package com.example.background.workers.provider

import androidx.core.content.FileProvider

class WorkerFileProvider : FileProvider() {

}

step 5: update your AndroidManifest.xml

changes from

<activity android:name=".MyActivity" />

to

<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>

step 6: add a resource under xml for FILE_PROVIDER_PATHS in my case I needed the pictures folder

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="pictures" path="Pictures"/>
</paths>
vaj7vani

vaj7vani4#

所有答案的总和,以及每个答案的重构版本。

如果您的应用已使用文件提供程序,则可以跳过对AndroidManifest.xmlfilepaths.xml的添加。

更新(2022年12月)

我不得不用applicationContext.getExternalFilesDir(DIRECTORY_PICTURES)替换getExternalStoragePublicDirectory(DIRECTORY_PICTURES),因为它在早期版本的Android中似乎会崩溃。请确保您在filepaths.xml中提供了根目录。

保存为PNG
/**
 * Saves a bitmap as a PNG file.
 *
 * Note that `.png` extension is added to the filename.
 */
fun Bitmap.saveAsPNG(filename: String) = "$filename.png".let { name ->
    if (SDK_INT < Q) {
        @Suppress("DEPRECATION")
        val file = File(applicationContext.getExternalFilesDir(DIRECTORY_PICTURES), name)
        FileOutputStream(file).use { compress(PNG, 100, it) }
        MediaScannerConnection.scanFile(applicationContext,
            arrayOf(file.absolutePath), null, null)
        FileProvider.getUriForFile(applicationContext,
            "${ applicationContext.packageName }.provider", file)
    } else {
        val values = ContentValues().apply {
            put(DISPLAY_NAME, name)
            put(MIME_TYPE, "image/png")
            put(RELATIVE_PATH, DIRECTORY_DCIM)
            put(IS_PENDING, 1)
        }

        val resolver = applicationContext.contentResolver
        val uri = resolver.insert(EXTERNAL_CONTENT_URI, values)
        uri?.let { resolver.openOutputStream(it) }
            ?.use { compress(PNG, 100, it) }

        values.clear()
        values.put(IS_PENDING, 0)
        uri?.also {
            resolver.update(it, values, null, null) }
    }
}
保存为JPG格式
/**
 * Saves a bitmap as a Jpeg file.
 *
 * Note that `.jpg` extension is added to the filename.
 */
fun Bitmap.saveAsJPG(filename: String) = "$filename.jpg".let { name ->
    if (SDK_INT < Q)
        @Suppress("DEPRECATION")
        FileOutputStream(File(applicationContext.getExternalFilesDir(DIRECTORY_PICTURES), name))
            .use { compress(JPEG, 100, it) }
    else {
        val values = ContentValues().apply {
            put(DISPLAY_NAME, name)
            put(MIME_TYPE, "image/jpg")
            put(RELATIVE_PATH, DIRECTORY_PICTURES)
            put(IS_PENDING, 1)
        }

        val resolver = applicationContext.contentResolver
        val uri = resolver.insert(EXTERNAL_CONTENT_URI, values)
        uri?.let { resolver.openOutputStream(it) }
            ?.use { compress(JPEG, 70, it) }

        values.clear()
        values.put(IS_PENDING, 0)
        uri?.also {
            resolver.update(it, values, null, null) }
    }
}

机器人清单. xml

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>
资源/文件路径. xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path
        name="whatever"
        path="/" />
</paths>
导入(以防遗漏)
import android.content.ContentValues
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat.JPEG
import android.graphics.Bitmap.CompressFormat.PNG
import android.media.MediaScannerConnection
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.Q
import android.os.Environment.DIRECTORY_DCIM
import android.os.Environment.DIRECTORY_PICTURES
import android.os.Environment.getExternalStoragePublicDirectory
import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
import android.provider.MediaStore.MediaColumns.DISPLAY_NAME
import android.provider.MediaStore.MediaColumns.MIME_TYPE
import android.provider.MediaStore.MediaColumns.RELATIVE_PATH
import android.provider.MediaStore.Video.Media.IS_PENDING
import androidx.core.content.FileProvider
import java.io.File
import java.io.FileOutputStream
nzrxty8p

nzrxty8p5#

我添加了第二个答案,因为我不确定是否有人关心版本检查,但如果你这样做,他们是更多的步骤,嗯...从

步骤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)
             }
        }
   }

相关问题