将图片保存到Android目标API 29的共享图片文件夹

5
我的目标是从相机应用程序请求图片,将其保存在我的应用数据中,并将相同的图片写入图库,以便如果用户删除应用程序,则仍具有拍摄的照片(与Messenger不同,我们的实用工具照片即使没有应用程序也被认为对用户非常有价值)。
我面临的问题与29个API级别紧密相关,这是构建目标(提供降级选项不可接受)。
所以基本上我遇到的问题是:
第一个问题:即使授予了“Manifest.permission.WRITE_EXTERNAL_STORAGE”权限,我也无法访问使用“Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)”构造的文件,我会收到IOException(权限被拒绝)。
第二个问题:预先Q设备(Nokia 8 Android Pie)会在contentResolver.insert(contentUri,contentValues)上抛出异常。
java.lang.SecurityException: Permission Denial: writing com.android.providers.media.MediaProvider uri content://media/external/images/media from .... requires android.permission.WRITE_EXTERNAL_STORAGE, or grantUriPermission()

如果我在Android 10设备上运行(在我的情况下是模拟器),使用contentResolver.insert(..)选项可以正常工作。
主要问题是如何正确地将图片写入Android托管的图库文件夹,以便在面向29Api级别的情况下可以在Android 10之前的设备上运行?
额外参考:
我从我的应用程序缓存存储中将图片写入Android 10图库的代码是:
    fun writePictureToGalleryQ(context: Context, pictureUri: Uri, pictureName: String) {

        val contentResolver = context.contentResolver

        val relativeLocation = "${Environment.DIRECTORY_PICTURES}${File.separator}WaiJuDuDisAndroid"

        val contentValues  = ContentValues().apply {
            put(MediaStore.Images.ImageColumns.DISPLAY_NAME, pictureName)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            put(MediaStore.Images.ImageColumns.RELATIVE_PATH, relativeLocation)
        }

        val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        var uri: Uri? = null

        uri = contentResolver.insert(contentUri, contentValues)
        uri?.let { galleryPartUri ->

            val outStream: OutputStream = contentResolver.openOutputStream(galleryPartUri)!!
            val inStream: InputStream = contentResolver.openInputStream(pictureUri)!!

            outStream.use {out ->
                inStream.use { inpt ->
                    inpt.copyTo(out)
                }
            }
        }
    }

附注:内容提供者在清单文件中设置,需要的权限和文件路径在xml中设置。

以下代码适用于每个Q版本的Android,但是它似乎并不起作用(假设我已经获得了Manifest.permission.WRITE_EXTERNAL_STORAGE)。

    fun writePictureToGalleryLegacy(context: Context, pictureUri: Uri, pictureName: String) {


        val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)

        val correctDir = File("${directory.absolutePath}${File.separator}WaiJuDuDisAndroid")
        correctDir.mkdirs()

        val file = File("${correctDir.absolutePath}${File.separator}${pictureUri.lastPathSegment}")

        val contentResolver = context.contentResolver

        val outStream: OutputStream = file.outputStream()
        val inStream: InputStream = contentResolver.openInputStream(pictureUri)!!

        outStream.use {out ->
            inStream.use { inpt ->
                inpt.copyTo(out)
            }
        }


        val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension)

        MediaScannerConnection.scanFile(
                context,
                arrayOf(file.absolutePath),
                arrayOf(mimeType?:"image/*"),
                null
        )

    }

问题和编辑更新

我调整了我的代码块,使它们更加正确,因为它们实际上是有效的。

重新构建项目最终解决了我的问题。不确定我更讨厌谷歌还是我自己。

当然,重新构建项目只是解决方案的一部分。

我有两个问题:

  1. 一个人在这里提出的问题是,我可能缺少 <application... android:requestLegacyExternalStorage="true" ....>
  2. 我的某些依赖库中的一个清单文件中有 android:maxSdkVersion,这基本上会导致在 Android 10 和 Android 9 设备上忽略我的权限,可能会影响从 Android 4.4 开始的所有设备。

因此,我相应地更改了我的应用程序清单:

<manifest>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="replace"/>

    <application android:requestLegacyExternalStorage="true" ...>
        ...
    </application>

</manifest>

2
https://proandroiddev.com/working-with-scoped-storage-8a7e7cafea3 - blackapps
对于其余部分,不清楚您是与 Q 有问题还是在 Q 之前有问题。 - blackapps
您可以使用具有正确权限的路径 Q。对于下面的 Q,如果您想要使用 MediaStore 插入文件或者不使用,这是不清楚的。为什么呢? - blackapps
在下面的代码中,我试图将图片直接写入文件“/storage/emulated/0/Pictures/PIC_1577795479840.jpeg”,但是我得到了一个带有“permission denied”注释的异常。我的目标是尽可能以任何方式将图片保存到Android图片目录或DCIM。 - Alpha
@blackapps 一切都运作正常。遗留项和覆盖清单的库的组合,加之重建并卸载 / 安装应用程序是必要的,以使这些更改生效...现在不确定我更生气谁,是Google还是我自己。 - Alpha
显示剩余3条评论
1个回答

0

我曾经遇到过同样的问题,现在我将为 Cordova 应用程序扩展解决方案。最终对我有用的是在 config.xml 中添加以下行:

<platform name="android">
   <custom-preference name="android-manifest/application/@android:requestLegacyExternalStorage" value="true" />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接