Android 11 个分区存储权限
Android 11 Scoped storage permissions
我的应用程序使用 Environment.getExternalStorageDirectory()
提供的图像文件路径来创建相册,但是 Android 11 我将无法直接访问文件.
根据 Android 开发人员文档,他们最近引入了 MANAGE_EXTERNAL_STORAGE
权限,但我不明白如果添加此权限我是否能够继续通过环境访问文件。
我在 Android 11 虚拟设备上尝试了我的应用程序,即使没有请求 MANAGE_EXTERNAL_STORAGE
许可,它似乎也能完美运行!
阅读有关 Android 开发人员的文档,似乎使用文件 API 访问照片和媒体唯一位置的应用程序可以继续工作,但 我'我不确定.
有没有人更了解 Android 文档???
According to the Android developers documentation they recently introduced the MANAGE_EXTERNAL_STORAGE permission, but I didn't understand if adding this permission I'm able to continue to access file by Environment or not.
是的,你会的。但是,请记住,如果您打算在 Play 商店(可能还有其他地方)分发您的应用程序,您将需要证明请求该许可的理由。所以,除非你有非常的充分理由使用MANAGE_EXTERNAL_STORAGE
,否则请使用something else。
Android 11 不允许直接访问存储中的文件,您必须从存储中 select 文件并将该文件复制到您的应用程序包 chache com.android.myapp 中。
以下是将文件从存储复制到应用程序包缓存的方法
private String copyFileToInternalStorage(Uri uri, String newDirName) {
Uri returnUri = uri;
Cursor returnCursor = mContext.getContentResolver().query(returnUri, new String[]{
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
}, null, null, null);
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
String name = (returnCursor.getString(nameIndex));
String size = (Long.toString(returnCursor.getLong(sizeIndex)));
File output;
if (!newDirName.equals("")) {
File dir = new File(mContext.getFilesDir() + "/" + newDirName);
if (!dir.exists()) {
dir.mkdir();
}
output = new File(mContext.getFilesDir() + "/" + newDirName + "/" + name);
} else {
output = new File(mContext.getFilesDir() + "/" + name);
}
try {
InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
FileOutputStream outputStream = new FileOutputStream(output);
int read = 0;
int bufferSize = 1024;
final byte[] buffers = new byte[bufferSize];
while ((read = inputStream.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
inputStream.close();
outputStream.close();
} catch (Exception e) {
Log.e("Exception", e.getMessage());
}
return output.getPath();
}
在此处查看 Android 11 个分区存储更新
此处提供快速解决方案:
对于快速解决方案,如果你把你的 android 目标和编译 sdk 版本是 29 那么你的应用程序将 运行 on android 11 与你在 android这里十个
在 mainfest 文件中
当您将 android 设备从 api 10(29) 更新到 android 11(30) Api 时,无法从您的设备检索数据存储或移动目录我今天在 play store 上检查了成千上万的应用程序,在 play store 上实时下载了数百万他们没有在 android 11 上工作,因为 android 11 引入了新的范围存储更新,你必须使用 MediaStore 对象实现获取媒体文件的新方法,
我在阅读 android 文档后想与您分享的一些有用信息列在这里:
in android 11 , you can access the cache only for their own specific apps.
apps cannot create their own app-specific directory on external storage. To access the directory that the system provides for your app, call getExternalFilesDirs()
If your app targets Android 11, it cannot access the files in any other app's data directory, even if the other app targets Android 8.1 (API level 27) or lower and has made the files in its data directory world-readable
On Android 11, apps can no longer access files in any other app's dedicated, app-specific directory within external storage.
Apps that run on Android 11 but target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. This flag allows apps to temporarily opt out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. After you update your app to target Android 11, the system ignores the requestLegacyExternalStorage flag.
在此之前 android 10 我们使用
android:requestLegacyExternalStorage="true"
tools:targetApi="q"
在 application 属性下的清单中现在此方法在 android 11.
中不起作用
所以现在迁移到新的更新谢谢
Android 11
如果您的目标是 Android 11 (targetSdkVersion 30
),那么您需要 AndroidManifest.xml 中的以下权限才能修改和访问文档。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
对于 Android 10,您将以下行放在 AndroidManifest.xml 标签中
android:requestLegacyExternalStorage="true"
下面的方法检查权限是被允许还是被拒绝
private boolean checkPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
int result = ContextCompat.checkSelfPermission(PermissionActivity.this, READ_EXTERNAL_STORAGE);
int result1 = ContextCompat.checkSelfPermission(PermissionActivity.this, WRITE_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
}
}
以下方法可用于在android11或以下
中请求权限
private void requestPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s",getApplicationContext().getPackageName())));
startActivityForResult(intent, 2296);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, 2296);
}
} else {
//below android 11
ActivityCompat.requestPermissions(PermissionActivity.this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
}
}
Android11 及以上版本
处理权限回调
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 2296) {
if (SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
// perform action when allow permission success
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
}
}
}
处理 OS 低于 Android 11
版本的权限回调
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
if (grantResults.length > 0) {
boolean READ_EXTERNAL_STORAGE = grantResults[0] == PackageManager.PERMISSION_GRANTED;
boolean WRITE_EXTERNAL_STORAGE = grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (READ_EXTERNAL_STORAGE && WRITE_EXTERNAL_STORAGE) {
// perform action when allow permission success
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
}
break;
}
}
注意:MANAGE_EXTERNAL_STORAGE
是一种特殊权限,仅适用于防病毒、文件管理器等少数应用程序。在将应用程序发布到 PlayStore 时,您必须说明原因。
我为 Android 11 (SDK R - 30):
找到了这种方法
1- 在 Manifest 中必须添加此权限:(仅适用于 R)
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
2-请求OS对话请求许可:
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 1);
3- 检查您的应用是否可以访问 存储 :
if (!Environment.isExternalStorageManager())
4- 使用 Intent 为您的应用打开 “所有文件访问权限”
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", this.getPackageName(), null);
intent.setData(uri);
startActivity(intent);
在 Android 11 这是我的功能齐全的代码,用于启动相机和 运行:
`
<!--Still need to request legacy storage for devices running on API 29 and below otherwise they won't work -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourpackage">
<!-- For Various Types -->
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="vnd.android.cursor.dir/email" />
</intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.CALL" />
</intent>
</queries>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- ... Rest of manifest -->
<application
...
android:requestLegacyExternalStorage="true"
...
<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">
</meta-data>
</provider>
</application>
</manifest
`
file_path.xml 文档位于 res/xml 文件夹中,包含以下图片:
`
<external-files-path
name="internal_images"
path="files/Pictures" />
<external-files-path
name="internal_images_alternate"
path="Pictures" />
</paths>
`
然后在实际检查存储选项时,我实现了以下代码:
`
private boolean hasManageExternalStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
return true;
} else {
if (Environment.isExternalStorageLegacy()) {
return true;
}
try {
Intent intent = new Intent();
intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:com.example.yourpackage"));
startActivityForResult(intent, RESULT_CODE); //result code is just an int
return false;
} catch (Exception e) {
return false;
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Environment.isExternalStorageLegacy()) {
return true;
} else {
try {
Intent intent = new Intent();
intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:com.example.yourpackage"));
startActivityForResult(intent, RESULT_CODE); //result code is just an int
return false;
} catch (Exception e) {
return true; //if anything needs adjusting it would be this
}
}
}
return true; // assumed storage permissions granted
}
`
接下来是权限请求:
`
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.MANAGE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permission request code is just an int
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permisison request code is just an int
}
`
然后(我知道这超出了原始问题的范围)你有可能使用现在这样的相机意图:
`
public static Intent getCameraIntentWithUpdatedPackages(Context context){
List<ResolveInfo> resolveInfo = new ArrayList<>();
final Intent capturePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
PackageManager pm = context.getPackageManager();
resolveInfo = pm.queryIntentActivities(capturePhoto, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
// For Android 11 we need to add specific camera apps
// due them are not added during ACTION_IMAGE_CAPTURE scanning...
resolveInfo.addAll(getCameraSpecificAppsInfo(context));
}
return capturePhoto;
}
private static List<ResolveInfo> getCameraSpecificAppsInfo(Context context){
List<ResolveInfo> resolveInfo = new ArrayList<>();
if (context == null){
return resolveInfo;
}
PackageManager pm = context.getPackageManager();
for (String packageName : CAMERA_SPECIFIC_APPS) {
resolveInfo.addAll(getCameraSpecificAppInfo(packageName, pm));
}
return resolveInfo;
}
private static List<ResolveInfo> getCameraSpecificAppInfo(String packageName, PackageManager pm){
Intent specificCameraApp = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
specificCameraApp.setPackage(packageName);
return pm.queryIntentActivities(specificCameraApp, 0);
}
public static File dispatchTakePictureIntent(Context context, String photoNameSuffix) {
Intent takePictureIntent = getCameraIntentWithUpdatedPackages(context);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile(activity, photoNameSuffix);
} catch (IOException ex) {
ex.printStackTrace();
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = Uri.fromFile(photoFile);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
} else {
File file = new File(photoURI.getPath());
if (!file.exists()) {
file.mkdirs();
file.mkdir();
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Uri photoUri = FileProvider.getUriForFile(context.getApplicationContext(), context.getApplicationContext().getPackageName() + ".provider", file);
activity.grantUriPermission(photoURI.getAuthority(), photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
}
//disable strict mode policies
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
context.startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
return photoFile;
}
return null;
}
static final String[] CAMERA_SPECIFIC_APPS = new String[]{
"best.camera",
"net.sourceforge.opencamera",
"com.google.android.GoogleCamera",
"tools.photo.hd.camera",
};
`
就像我们有一张图片,我们可以重命名到我们自己的目录中,假设包名称被授予所有文件访问权限!
在简单的方法中,我们只需要启用以下权限
$<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
然后我们可以使用我们的旧代码执行任何操作
我也找了几个小时的解决方案,测试了一些方法。在我的应用程序中,用户可以发送带有 pdf 文档作为附件的电子邮件,但由于 android 的权限更改,自 android 11 起附件突然为空。为了获取文件,我使用了 FileProvider。
我在这里和其他线程中找到的建议方法没有用,直到我自己测试并随意地做了与 Monu meena 相同的事情并将其添加到我的 android 清单文件中:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
这是我唯一可行的解决方案 case.I 没有删除读取或写入权限,我也没有将目标 sdk 设置为 29 或更低,我的目标 sdk 仍然是 30,它也是适用于 API 低于 30 的设备。我已经在 android studio 模拟器中的多个具有不同 API 版本的设备上成功测试。
所以试试吧,有时最简单的解决方案是最好的。
MANAGE_EXTERNAL_STORAGE
是严格许可,只能用于有效目的,例如文件管理器和防病毒应用程序。参见 the usage。
我会用 this library 提供更简单的东西。您可以在没有完整磁盘权限的情况下访问分区存储 (MANAGE_EXTERNAL_STORAGE
)。此代码将要求用户授予访问权限:
class MainActivity : AppCompatActivity() {
private val storageHelper = SimpleStorageHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupSimpleStorage(savedInstanceState)
setupButtonActions()
}
private fun setupButtonActions() {
btnRequestStorageAccess.setOnClickListener {
storageHelper.requestStorageAccess()
}
btnSelectFolder.setOnClickListener {
storageHelper.openFolderPicker()
}
btnSelectFile.setOnClickListener {
storageHelper.openFilePicker()
}
}
private fun setupSimpleStorage(savedInstanceState: Bundle?) {
savedInstanceState?.let { storageHelper.onRestoreInstanceState(it) }
storageHelper.onStorageAccessGranted = { requestCode, root ->
Toast.makeText(this, "Yay, granted!", Toast.LENGTH_SHORT).show()
}
storageHelper.onFileSelected = { requestCode, file ->
Toast.makeText(this, file.fullName, Toast.LENGTH_SHORT).show()
}
storageHelper.onFolderSelected = { requestCode, folder ->
Toast.makeText(this, folder.getAbsolutePath(this), Toast.LENGTH_SHORT).show()
}
}
}
由于直接文件路径 (java.io.File
) 不再可靠,因此您需要 DocumentFile
通过 URI 管理文件。该库还提供了丰富的扩展功能,即:
DocumentFile.getProperties()
DocumentFile.search()
DocumentFile.deleteRecursively()
DocumentFile.openOutputStream()
DocumentFile.copyFileTo()
List<DocumentFile>.moveTo()
等
注意:此回答不需要MANAGE_EXTERNAL_STORAGE
权限
在 android 10 及更高版本 MANAGE_EXTERNAL_STORAGE
中,我们不能将它用于 Play 商店应用程序,除非文件管理器或防病毒软件使它变得毫无用处。
因此,在没有 MANAGE_EXTERNAL_STORAGE 以下答案的情况下访问存储中的照片会很有用
在清单中
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
访问媒体文件
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.
// Container for information about each video.
data class Image(val uri: Uri,
val name: String,
val duration: Int,
val size: Int
)
val imgList = mutableListOf<Image>()
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(
MediaStore.VOLUME_EXTERNAL
)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE
)
// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Images.Media.DISPLAY_NAME} ASC"
val query = ContentResolver.query(
collection,
projection,
null,
null,
sortOrder
)
query?.use { cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val size = cursor.getInt(sizeColumn)
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
// Stores column values and the contentUri in a local object
// that represents the media file.
imgList += Image(contentUri, name, size)
}
}
创建文件
// Request code
const val CREATE_FILE = 1
private fun createFile(pickerInitialUri: Uri) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "Type of file"
putExtra(Intent.EXTRA_TITLE, "Name of File")
// Optionally, specify a URI for the directory that should be opened in
// the system file picker before your app creates the document.
putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, CREATE_FILE)
}
在文件位置(无论你在哪里使用它)
使用
mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
而不是
Environment.getExternalStorageDirectory();
并且在权限中
使用(未经许可见评论)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// return Environment.isExternalStorageManager();
return true;
}
我已经解决了这个问题-
做 -
- 保存在外部目录中,这将有助于在 SDK 版本 30 或更高版本中阅读。
- 添加 '//' + 目录路径 & 你的问题将得到解决 所以这意味着你的路径将是 '//'+ getExternalStorageDirectory())!.path
- 添加读写权限-
在清单中
访问媒体文件
请勿使用此应用,因为 Play 商店不会接受您的应用。
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
这是保存和检索文件的代码,它适用于 SDK > 30 和 SDK =< 30。
final directory = (await getExternalStorageDirectory())!.path;
ByteData? byteData =
await (image.toByteData(format: ui.ImageByteFormat.png));
Uint8List pngBytes = byteData!.buffer.asUint8List();
File imgFile = new File('$directory/screenshot${rng.nextInt(2000)}.png');
await imgFile.writeAsBytes(pngBytes);
setState(() {
_imageFile = imgFile;
});
// Add '//' + directory path & your problem will be resolved
return '//'+imgFile.path;
现在分享文件 - takeScreenshot().then((value) => Share.shareFiles(['$value'],
文本:
'Hello'),
);
如果您想从设备写入和读取文件。您基本上可以使用 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
(不必是 DIRECTORY DOCUMENTS)代替 Environment.getExternalStorageDirectory()
,您不需要请求 MANAGE_EXTERNAL_STORAGE
许可。这样在Android11上正常工作
在我的例子中,只需要创建一个 .csv 文件,然后发送到我遵循此文档的服务器。
File folder = new File(getBaseContext().getFilesDir(), "/test/CSV");
filepath = folder.toString() + "/" + id + ".csv";
private fun loadFilesFromSharedStorage() {
try {
val projection = arrayOf(
MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.DISPLAY_NAME
)
val selection = when (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
true -> "${MediaStore.MediaColumns.RELATIVE_PATH} LIKE ?"
else -> MediaStore.Images.Media.DATA + " like ? "
}
val selectionArgs = arrayOf("%test%")
val uriExternal = MediaStore.Files.getContentUri("external")
contentResolver.query(
uriExternal,
projection,
selection,
selectionArgs,
null
)?.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
while (it.moveToNext()) {
try {
val contentUri: Uri = ContentUris.withAppendedId(
uriExternal,
it.getLong(idColumn)
) /*Use this URI for next*/
} catch (e: Exception) {
e.printStackTrace()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
使用块获取 Android11 中的文件共享存储并使用它
我的应用程序使用 Environment.getExternalStorageDirectory()
提供的图像文件路径来创建相册,但是 Android 11 我将无法直接访问文件.
根据 Android 开发人员文档,他们最近引入了 MANAGE_EXTERNAL_STORAGE
权限,但我不明白如果添加此权限我是否能够继续通过环境访问文件。
我在 Android 11 虚拟设备上尝试了我的应用程序,即使没有请求 MANAGE_EXTERNAL_STORAGE
许可,它似乎也能完美运行!
阅读有关 Android 开发人员的文档,似乎使用文件 API 访问照片和媒体唯一位置的应用程序可以继续工作,但 我'我不确定.
有没有人更了解 Android 文档???
According to the Android developers documentation they recently introduced the MANAGE_EXTERNAL_STORAGE permission, but I didn't understand if adding this permission I'm able to continue to access file by Environment or not.
是的,你会的。但是,请记住,如果您打算在 Play 商店(可能还有其他地方)分发您的应用程序,您将需要证明请求该许可的理由。所以,除非你有非常的充分理由使用MANAGE_EXTERNAL_STORAGE
,否则请使用something else。
Android 11 不允许直接访问存储中的文件,您必须从存储中 select 文件并将该文件复制到您的应用程序包 chache com.android.myapp 中。 以下是将文件从存储复制到应用程序包缓存的方法
private String copyFileToInternalStorage(Uri uri, String newDirName) {
Uri returnUri = uri;
Cursor returnCursor = mContext.getContentResolver().query(returnUri, new String[]{
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
}, null, null, null);
/*
* Get the column indexes of the data in the Cursor,
* * move to the first row in the Cursor, get the data,
* * and display it.
* */
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
String name = (returnCursor.getString(nameIndex));
String size = (Long.toString(returnCursor.getLong(sizeIndex)));
File output;
if (!newDirName.equals("")) {
File dir = new File(mContext.getFilesDir() + "/" + newDirName);
if (!dir.exists()) {
dir.mkdir();
}
output = new File(mContext.getFilesDir() + "/" + newDirName + "/" + name);
} else {
output = new File(mContext.getFilesDir() + "/" + name);
}
try {
InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
FileOutputStream outputStream = new FileOutputStream(output);
int read = 0;
int bufferSize = 1024;
final byte[] buffers = new byte[bufferSize];
while ((read = inputStream.read(buffers)) != -1) {
outputStream.write(buffers, 0, read);
}
inputStream.close();
outputStream.close();
} catch (Exception e) {
Log.e("Exception", e.getMessage());
}
return output.getPath();
}
在此处查看 Android 11 个分区存储更新
此处提供快速解决方案:
对于快速解决方案,如果你把你的 android 目标和编译 sdk 版本是 29 那么你的应用程序将 运行 on android 11 与你在 android这里十个
在 mainfest 文件中
当您将 android 设备从 api 10(29) 更新到 android 11(30) Api 时,无法从您的设备检索数据存储或移动目录我今天在 play store 上检查了成千上万的应用程序,在 play store 上实时下载了数百万他们没有在 android 11 上工作,因为 android 11 引入了新的范围存储更新,你必须使用 MediaStore 对象实现获取媒体文件的新方法,
我在阅读 android 文档后想与您分享的一些有用信息列在这里:
in android 11 , you can access the cache only for their own specific apps.
apps cannot create their own app-specific directory on external storage. To access the directory that the system provides for your app, call getExternalFilesDirs()
If your app targets Android 11, it cannot access the files in any other app's data directory, even if the other app targets Android 8.1 (API level 27) or lower and has made the files in its data directory world-readable
On Android 11, apps can no longer access files in any other app's dedicated, app-specific directory within external storage.
Apps that run on Android 11 but target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. This flag allows apps to temporarily opt out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. After you update your app to target Android 11, the system ignores the requestLegacyExternalStorage flag.
在此之前 android 10 我们使用
android:requestLegacyExternalStorage="true"
tools:targetApi="q"
在 application 属性下的清单中现在此方法在 android 11.
中不起作用所以现在迁移到新的更新谢谢
Android 11
如果您的目标是 Android 11 (targetSdkVersion 30
),那么您需要 AndroidManifest.xml 中的以下权限才能修改和访问文档。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
对于 Android 10,您将以下行放在 AndroidManifest.xml 标签中
android:requestLegacyExternalStorage="true"
下面的方法检查权限是被允许还是被拒绝
private boolean checkPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
int result = ContextCompat.checkSelfPermission(PermissionActivity.this, READ_EXTERNAL_STORAGE);
int result1 = ContextCompat.checkSelfPermission(PermissionActivity.this, WRITE_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
}
}
以下方法可用于在android11或以下
中请求权限private void requestPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s",getApplicationContext().getPackageName())));
startActivityForResult(intent, 2296);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, 2296);
}
} else {
//below android 11
ActivityCompat.requestPermissions(PermissionActivity.this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
}
}
Android11 及以上版本
处理权限回调@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 2296) {
if (SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
// perform action when allow permission success
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
}
}
}
处理 OS 低于 Android 11
版本的权限回调@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
if (grantResults.length > 0) {
boolean READ_EXTERNAL_STORAGE = grantResults[0] == PackageManager.PERMISSION_GRANTED;
boolean WRITE_EXTERNAL_STORAGE = grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (READ_EXTERNAL_STORAGE && WRITE_EXTERNAL_STORAGE) {
// perform action when allow permission success
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
}
break;
}
}
注意:MANAGE_EXTERNAL_STORAGE
是一种特殊权限,仅适用于防病毒、文件管理器等少数应用程序。在将应用程序发布到 PlayStore 时,您必须说明原因。
我为 Android 11 (SDK R - 30):
找到了这种方法1- 在 Manifest 中必须添加此权限:(仅适用于 R)
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
2-请求OS对话请求许可:
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 1);
3- 检查您的应用是否可以访问 存储 :
if (!Environment.isExternalStorageManager())
4- 使用 Intent 为您的应用打开 “所有文件访问权限”
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", this.getPackageName(), null);
intent.setData(uri);
startActivity(intent);
在 Android 11 这是我的功能齐全的代码,用于启动相机和 运行:
`
<!--Still need to request legacy storage for devices running on API 29 and below otherwise they won't work -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourpackage">
<!-- For Various Types -->
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="vnd.android.cursor.dir/email" />
</intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.CALL" />
</intent>
</queries>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- ... Rest of manifest -->
<application
...
android:requestLegacyExternalStorage="true"
...
<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">
</meta-data>
</provider>
</application>
</manifest
`
file_path.xml 文档位于 res/xml 文件夹中,包含以下图片:
`
<external-files-path
name="internal_images"
path="files/Pictures" />
<external-files-path
name="internal_images_alternate"
path="Pictures" />
</paths>
`
然后在实际检查存储选项时,我实现了以下代码:
`
private boolean hasManageExternalStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
return true;
} else {
if (Environment.isExternalStorageLegacy()) {
return true;
}
try {
Intent intent = new Intent();
intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:com.example.yourpackage"));
startActivityForResult(intent, RESULT_CODE); //result code is just an int
return false;
} catch (Exception e) {
return false;
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (Environment.isExternalStorageLegacy()) {
return true;
} else {
try {
Intent intent = new Intent();
intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:com.example.yourpackage"));
startActivityForResult(intent, RESULT_CODE); //result code is just an int
return false;
} catch (Exception e) {
return true; //if anything needs adjusting it would be this
}
}
}
return true; // assumed storage permissions granted
}
`
接下来是权限请求: `
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.MANAGE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permission request code is just an int
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permisison request code is just an int
}
`
然后(我知道这超出了原始问题的范围)你有可能使用现在这样的相机意图:
`
public static Intent getCameraIntentWithUpdatedPackages(Context context){
List<ResolveInfo> resolveInfo = new ArrayList<>();
final Intent capturePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
PackageManager pm = context.getPackageManager();
resolveInfo = pm.queryIntentActivities(capturePhoto, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
// For Android 11 we need to add specific camera apps
// due them are not added during ACTION_IMAGE_CAPTURE scanning...
resolveInfo.addAll(getCameraSpecificAppsInfo(context));
}
return capturePhoto;
}
private static List<ResolveInfo> getCameraSpecificAppsInfo(Context context){
List<ResolveInfo> resolveInfo = new ArrayList<>();
if (context == null){
return resolveInfo;
}
PackageManager pm = context.getPackageManager();
for (String packageName : CAMERA_SPECIFIC_APPS) {
resolveInfo.addAll(getCameraSpecificAppInfo(packageName, pm));
}
return resolveInfo;
}
private static List<ResolveInfo> getCameraSpecificAppInfo(String packageName, PackageManager pm){
Intent specificCameraApp = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
specificCameraApp.setPackage(packageName);
return pm.queryIntentActivities(specificCameraApp, 0);
}
public static File dispatchTakePictureIntent(Context context, String photoNameSuffix) {
Intent takePictureIntent = getCameraIntentWithUpdatedPackages(context);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile(activity, photoNameSuffix);
} catch (IOException ex) {
ex.printStackTrace();
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = Uri.fromFile(photoFile);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
} else {
File file = new File(photoURI.getPath());
if (!file.exists()) {
file.mkdirs();
file.mkdir();
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Uri photoUri = FileProvider.getUriForFile(context.getApplicationContext(), context.getApplicationContext().getPackageName() + ".provider", file);
activity.grantUriPermission(photoURI.getAuthority(), photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
}
//disable strict mode policies
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
context.startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
return photoFile;
}
return null;
}
static final String[] CAMERA_SPECIFIC_APPS = new String[]{
"best.camera",
"net.sourceforge.opencamera",
"com.google.android.GoogleCamera",
"tools.photo.hd.camera",
};
`
就像我们有一张图片,我们可以重命名到我们自己的目录中,假设包名称被授予所有文件访问权限!
在简单的方法中,我们只需要启用以下权限
$<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
然后我们可以使用我们的旧代码执行任何操作
我也找了几个小时的解决方案,测试了一些方法。在我的应用程序中,用户可以发送带有 pdf 文档作为附件的电子邮件,但由于 android 的权限更改,自 android 11 起附件突然为空。为了获取文件,我使用了 FileProvider。 我在这里和其他线程中找到的建议方法没有用,直到我自己测试并随意地做了与 Monu meena 相同的事情并将其添加到我的 android 清单文件中:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
这是我唯一可行的解决方案 case.I 没有删除读取或写入权限,我也没有将目标 sdk 设置为 29 或更低,我的目标 sdk 仍然是 30,它也是适用于 API 低于 30 的设备。我已经在 android studio 模拟器中的多个具有不同 API 版本的设备上成功测试。
所以试试吧,有时最简单的解决方案是最好的。
MANAGE_EXTERNAL_STORAGE
是严格许可,只能用于有效目的,例如文件管理器和防病毒应用程序。参见 the usage。
我会用 this library 提供更简单的东西。您可以在没有完整磁盘权限的情况下访问分区存储 (MANAGE_EXTERNAL_STORAGE
)。此代码将要求用户授予访问权限:
class MainActivity : AppCompatActivity() {
private val storageHelper = SimpleStorageHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupSimpleStorage(savedInstanceState)
setupButtonActions()
}
private fun setupButtonActions() {
btnRequestStorageAccess.setOnClickListener {
storageHelper.requestStorageAccess()
}
btnSelectFolder.setOnClickListener {
storageHelper.openFolderPicker()
}
btnSelectFile.setOnClickListener {
storageHelper.openFilePicker()
}
}
private fun setupSimpleStorage(savedInstanceState: Bundle?) {
savedInstanceState?.let { storageHelper.onRestoreInstanceState(it) }
storageHelper.onStorageAccessGranted = { requestCode, root ->
Toast.makeText(this, "Yay, granted!", Toast.LENGTH_SHORT).show()
}
storageHelper.onFileSelected = { requestCode, file ->
Toast.makeText(this, file.fullName, Toast.LENGTH_SHORT).show()
}
storageHelper.onFolderSelected = { requestCode, folder ->
Toast.makeText(this, folder.getAbsolutePath(this), Toast.LENGTH_SHORT).show()
}
}
}
由于直接文件路径 (java.io.File
) 不再可靠,因此您需要 DocumentFile
通过 URI 管理文件。该库还提供了丰富的扩展功能,即:
DocumentFile.getProperties()
DocumentFile.search()
DocumentFile.deleteRecursively()
DocumentFile.openOutputStream()
DocumentFile.copyFileTo()
List<DocumentFile>.moveTo()
等
注意:此回答不需要MANAGE_EXTERNAL_STORAGE
权限
在 android 10 及更高版本 MANAGE_EXTERNAL_STORAGE
中,我们不能将它用于 Play 商店应用程序,除非文件管理器或防病毒软件使它变得毫无用处。
因此,在没有 MANAGE_EXTERNAL_STORAGE 以下答案的情况下访问存储中的照片会很有用
在清单中
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
访问媒体文件
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.
// Container for information about each video.
data class Image(val uri: Uri,
val name: String,
val duration: Int,
val size: Int
)
val imgList = mutableListOf<Image>()
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(
MediaStore.VOLUME_EXTERNAL
)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE
)
// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Images.Media.DISPLAY_NAME} ASC"
val query = ContentResolver.query(
collection,
projection,
null,
null,
sortOrder
)
query?.use { cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val size = cursor.getInt(sizeColumn)
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
// Stores column values and the contentUri in a local object
// that represents the media file.
imgList += Image(contentUri, name, size)
}
}
创建文件
// Request code
const val CREATE_FILE = 1
private fun createFile(pickerInitialUri: Uri) {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "Type of file"
putExtra(Intent.EXTRA_TITLE, "Name of File")
// Optionally, specify a URI for the directory that should be opened in
// the system file picker before your app creates the document.
putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, CREATE_FILE)
}
在文件位置(无论你在哪里使用它) 使用
mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
而不是
Environment.getExternalStorageDirectory();
并且在权限中 使用(未经许可见评论)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// return Environment.isExternalStorageManager();
return true;
}
我已经解决了这个问题-
做 -
- 保存在外部目录中,这将有助于在 SDK 版本 30 或更高版本中阅读。
- 添加 '//' + 目录路径 & 你的问题将得到解决 所以这意味着你的路径将是 '//'+ getExternalStorageDirectory())!.path
- 添加读写权限- 在清单中
访问媒体文件
请勿使用此应用,因为 Play 商店不会接受您的应用。
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
这是保存和检索文件的代码,它适用于 SDK > 30 和 SDK =< 30。
final directory = (await getExternalStorageDirectory())!.path;
ByteData? byteData =
await (image.toByteData(format: ui.ImageByteFormat.png));
Uint8List pngBytes = byteData!.buffer.asUint8List();
File imgFile = new File('$directory/screenshot${rng.nextInt(2000)}.png');
await imgFile.writeAsBytes(pngBytes);
setState(() {
_imageFile = imgFile;
});
// Add '//' + directory path & your problem will be resolved
return '//'+imgFile.path;
现在分享文件 - takeScreenshot().then((value) => Share.shareFiles(['$value'], 文本: 'Hello'), );
如果您想从设备写入和读取文件。您基本上可以使用 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
(不必是 DIRECTORY DOCUMENTS)代替 Environment.getExternalStorageDirectory()
,您不需要请求 MANAGE_EXTERNAL_STORAGE
许可。这样在Android11上正常工作
在我的例子中,只需要创建一个 .csv 文件,然后发送到我遵循此文档的服务器。
File folder = new File(getBaseContext().getFilesDir(), "/test/CSV");
filepath = folder.toString() + "/" + id + ".csv";
private fun loadFilesFromSharedStorage() {
try {
val projection = arrayOf(
MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.DISPLAY_NAME
)
val selection = when (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
true -> "${MediaStore.MediaColumns.RELATIVE_PATH} LIKE ?"
else -> MediaStore.Images.Media.DATA + " like ? "
}
val selectionArgs = arrayOf("%test%")
val uriExternal = MediaStore.Files.getContentUri("external")
contentResolver.query(
uriExternal,
projection,
selection,
selectionArgs,
null
)?.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
while (it.moveToNext()) {
try {
val contentUri: Uri = ContentUris.withAppendedId(
uriExternal,
it.getLong(idColumn)
) /*Use this URI for next*/
} catch (e: Exception) {
e.printStackTrace()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
使用块获取 Android11 中的文件共享存储并使用它