如何将图像保存在 android Q 的子目录中,同时保持向后兼容
Posted
技术标签:
【中文标题】如何将图像保存在 android Q 的子目录中,同时保持向后兼容【英文标题】:How to save an image in a subdirectory on android Q whilst remaining backwards compatible 【发布时间】:2019-11-23 15:20:57 【问题描述】:我正在创建一个简单的图像编辑器应用程序,因此需要加载和保存图像文件。我希望保存的文件出现在画廊中的单独相册中。从 android API 28 到 29,应用程序能够访问存储的程度发生了巨大变化。我可以在 Android Q (API 29) 中做我想做的事,但这种方式不向后兼容。
当我想在较低的 API 版本中获得相同的结果时,到目前为止,我只找到了需要使用已弃用代码的方法(从 API 29 开始)。
这些包括:
MediaStore.Images.Media.DATA
列的使用
通过Environment.getExternalStoragePublicDirectory(...)
获取外部存储的文件路径
直接通过MediaStore.Images.Media.insertImage(...)
插入图片
我的问题是:是否有可能以这种方式实现它,因此它向后兼容,但不需要弃用代码?如果不是,在这种情况下可以使用已弃用的代码,还是这些方法很快会从 sdk 中删除?无论如何,使用不推荐使用的方法感觉很糟糕,所以我宁愿不这样做:)
这是我发现的适用于 API 29 的方式:
ContentValues values = new ContentValues();
String filename = System.currentTimeMillis() + ".jpg";
values.put(MediaStore.Images.Media.TITLE, filename);
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.RELATIVE_PATH, "PATH/TO/ALBUM");
getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values);
然后我使用 insert 方法返回的 URI 来保存位图。问题是 API 29 中引入了 RELATIVE_PATH 字段,因此当我在较低版本上运行代码时,图像被放入“图片”文件夹而不是“PATH/TO/ALBUM”文件夹中。
【问题讨论】:
我的猜测是您将需要使用两种不同的存储策略,一种用于 API Level 29+,另一种用于旧设备。 “在这种情况下可以使用不推荐使用的代码吗” - 是的。像这样的东西“弃用”意味着“我们希望你使用其他东西”。如果您在较新的 Android 版本上使用这些“其他东西”,则在较旧的 Android 版本上运行的代码可以毫无问题地使用已弃用的 API。很少会从 SDK 中删除类和方法,这样您的代码将不再构建,我不希望您的任何选项都会发生这种情况。 @CommonsWare 感谢您的想法,这正是我现在正在做的事情,我想这是唯一可行的方法。如果您愿意,可以将您的评论转换为答案,我会接受。 @multimodcrafter 你是如何让图像保存工作的?insert
方法的 uri 基本上类似于 content://media/external/images/media/123
我无法从中获取文件路径。你介意发布代码吗?谢谢,
其实我只是想通了。感谢@multimodcrafter,如果没有你的帖子,我将无法在 Android Q 中将图片保存到图库中。
@DavidSantiagoTuriño 不错。我使用contentResolver.openOutputStream(uri)
直接基于uri 创建了一个流,我猜在引擎盖下它与您的方法相同。我的代码记录在这里:***.com/questions/36624756/…
【参考方案1】:
在这种情况下是否可以使用已弃用的代码,或者这些方法是否会很快从 sdk 中删除?
DATA
选项在 Android Q 上不起作用,因为该数据不包含在 query()
结果中,即使您要求它,您也不能使用它返回的路径,即使它们被返回。
默认情况下,Environment.getExternalStoragePublicDirectory(...)
选项在 Android Q 上不起作用,但您可以添加清单条目以重新启用它。但是,该清单条目可能会在 Android R 中删除,因此除非您时间紧迫,否则我不会走这条路。
AFAIK,MediaStore.Images.Media.insertImage(...)
仍然有效,即使它已被弃用。
是否有可能以这种方式实现它,因此它是向后兼容的,但不需要弃用的代码?
我的猜测是,您将需要使用两种不同的存储策略,一种用于 API 级别 29+,另一种用于旧设备。我在this sample app 中采用了这种方法,尽管我正在处理视频内容,而不是图像,所以insertImage()
不是一个选项。
【讨论】:
经过实际测试(三星galaxy s10),DATA
栏目在Android Q上仍然可用。我想你误解了文档的意思。它说您无法使用文件路径访问文件,而不是无法获取路径本身。
@Chenhe:“DATA 列在 Android Q 上仍然可用”——在我写这篇文章和 Android 10 发布之间的某个时间,规则也可能发生了变化,或者我的回答是基于之前的Android Q 的开发者预览版。
@CommonsWare 但是您刚才提到的示例是使用两种不同的方法来保存文件/downloadLegacy()
内部的旧方法和download()
中的android Q 及更高版本的新方法
@superjos:在 Android 9 及更早版本上,您应该可以使用 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
获取 Videos 目录,然后在该目录下创建一个子目录。我的示例正好直接下载到 Videos,因为我写示例的时候很懒。
感谢您的帮助。对于其他可能像我一样忽略的人:Environment.DIRECTORY_MOVIES
可能还不存在,所以在忙于创建自定义子目录 (mySubDir.mkdir()
) 时,应确保整个子树存在 (mySubDir.mkdirs()
)【参考方案2】:
这是适合我的代码。此代码将图像保存到手机上的子目录文件夹中。它检查手机的android版本,如果高于android q,它运行所需的代码,如果低于,它运行else语句中的代码。
来源:https://androidnoon.com/save-file-in-android-10-and-below-using-scoped-storage-in-android-studio/
private void saveImageToStorage(Bitmap bitmap) throws IOException
OutputStream imageOutStream;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME,
"image_screenshot.jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH,
Environment.DIRECTORY_PICTURES + File.pathSeparator + "AppName");
Uri uri =
getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values);
imageOutStream = getContentResolver().openOutputStream(uri);
else
String imagesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES). toString() + "/AppName";
File image = new File(imagesDir, "image_screenshot.jpg");
imageOutStream = new FileOutputStream(image);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream);
imageOutStream.close();
【讨论】:
【参考方案3】:对于旧 API (
让我们看看我的代码。
此函数创建一个图像文件。注意 appName 变量 - 它是一个相册的名称,图片将在其中显示。
override fun createImageFile(appName: String): File
val dir = File(appContext.externalMediaDirs[0], appName)
if(!dir.exists())
ir.mkdir()
return File(dir, createFileName())
然后,我将图像放入文件中,最后,我像这样运行媒体扫描仪:
private suspend fun scanNewFile(shot: File): Uri?
return suspendCancellableCoroutine continuation ->
MediaScannerConnection.scanFile(
appContext,
arrayOf<String>(shot.absolutePath),
arrayOf(imageMimeType)) _, uri -> continuation.resume(uri)
【讨论】:
【参考方案4】:经过反复试验,我发现可以以向后兼容的方式使用MediaStore
,以便在不同版本的实现之间共享尽可能多的代码。唯一的窍门是记住,如果您使用MediaColumns.DATA
,您需要自己创建文件。
让我们看看code from my project (Kotlin)。此示例用于保存音频,而不是图像,但您只需将 MIME_TYPE
和 DIRECTORY_MUSIC
替换为您需要的任何内容。
private fun newFile(): FileDescriptor?
// Create a file descriptor for a new recording.
val date = DateFormat.getDateTimeInstance().format(Calendar.getInstance().time)
val filename = "$date.mp3"
val values = ContentValues().apply
put(MediaColumns.TITLE, date)
put(MediaColumns.MIME_TYPE, "audio/mp3")
// store the file in a subdirectory
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
put(MediaColumns.DISPLAY_NAME, filename)
put(MediaColumns.RELATIVE_PATH, saveTo)
else
// RELATIVE_PATH was added in Q, so work around it by using DATA and creating the file manually
@Suppress("DEPRECATION")
val music = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).path
with(File("$music/P2oggle/$filename"))
@Suppress("DEPRECATION")
put(MediaColumns.DATA, path)
parentFile!!.mkdir()
createNewFile()
val uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)!!
return contentResolver.openFileDescriptor(uri, "w")?.fileDescriptor
在 Android 10 及更高版本上,我们使用DISPLAY_NAME
设置文件名并使用RELATIVE_PATH
设置子目录。在旧版本中,我们使用DATA
并手动创建文件(及其目录)。在此之后,两者的实现是相同的:我们只需从MediaStore
中提取文件描述符并将其返回以供使用。
【讨论】:
以上是关于如何将图像保存在 android Q 的子目录中,同时保持向后兼容的主要内容,如果未能解决你的问题,请参考以下文章
如何将包含内容的私有文件夹复制到 android q 及更高版本中的公共目录?
将图像从 Android 上的可绘制资源保存到 sdcard