Android 11作用域存储权限

117

我的应用程序使用Environment.getExternalStorageDirectory()提供的图像文件路径创建相册,但是在Android 11中我将无法直接访问文件。

根据Android开发者文档,他们最近引入了MANAGE_EXTERNAL_STORAGE权限,但我不确定添加此权限是否能够继续通过Environment访问文件。

我在Android 11虚拟设备上尝试过我的应用程序,似乎即使没有请求MANAGE_EXTERNAL_STORAGE权限也可以完美地运行!

根据Android Developers的文档,似乎仅使用File API访问照片和媒体库位置的应用程序仍然可以继续工作,但是我不确定

是否有人更好地理解了Android文档?


2
@blackapps 目前还没有 Android 11 的模拟器或手机。 - Numan Karaaslan
1
哦,是的。已经有几个月了。Android Studio为所有Pixel模拟器提供了此功能。更新您的SDK。在Android 11上,默认情况下再次可以访问外部存储。 - blackapps
小心:它在模拟器上可以运行,但在实际设备上可能会出问题!非常危险。 - Violet Giraffe
请查看以下链接:https://stackoverflow.com/a/66534787/3904109,https://dev59.com/ZbXna4cB1Zd3GeqPEAqF#62601219和https://dev59.com/LFMI5IYBdhLWcg3wFXWg#57116787,不要使用管理外部存储权限。 - DragonFire
16个回答

159

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 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;
    }
}

以下方法可用于在 Android 11 或更低版本中请求权限

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);
    }
}

处理Android 11或以上版本的权限回调

@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();
            }
        }
    }
}

处理 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时,您必须证明使用此权限的原因。


4
当我提交应用商店时,出现如下提示:您的 APK 或 Android 应用程序包请求 'android.permission.MANAGE_EXTERNAL_STORAGE' 权限,但此权限当前尚未得到 Google Play 的支持。 - DragonFire
68
实施了这个解决方案后,谷歌拒绝了我的应用。 - Yogesh Nikam Patil
6
应用被Play商店拒绝,需要采取措施:您的应用不符合Google Play政策。 - MohanRaj S
7
应用被谷歌商店拒绝时的不良答复。 - famfamfam
3
这是一项风险较高的权限,因为Google只允许某些应用程序使用该权限,如文件管理器、杀毒软件等。请仔细阅读政策规定。 - Thoriya Prahalad
显示剩余27条评论

35

Android 11不允许直接从存储中访问文件,您必须从存储中选择文件并将该文件复制到您的应用程序包缓存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();
}

2
从Playstore获取应用程序时,是否需要“管理所有文件”权限? - Noor Hossain
将 Android:maxSdkVersion 28 更改为 29 来更新用户权限。 - Sonu Kumar
1
好的,所有代码都运行得很完美,但是当我获得了所有文件的访问权限后,我该如何读取/写入/删除这些文件呢? - Phantom Lord
1
copyFileToInternalStorage方法中Uri的值是多少? - Md Mohsin
2
我收到了一个错误信息:“Attempt to invoke interface method 'int android.database.Cursor.getColumnIndex(java.lang.String)' on a null object reference”。 - famfamfam
显示剩余2条评论

21

Android 11 Scoped Storage更新的评论在此处

快速解决方案在这里

如果您将android目标和编译sdk版本设置为29,则您的应用程序将在android 11上运行,与您在android ten上执行的相同实现

gradle is here

在清单文件中

in mainfest

当您将android设备从api 10(29)更新到android 11(30)api时,无法从设备存储或移动目录检索数据,我今天在Play商店上检查了成千上万个应用程序,它们在Android 11上无法工作,因为Android 11引入了新的作用域存储更新,您必须使用MediaStore对象实现新的方法来获取媒体文件

阅读Android文档后我想要分享的一些有用信息如下:

在Android 11中,您只能访问自己特定应用程序的缓存。

应用程序无法在外部存储上创建自己的应用程序特定目录。要访问系统为您的应用程序提供的目录,请调用getExternalFilesDirs()

如果您的应用程序针对Android 11,则无法访问任何其他应用程序的数据目录中的文件,即使其他应用程序针对Android 8.1(API级别27)或更低版本,并且已使其数据目录中的文件可读取

在Android 11上,应用程序不再可以访问外部存储器中任何其他应用程序专用的应用程序特定目录中的文件。

在Android 11上运行但目标为Android 10(API级别29)的应用程序仍然可以请求requestLegacyExternalStorage属性。此标志允许应用程序暂时退出与作用域存储相关的更改,例如授予对不同目录和不同类型的媒体文件的访问权限。将应用程序更新为针对Android 11后,系统将忽略requestLegacyExternalStorage标志。

在android 10之前,我们使用的是

 android:requestLegacyExternalStorage="true"
    tools:targetApi="q"

在Android 11中,应用属性清单中的此方法已经失效。

因此,请立即迁移到新的更新版本。谢谢。

请查看此处有关作用域存储更新的信息

请在此处遵循教程指南 Follow the Scoped Storage tutorial at GitHub


1
如果从SDK 29升级到30,requestLegacyExternalStorage被忽略了。那么我能解决它吗? - JackHuynh
如果您的应用程序针对API 30,则有两种解决方案。第一种是您不能使用requestlegacyExternalstorage权限,但您仍然可以在Android 29上使用它,并且您的应用程序也可以在API 30上运行。第二种解决方案是使用Media Store API并管理请求权限,同时删除requestlegacyexternal权限。 - Najaf Ali
非常感谢,你的解决方案真是帮了我大忙。 - C_compnay
如果我们将最大SDK设置为29,那么使用新手机的用户是否能在Play商店中看到该应用程序? - Raii
3
现在这不是一个有效的答案,我们必须使用更新的targetSdkVersion才能将应用程序发布到Play商店,至少为31。请查看文档 - https://developer.android.com/google/play/requirements/target-sdk - Aditya Nandardhane

20
根据Android开发者文档,他们最近引入了MANAGE_EXTERNAL_STORAGE权限,但我不确定是否添加此权限后仍能通过Environment访问文件。
是的,你可以。但请记住,如果您打算在Play商店(或其他地方)分发应用程序,则需要为请求该权限的原因进行说明。因此,除非您有非常好的理由使用MANAGE_EXTERNAL_STORAGE,请使用其他内容

2
@blackapps:“添加到清单不起作用”-添加什么?如果您指的是MANAGE_EXTERNAL_STORAGE,上次我在Pixel 2上尝试时它可以工作。“在应用程序设置中无法完成任何操作”-当启动Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION时,对我而言可以执行操作。请参阅https://gitlab.com/commonsguy/cw-android-r/-/tree/v0.3/RawPaths。“令我惊讶的是,默认情况下具有外部存储器的读取访问权限,这在29版本上并非如此”-那是“原始路径”。大多数外部存储都被涵盖,但并非全部。 - CommonsWare
@CommonsWare 我有一个问题。我想编写一个文档阅读器程序,它必须能够在任何地方读取任何文档,就像杀毒软件一样,用户可能在 WhatsApp 目录中拥有一个文档,并希望在我的应用程序中阅读它。这是否需要或者正当所有文件访问权限? - Numan Karaaslan
@NumanKaraaslan: 我不能回答这个问题,因为我不是谷歌公司负责这些决定的员工。话虽如此,虽然可能认为它“必须能够读取任何地方的任何文档”,但Google可能会有不同的看法。但是,谷歌的规则只适用于Play商店,因此您可以通过其他方式(例如,如果它是开源的,则可以通过F-Droid)分发应用程序。 - CommonsWare
@CommonsWare File createFile = new File(getApplicationContext().getExternalFilesDir(null)+File.separator+"My App"); 应用程序专用存储(私有存储)需要WRITE_EXTERNAL_STORAGE权限吗? - M DEV
1
@MDev:不需要权限即可使用Context方法提供的目录,例如getExternalFilesDir() - CommonsWare
显示剩余2条评论

15

我找到了适用于Android 11 (SDK R - 30)的方法:

1- 在清单文件中必须添加此权限:(仅适用于R)

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

2- 请求操作系统对话框以请求许可:

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);

在此输入图片描述

在此输入图片描述


4
当我提交应用商店时,我收到以下提示信息:“您的APK或Android App Bundle请求'android.permission.MANAGE_EXTERNAL_STORAGE'权限,Google Play目前不支持此权限。” - DragonFire
2
@DragonFire,是的,你说得对。谷歌提到这个权限将在5月5日后可用。 - Mori
2
那么...现在是2021年11月。谷歌支持它吗?找不到相关信息。 - Akito
@Akito 请查看此页面:https://developer.android.com/training/data-storage/manage-all-files#all-files-access-google-play - Mori
2
在 Android 10 及以上版本中,除非应用程序是文件管理器或杀毒软件,否则我们无法在 Play Store 应用提交中使用 MANAGE_EXTERNAL_STORAGE 权限。谷歌将拒绝您的应用程序。 - Pawan Jain
显示剩余5条评论

7

注意:此回答不需要MANAGE_EXTERNAL_STORAGE权限

在安卓10及以上版本中,MANAGE_EXTERNAL_STORAGE权限不能用于Play Store应用程序,除非它是文件管理器或杀毒软件,否则它几乎没有任何用处。

因此,为了在没有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)
}

1
你要把实际文件信息放在哪里,比如如果我用createfile创建一个.txt文件? - J. M.
1
@J. M. 你需要重写onActivityResult()方法。在这个方法中,你可以编写文件的内容。 - Saurabh Dhage

4

MANAGE_EXTERNAL_STORAGE是一个严格的权限,只应该用于合法目的,例如文件管理器和防病毒应用程序。请参见用途

我建议您使用这个库来更简单地访问受限存储,而无需获得完整磁盘权限(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()等。

你能找到文件夹中的所有文件吗? - J. M.
是的,我可以列出授予文件夹下的文件。@J.M. - Anggrayudi H
你能否提供一个示例?这是我更新我的应用程序以便写入Android 11所需的全部内容。我只是无法弄清楚文件夹URI是否为空并读取文件列表。 - J. M.
这是我的问题 https://stackoverflow.com/questions/70979344/how-can-i-check-if-treeuri-is-empty?noredirect=1#comment1 - J. M.

4

在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文件夹中,其中包含有关图片的以下内容:

<paths xmlns:android="http://schemas.android.com/apk/res/android">

<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",
};

就这样,我们就可以拥有一张图片,并将其重命名到我们自己的目录中,假设包名被授予所有文件访问权限!


1
我在Android 30级上进行了测试,它可以正常工作,但是如果没有请求传统存储,则Android 29仍然会出现问题... - KoalaKoalified
当我提交到Playstore时,我收到以下提示:您的APK或Android App Bundle请求'android.permission.MANAGE_EXTERNAL_STORAGE'权限,而Google Play尚不支持此权限。 - DragonFire
@Vishalkumarsinghvi 将目标从30降级到29...这样就可以移除30的代码了...然后等待Google的批准。 - DragonFire
如果我们的应用程序目标是29,我们仍然可以使用READ和WRITE_EXTERNAL_STORAGE权限吗?据我所知,这些权限应该仅替换为MANAGE_EXTERNAL_STORAGE,适用于目标为30的应用程序。因此,在谷歌允许推送目标为29的应用程序之前,我们仍然可以使用它们吗? - Максим Петлюк
当Google允许新的API级别30(即5月5日推出)时,此代码将完全升级您的应用程序。在那之前,请使用请求遗留标志转到API级别29。 - KoalaKoalified
显示剩余8条评论

2

如果您想从设备上读写文件,可以基本上使用Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)(不一定非得是DIRECTORY DOCUMENTS),而不是Environment.getExternalStorageDirectory(),您不需要请求MANAGE_EXTERNAL_STORAGE权限。在Android 11上以这种方式正常工作。


非常好,谢谢。 - Dave
这对我目前来说是有效的,但是 Environment.getExternalStoragePublicDirectory 在 API 级别 29 中已被弃用。 - sidetrail
非常感谢。我原本以为上传 .txt 文件需要 MANAGE_EXTERNAL_STORAGE 权限,但是你的回答帮助我成功地将 .txt 文件上传到了服务器上,我在其中保存了日志信息。 - Lathesh

1

我已经解决了这个问题 -

要做的事情:

  1. 保存在外部目录中,因为这有助于在SDK版本30或更高版本中读取。
  2. 添加'//' + 目录路径,您的问题将得到解决。这意味着您的路径将是'//'+ getExternalStorageDirectory())!.path
  3. 在清单中添加读写权限-以访问媒体文件

不要使用这个,因为您的应用程序将无法被Play商店接受。

  1. <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'], text: '你好'),);

使用这个//可以访问WhatsApp状态吗? - Vivek
如果你想在Play商店上架应用,这只是一个不完整的解决方案:https://support.google.com/googleplay/android-developer/answer/10467955?hl=en - Al Ro
请提供Java的相同内容。您的应用程序是否在Play商店上被接受? - Kamleshwer Purohit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接