在 Android Q 之前,如果我们想要获取有关 APK 文件的信息,我们可以使用WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE来访问存储,然后在文件路径上使用PackageManager.getPackageArchiveInfo函数。
类似的情况也存在,例如在压缩文件上使用ZipFile 类,可能还有无数的框架 API 和第三方库。
谷歌最近宣布了对 Android Q 的大量限制。
其中之一称为“范围存储”,当访问设备拥有的所有文件时,它会破坏存储权限。它可以让您处理媒体文件,或者使用非常受限的存储访问框架 ( SAF ),该框架不允许应用程序使用文件 API 和文件路径访问和使用文件。
当Android Q Beta 2发布时,它破坏了很多应用程序,包括谷歌的应用程序。原因是它默认打开,影响所有应用程序,无论它们是否针对 Android Q。
原因是许多应用程序、SDK 和 Android 框架本身 - 都经常使用文件 API。在许多情况下,它们也不支持 InputStream 或 SAF 相关的解决方案。一个例子就是我写的 APK 解析示例 (PackageManager.getPackageArchiveInfo)。
然而,在 Q beta 3 上,情况发生了一些变化,因此目标 Q 的应用程序将具有范围存储,并且有一个标志可以禁用它,但仍然像往常一样使用正常的存储权限和文件 API。遗憾的是,该标志只是暂时的(请阅读此处),因此它推迟了不可避免的事情。
我已经尝试并发现了接下来的事情:
android file storage-access-framework android-10.0 scoped-storage
在 Android 11 中使用范围存储模型,我想让用户能够选择文件夹,从文档文件夹开始:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, ??? )
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE,null)
Run Code Online (Sandbox Code Playgroud)
问题是,如何生成手机文档文件夹的正确 URI?(它位于 root / 中)在官方文档中,没有给出示例。我真的希望所有标准位置都有一些简洁的常量?
该应用程序需要与其他应用程序共享存储在cacheDir根目录中的PDF文件。该问题出现在 Android 12 上,也可能出现在其他版本上。
显现:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
Run Code Online (Sandbox Code Playgroud)
提供者路径:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="." />
</paths>
Run Code Online (Sandbox Code Playgroud)
意图:
val pdfFile = File(requireContext().cacheDir, pdfFileName)
val fileUri: Uri = FileProvider.getUriForFile(
requireContext().applicationContext,
requireContext().packageName.toString() + ".provider",
pdfFile
)
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "application/pdf"
startActivity(Intent.createChooser(intent, "Share Document"))
Run Code Online (Sandbox Code Playgroud)
共享表成功打开,但此时始终显示此异常,并且随后共享到另一个应用程序失败。
Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading
androidx.core.content.FileProvider uri
content://uk.co.packagename.provider/cache/8BEDF7212-0DE46-42B0-9FA9-32C434BDD2F3HO.pdf
from pid=15363, uid=1000 requires the provider be exported, or grantUriPermission() …Run Code Online (Sandbox Code Playgroud) 我的目标是 android 10,我不使用android:requestLegacyExternalStorage. 我用的是DownloadManager通过下载到下载文件夹:request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)。我收听下载完整广播,提取 uri ( file:///storage/emulated/0/Download/<name>.pdf) 并通过 .pdf 文件在 pdf 阅读器中打开它FileProvider。它有效......为什么?自 android 10 和范围存储以来,它不应该不起作用吗?为什么我可以访问下载文件夹中的文件?
编辑:Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).listFiles()列出所有文件,尽管文档说:
为了提高用户隐私,不推荐直接访问共享/外部存储设备。当应用以 Q 为目标时,此方法返回的路径不再可供应用直接访问
android android-download-manager storage-access-framework android-10.0 scoped-storage
我的应用程序需要在下载文件夹中的文本文件中保存一个字符串。目前(目标:API 29 (Q) 我正在使用 FILE API 和:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
android:requestLegacyExternalStorage="true"
Run Code Online (Sandbox Code Playgroud)
但是对于 API 30 (R),如果我理解得很好,我必须将其迁移到作用域存储 (MediaStore.Downloads)。
在这里,我有点失落。我在下载中找不到显示如何创建文本文件的好的文档或片段。如果有人可以解释或展示如何做到这一点,我会很高兴吗?
我的应用程序扫描某些目录中的文本文件。用户可以点击按钮来添加目录:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, RESULT_CODE_DOCUMENT_TREE)
Run Code Online (Sandbox Code Playgroud)
然后,我将 URI 存储在数据库中,并用于DocumentsContract扫描目录中的文件并读取它们。在应用程序启动时,我加载存储的 URI 并以与扫描文件相同的方式处理它们。
在 Android API 28 上,存储的 URI 工作正常。在 Android API 30 上,SecurityException如果我在应用程序重新启动后尝试访问其中任何一个,我会收到一个错误消息。
有没有办法让这些权限在现代 Android 上持久存在?或者我应该实施一种完全不同的方法?
我想保存 Whatsapp 的状态。通过使用此代码,我可以保存图像
public void saveImage(Bitmap bitmap, String name) {
OutputStream fileOutputStream;
Uri imageUri;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver contentResolver = context.getApplicationContext().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "" + name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/V Troid/WhatsApp");
imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
fileOutputStream = (FileOutputStream) contentResolver.openOutputStream(Objects.requireNonNull(imageUri));
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
Objects.requireNonNull(fileOutputStream);
Toast.makeText(context.getApplicationContext(), "Image Saved", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(context.getApplicationContext(), "Error \n" + e, Toast.LENGTH_SHORT).show();
}
}
Run Code Online (Sandbox Code Playgroud)
但现在我也想保存视频文件,为此我正在使用此代码,但它对我不起作用,此代码损坏后我得到的文件
public void saveVideo(String name, Uri vidUri) {
OutputStream …Run Code Online (Sandbox Code Playgroud) 更新:
为了保存PDF文件:
在下面的答案部分。
为了保存位图文件:
@RequiresApi(api = Build.VERSION_CODES.Q)
@NonNull
private Uri saveBitmap(@NonNull final Context context, @NonNull final Bitmap bitmap,
@NonNull final Bitmap.CompressFormat format, @NonNull final String mimeType,
@NonNull final String displayName, @Nullable final String subFolder) throws IOException {
String relativeLocation = Environment.DIRECTORY_PICTURES;
if (!TextUtils.isEmpty(subFolder)) {
relativeLocation += File.separator + subFolder;
}
final ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
final ContentResolver resolver = context.getContentResolver();
OutputStream stream = null;
Uri uri = null;
try {
final Uri contentUri …Run Code Online (Sandbox Code Playgroud)