我正在尝试从设备的外部存储中删除音频文件(例如在/storage/emulated/0/Music文件夹中)。在分析了MediaStore示例之后,我最终得到了 API 28 及更早版本的以下解决方案:
fun deleteTracks(trackIds: LongArray): Int {
val whereClause = buildWildcardInClause(trackIds.size) // _id IN (?, ?, ?, ...)
return resolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, Array(trackIds.size) { trackIds[it].toString() })
}
Run Code Online (Sandbox Code Playgroud)
我注意到上面的代码只从 MediaStore 数据库中删除曲目,而不是从设备中删除(重新启动后文件会重新添加到 MediaStore)。因此,我修改了该代码以查询该Media.DATA列,然后使用该信息删除关联的文件。这按预期工作。
但是现在 Android Q 引入了 Scoped Storage,Media.DATA(和Albums.ALBUM_ART)现在已被弃用,因为应用程序可能无法访问这些文件。ContentResolver.openFileDescriptor只能用于读取文件,不能用于删除文件。
那么从 Android Q 开始,推荐的删除曲目的方法是什么?该样品不表明如何从删除多个文件MediaStore,并MediaStore.Audio.Media似乎的工作方式不同MediaStore.Images.Media。
我需要在原生 C/C++ 代码中的 Android 应用程序中按文件名打开文件。本机代码是我不想修改的第 3 方库,但它们通常需要文件名作为读/写文件的参数。使用 Google 的“范围存储”API 并在 Android 10 或更高版本中禁用对文件的本机访问,这是一个真正的问题。
一个众所周知的解决方案是获取文件描述符并使用“proc/self/fd/FD_NUMER”技巧,例如:
ParcelFileDescriptor mParcelFileDescriptor = null;
String getFileNameThatICanUseInNativeCode(Context context, DocumentFile doc) {
try {
Uri uri = doc.getUri();
mParcelFileDescriptor =
context.getContentResolver().openFileDescriptor(uri, "r");
if (mParcelFileDescriptor != null) {
int fd = mParcelFileDescriptor.getFd();
return "/proc/self/fd/" + fd;
}
}
catch (FileNotFoundException fne) {
return "";
}
}
// Don't forget to close mParcelFileDescriptor when done!
Run Code Online (Sandbox Code Playgroud)
将此传递给本机 C/C++ 代码有效,但前提是文件位于手机主存储中。如果用户尝试打开插入手机插槽的外部 SD 卡上的文件,则不起作用 - 以这种方式打开的文件没有读取权限。我只能获取文件描述符 int 号并使用 fdopen(fd)。但这将需要修改 3rd 方库(开源或许可)的源代码,并且每次更新这些库的原始源时都会令人头疼。
有没有更好的解决这个问题的方法?不,我不想听到添加的解决方案 …
我最近将应用程序的目标版本升级到 API 29。由于 Android 10 中的范围存储,我使用 MediaStore API 来存储和检索应用程序外部存储中的图像。之前,我曾经getExternalStoragePublicDirectory存储通过相机拍摄的图像,现在我使用MediaStore.Images.Media.EXTERNAL_CONTENT_URI将文件写入外部存储位置。
我现在面临的问题是,当我打开我的应用程序并拍照时,它存储在我给“myapp”的文件夹名称下,我可以通过 Mediastore 光标检索我的图像并将它们显示在自定义图库中。当我卸载我的应用程序“myapp”文件夹时仍然存在。当我再次安装我的应用程序并尝试从图库中读取图像时,光标没有返回任何图像。但是如果我再次拍照,那么我可以将它们加载到我的自定义图库中。自定义图库视图只是屏幕底部的一行图像,因此用户无需浏览照片文件夹即可将图像加载到应用程序。
这就是我在 MediaStore 中存储图像的方式
内容价值:
String RELATIVE_PATH = Environment.DIRECTORY_PICTURES + File.separator + "myApp";
final ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, generateImageName(new Date()));
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, RELATIVE_PATH);
Run Code Online (Sandbox Code Playgroud)
生成名称方法:
int sameSecondCount;
protected String generateName(Date now)
{
String result = formatter.format(now);
long nowMillis = now.getTime();
if (nowMillis / 1000 == lastMillis / 1000)
{
sameSecondCount++;
result += "_" + sameSecondCount;
}
else
sameSecondCount = 0;
lastMillis = nowMillis; …Run Code Online (Sandbox Code Playgroud) 我使用 DownloadManager 下载照片并将其保存到外部图片目录:
.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, File.separator + filename)
Run Code Online (Sandbox Code Playgroud)
Marshmallow 需要 WRITE_EXTERNAL_STORAGE(如预期),但在 Android Q 及更高版本上,它无需许可即可正常工作。为什么会这样?是因为范围存储吗?
android android-permissions android-download-manager android-external-storage scoped-storage
在引入范围存储之前,我使用下载管理器在我的应用程序中下载 pdf 并从 获取 pdf getExternalStorageDirectory,但由于范围存储,我无法再使用getExternalStorageDirectory它,因为它已被弃用。我决定放弃下载管理器以及它下载公共目录中的文件,而是使用改造来下载 pdf 文件。我知道我可以在 Android Manifest 中使用该requiredLegacyStorage标签,但它不适用于 Android 11,所以我没有使用它。
这是我的代码
fun readAndDownloadFile(context: Context) {
readQuraanInterface?.downloadFile()
Coroutines.io {
file = File(context.filesDir,"$DESTINATION_DIRECTORY/$FILE_NAME$FILE_EXTENSION")
if (file?.exists() == true) {
renderPDF()
showPdf(mPageIndex, Direction.None)
} else {
Log.i("new","new0")
val response = readQuraanRepository.downloadPdf()
if (response.isSuccessful) {
Log.i("new","new00 ${file!!.path} ${response.body()?.byteStream().toString()}")
response.body()?.byteStream()?.let {
file!!.copyInputStreamToFile(
it
)
}
Log.i("new","new1")
// renderPDF()
// showPdf(mPageIndex, Direction.None)
} else {
Log.i("new","new2")
Coroutines.main {
response.errorBody()?.string()
?.let { readQuraanInterface?.downloadFailed(it) }
}
}
}
}
}
private …Run Code Online (Sandbox Code Playgroud) 根据文档,RELATIVE_PATH 在 API 级别 29 中添加 public static final String RELATIVE_PATH 该媒体项在其持久存储的存储设备中的相对路径。例如,存储在 /storage/0000-0000/DCIM/Vacation/IMG1024.JPG 的项目的路径为 DCIM/Vacation/。
在 Android Q 之前,如何组织在 Android 上插入 MediaStore 的媒体文件 Afaik:根据已删除的文档,它PRIMARY_DIRECTORY不起作用
put(MediaStore.MediaColumns.PRIMARY_DIRECTORY, relativePath)
/**
* The primary directory name this media exists under. The value may be
* {@code NULL} if the media doesn't have a primary directory name.
*
* @removed
* @deprecated Replaced by {@link #RELATIVE_PATH}.
*/
@Column(Cursor.FIELD_TYPE_STRING)
@Deprecated
public static final String PRIMARY_DIRECTORY = "primary_directory";
Run Code Online (Sandbox Code Playgroud) 我已将我的应用程序设置为目标 AP 29 并requestLegacyExternalStorage=true从清单中删除。
现在我正在检查用户是否具有此权限,如果结果被拒绝,我会请求权限。
我的问题是,请求许可返回时Granted没有显示提示...我知道流程正在运行,因为在获得许可后我能够从图片中读取 GPS 位置。
我看到权限状态= Denied,一旦我明确请求此权限,它就会返回,Granted而无需任何用户交互。
一切看起来都不错,但我对没有看到提示感到困惑......这是预期的吗?我看到此权限属于“危险”权限,因此我期待出现提示。我正在 Android 10 设备上进行测试。
我没有显示任何代码,因为该项目是 Xamarin 并且权限逻辑是通过第三方库处理的,我认为我的代码不会有帮助,因为请求权限的平台逻辑被组件隐藏了。
设想
我有两个应用程序,一个是跟踪器应用程序,它记录传入和传出呼叫并压缩这些文件,并将文件路径发送到主应用程序,通过主应用程序将Inter-Process Communication这些文件上传到服务器。
现在我正在将这两个应用程序升级到 Android 11。在 Tracker 应用程序中,我用于MediaStore.Files API保存文件并尝试使用主应用程序中的文件路径读取这些文件。读取文件时File.canRead()返回false主应用程序。即使我尝试MediaStore API读取这些文件,它也会返回空Cursor。
在这里我有几个问题。
MANAGE_EXTERNAL_STORAGE访问存储中的所有文件?我正在将文件保存在 public 目录中Documents/AppData/Audio。请给我有关此的工作链接。任何帮助将不胜感激。谢谢
android mediastore storage-access-framework scoped-storage android-11
Android 11 中引入了范围存储。根据文档,应使用保存非媒体文件(如 PDF)的存储访问框架。因此,使用存储访问框架的用户需要使用系统文件选择器选择位置来保存非媒体文件(PDF)。但是如果您在 Android 11 上使用 Whatsapp 应用程序并尝试保存 PDF 文件,它永远不会要求用户选择位置。默认情况下,它将保存 pdf 文件到外部文档目录。我想在我的应用程序中复制相同的行为。有人可以在这里指导我吗?
Android 12最近在一些手机上发布,一些用户开始抱怨存储访问:他们无法再向应用程序授予对特定文件夹的访问权限(例如“下载”文件夹)。
该消息表示:“无法使用此文件夹。为了保护您的隐私,请选择其他文件夹”。
这个问题很容易重现,我花了一些时间寻找解决方案,但找不到一个简单的解决方案。
似乎唯一的解决方法是要求完全存储访问(但这不是我想要做的,这很糟糕)或要求用户创建一个新文件夹,这将是用户体验的绝对痛苦。
我不明白的是:Google 是否在没有任何文档的情况下就在 Android 12 上弃用了 SAF?这是一个错误还是一个功能?我真的很困惑那里。Android 11 的迁移已经很痛苦了,现在恐怕 12 会变得更加痛苦。
这是代码,但没什么特别的:
/*
* Requests Scoped Storage access authorization
*/
@RequiresApi(Build.VERSION_CODES.Q)
@JvmStatic
fun requestScopedStorageAccess(activity: Activity, requestCode: Int) {
val storageManager = activity.getSystemService(Context.STORAGE_SERVICE) as StorageManager
val intent = storageManager.primaryStorageVolume.createOpenDocumentTreeIntent()
activity.startActivityForResult(intent, requestCode)
}
Run Code Online (Sandbox Code Playgroud)
我认为值得注意的是,一切都在 Android 10 和 11 上完美运行,因此问题确实与 Android 12 有关。
storage android storage-access-framework scoped-storage android-12
android ×10
scoped-storage ×10
mediastore ×4
storage ×3
android-10.0 ×2
android-11 ×2
android-12 ×1
file-storage ×1
java ×1
kotlin ×1
media ×1
retrofit ×1