我正在创建一个简单的图像编辑器应用程序,因此需要加载和保存图像文件。我希望保存的文件在单独的相册中显示在图库中。从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/jpg");
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,因此当我在较低版本上运行代码时,图像将放入“ Pictures”文件夹,而不是“ PATH / TO / ALBUM”文件夹。
答案 0 :(得分:2)
这是对我有用的代码。此代码将图像保存到手机的子目录文件夹中。它会检查手机的android版本,如果它高于android q,则运行所需的代码,如果它低于android,则运行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();
}
答案 1 :(得分:0)
在这种情况下可以使用不赞成使用的代码吗?还是会很快从sdk中删除这些方法?
DATA
选项在Android Q上不起作用,因为即使您要求,该数据也不包括在query()
结果中。
Environment.getExternalStoragePublicDirectory(...)
选项在Android Q上默认情况下不起作用,尽管您可以添加一个清单条目来重新启用它。但是,该清单条目可能会在Android R中删除,因此,除非您时间有限,否则我不会走这条路线。
AFAIK,MediaStore.Images.Media.insertImage(...)
仍然有效,即使已弃用。
是否可以以这种方式实现它,因此它向后兼容,但不需要不推荐使用的代码?
我的猜测是,您将需要使用两种不同的存储策略,一种用于API Level 29+,另一种用于较旧的设备。我在this sample app中采用了这种方法,尽管在那里我处理的是视频内容,而不是图像,所以insertImage()
不是一个选择。
答案 2 :(得分:0)
对于旧的API(<29),我将图像放置在外部媒体目录中,并通过MediaScannerConnection对其进行扫描。
让我们看看我的代码。
此功能创建一个图像文件。请注意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)
}
}
}
答案 3 :(得分:0)
经过反复试验,我发现可以以向后兼容的方式使用 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
中提取文件描述符并将其返回以供使用。