安卓6.0系统中存储权限错误

187
在Lollipop中,我的应用程序的下载功能运行良好,但当我升级到Marshmallow时,当我尝试从互联网下载到SD卡时,我的应用程序会崩溃并出现以下错误:
Neither user  nor current process has android.permission.WRITE_EXTERNAL_STORAGE

它抱怨这行代码:

DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);

我在应用程序外的清单中拥有权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

我清理并重新构建了项目,但仍然崩溃。


尝试一下这个,它可能会对你有所帮助:-https://dev59.com/J1wX5IYBdhLWcg3w-Thv#41221852 - Bipin Bharti
我已经准备了一个库,可以帮助轻松处理运行时权限。https://github.com/nabinbhandari/Android-Permissions - Nabin Bhandari
12个回答

390

您应该使用以下代码检查用户是否已授予外部存储权限:

if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
    Log.v(TAG,"Permission is granted");
    //File write logic here
    return true;
}

如果没有,您需要请求用户授予您的应用权限:

ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);

当然,这些只适用于棉花糖设备,所以您需要检查您的应用程序是否在棉花糖上运行:

 if (Build.VERSION.SDK_INT >= 23) {
      //do your check here
 }

确保您的活动也实现了 OnRequestPermissionResult

整个权限看起来像这样:

public  boolean isStoragePermissionGranted() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                == PackageManager.PERMISSION_GRANTED) {
            Log.v(TAG,"Permission is granted");
            return true;
        } else {

            Log.v(TAG,"Permission is revoked");
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
            return false;
        }
    }
    else { //permission is automatically granted on sdk<23 upon installation
        Log.v(TAG,"Permission is granted");
        return true;
    }
}

权限结果回调:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
        Log.v(TAG,"Permission: "+permissions[0]+ "was "+grantResults[0]);
        //resume tasks needing this permission
    }
}

2
@Houssem 很高兴能帮忙! - MetaSnarf
22
这在API版本大于等于23时无效,请求Manifest.permission.WRITE_EXTERNAL_STORAGE权限总是返回"-1"即被拒绝的状态。请查看新文档,它指出从API 19开始,您不需要WRITE_EXTERNAL_STORAGE权限。正如上面一些评论所提到的,您应该使用Manifest.permission.READ_EXTERNAL_STORAGE来完成所有操作。 - AmeyaB
3
@VSB:虽然它在一个奇怪的位置,但是这里是链接:https://developer.android.com/guide/topics/manifest/uses-permission-element.html。 - AmeyaB
3
根据文档所述,对于API>=19,如果应用程序将使用由getExternalFilesDir()返回的外部存储器上的自己特定的目录,则不需要声明写入外部存储器的权限。在其他情况下,仍然必须在清单中声明android.permission.WRITE_EXTERNAL_STORAGE权限。 - VSB
1
是的,问题已经解决了。问题与Hockey App库有关。该库一直在覆盖我的写入权限。@MetaSnarf - Selin
显示剩余13条评论

41
安卓的权限系统一直以来都是最大的安全关注点之一,因为这些权限在安装时就被要求了。一旦安装,应用程序将能够访问所有已���权的内容,而不需要任何用户的确认应用程序对权限做了什么。

Android 6.0 Marshmallow 引入了针对权限模型最大的更改之一——运行时权限,这是一个新的权限模型,在您的目标 API 是 23 的情况下替换现有的安装时间权限模型,并且应用正在 Android 6.0+ 设备上运行。

感谢 在运行时请求权限

示例

将此声明为全局变量

private static final int PERMISSION_REQUEST_CODE = 1;

请在您的onCreate()部分添加以下内容:

setContentView(R.layout.your_xml);之后

 if (Build.VERSION.SDK_INT >= 23)
    {
        if (checkPermission())
        {
            // Code for above or equal 23 API Oriented Device 
            // Your Permission granted already .Do next code
        } else {
            requestPermission(); // Code for permission
        }
    }
  else
    {

       // Code for Below 23 API Oriented Device 
       // Do next code
    }

现在添加了 checkPermission()requestPermission()
 private boolean checkPermission() {
    int result = ContextCompat.checkSelfPermission(Your_Activity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
    if (result == PackageManager.PERMISSION_GRANTED) {
        return true;
    } else {
        return false;
    }
}

private void requestPermission() {

    if (ActivityCompat.shouldShowRequestPermissionRationale(Your_Activity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        Toast.makeText(Your_Activity.this, "Write External Storage permission allows us to do store images. Please allow this permission in App Settings.", Toast.LENGTH_LONG).show();
    } else {
        ActivityCompat.requestPermissions(Your_Activity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case PERMISSION_REQUEST_CODE:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.e("value", "Permission Granted, Now you can use local drive .");
            } else {
                Log.e("value", "Permission Denied, You cannot use local drive .");
            }
            break;
    }
}

提示

onRequestPermissionsResult

该接口是用于接收权限请求结果的契约。


30

在API 23级中检查多个权限 步骤1:

 String[] permissions = new String[]{
        Manifest.permission.INTERNET,
        Manifest.permission.READ_PHONE_STATE,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.VIBRATE,
        Manifest.permission.RECORD_AUDIO,
};

第二步:

 private boolean checkPermissions() {
    int result;
    List<String> listPermissionsNeeded = new ArrayList<>();
    for (String p : permissions) {
        result = ContextCompat.checkSelfPermission(this, p);
        if (result != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(p);
        }
    }
    if (!listPermissionsNeeded.isEmpty()) {
        ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]), 100);
        return false;
    }
    return true;
}

第三步:

 @Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    if (requestCode == 100) {
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // do something
        }
        return;
    }
}

第四步:在Activity的onCreate中检查权限,调用checkPermissions()函数。


2
谢谢。这看起来是一个有前途的多权限答案。但是它在示例方面有些不足。它如何处理“永不再询问”和缺少一些权限?用户会得到任何反馈吗?很想看到一个真实的例子或更多关于您代码片段的评论。 - not2qubit
为了处理“不再询问”,请将“添加权限”if块包围在if(shouldshowpermissionrationale()){}中... 如果需要权限且未永久解除,则为true。 - me_
这个应该是正确且优化的答案。 - VikaS GuttE

21

除非确实需要在外部存储器上写作,否则您始终可以选择将文件保存在应用目录中。在我的情况下,我必须保存文件,经过2到3天的浪费后,我发现如果更改存储路径为

Environment.getExternalStorageDirectory()
getApplicationContext().getFilesDir().getPath() //which returns the internal app files directory path

它在所有设备上都能完美运行。这是因为如果要在外部存储器上写入数据,则需要额外的权限,但在内部应用程序目录中写入数据就很简单。


你在哪里写这段代码的?我记不得在我的代码中使用过Environment.getExternalStorageDirectory()。 - royjavelosa
请记住,使用这种方法时,如果用户卸载应用程序,文件也将被删除。有时,例如对于照片,即使卸载应用程序,用户也可能希望文件保留在设备上。 - shagberg

5

2
实际上,这个回答中的“应用程序信息-> 权限”解决了崩溃问题 :),但我接受了其他答案,因为它提供了详细的操作步骤。我希望我可以接受两个答案。非常感谢! - fullmoon
感谢您解决了在 Android 模拟器上下载时浏览器崩溃的问题。 - sdaffa23fdsf

2
我发现最简单的方法是:
private boolean checkPermissions(){
    if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
        return true;
    }
    else {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_CODE);
        return false;
    }
}

2

从棉花糖版本开始,开发人员需要向用户请求运行时权限。

让我给你整个请求运行时权限的过程。

我使用了这里的参考:marshmallow runtime permissions android

首先创建一个方法来检查是否已经授予所有权限。

private  boolean checkAndRequestPermissions() {
        int camerapermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
        int writepermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
        int permissionLocation = ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION);
        int permissionRecordAudio = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);


        List<String> listPermissionsNeeded = new ArrayList<>();

        if (camerapermission != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(Manifest.permission.CAMERA);
        }
        if (writepermission != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (permissionLocation != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (permissionRecordAudio != PackageManager.PERMISSION_GRANTED) {
            listPermissionsNeeded.add(Manifest.permission.RECORD_AUDIO);
        }
        if (!listPermissionsNeeded.isEmpty()) {
            ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]), REQUEST_ID_MULTIPLE_PERMISSIONS);
            return false;
        }
        return true;
    } 

现在这里是上面方法运行后的代码。我们将重写 onRequestPermissionsResult() 方法:
 @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        Log.d(TAG, "Permission callback called-------");
        switch (requestCode) {
            case REQUEST_ID_MULTIPLE_PERMISSIONS: {

                Map<String, Integer> perms = new HashMap<>();
                // Initialize the map with both permissions
                perms.put(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.RECORD_AUDIO, PackageManager.PERMISSION_GRANTED);
                // Fill with actual results from user
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++)
                        perms.put(permissions[i], grantResults[i]);
                    // Check for both permissions
                    if (perms.get(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
                            && perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED 
&& perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED 
&& perms.get(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
                        Log.d(TAG, "sms & location services permission granted");
                        // process the normal flow
                        Intent i = new Intent(MainActivity.this, WelcomeActivity.class);
                        startActivity(i);
                        finish();
                        //else any one or both the permissions are not granted
                    } else {
                        Log.d(TAG, "Some permissions are not granted ask again ");
                        //permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission
//                        // shouldShowRequestPermissionRationale will return true
                        //show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
                        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) 
|| ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) 
|| ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)
 || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
                            showDialogOK("Service Permissions are required for this app",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            switch (which) {
                                                case DialogInterface.BUTTON_POSITIVE:
                                                    checkAndRequestPermissions();
                                                    break;
                                                case DialogInterface.BUTTON_NEGATIVE:
                                                    // proceed with logic by disabling the related features or quit the app.
                                                    finish();
                                                    break;
                                            }
                                        }
                                    });
                        }
                        //permission is denied (and never ask again is  checked)
                        //shouldShowRequestPermissionRationale will return false
                        else {
                            explain("You need to give some mandatory permissions to continue. Do you want to go to app settings?");
                            //                            //proceed with logic by disabling the related features or quit the app.
                        }
                    }
                }
            }
        }

    }

如果用户点击了拒绝选项,则会使用showDialogOK()方法显示对话框。

如果用户同时点击了拒绝选项和勾选了一个写着“不再询问”的复选框,则会使用explain()方法显示对话框。

显示对话框的方法:

 private void showDialogOK(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", okListener)
                .create()
                .show();
    }
    private void explain(String msg){
        final android.support.v7.app.AlertDialog.Builder dialog = new android.support.v7.app.AlertDialog.Builder(this);
        dialog.setMessage(msg)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        //  permissionsclass.requestPermission(type,code);
                        startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:com.exampledemo.parsaniahardik.marshmallowpermission")));
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        finish();
                    }
                });
        dialog.show();
    }

以上代码片段一次性请求四个权限。根据您的要求,您还可以在任何活动中请求任意数量的权限。请注意,不要删除HTML标记。

1
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            Log.d(TAG, "Permission granted");
        } else {
            ActivityCompat.requestPermissions(getActivity(),
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    100);
        }

        fab.setOnClickListener(v -> {
            Bitmap b = BitmapFactory.decodeResource(getResources(), R.drawable.refer_pic);
            Intent share = new Intent(Intent.ACTION_SEND);
            share.setType("image/*");
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            b.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
            String path = MediaStore.Images.Media.insertImage(requireActivity().getContentResolver(),
                    b, "Title", null);
            Uri imageUri = Uri.parse(path);
            share.putExtra(Intent.EXTRA_STREAM, imageUri);
            share.putExtra(Intent.EXTRA_TEXT, "Here is text");
            startActivity(Intent.createChooser(share, "Share via"));
        });

1
在MainActivity的OnCreate()函数中添加以下代码,这将显示弹出窗口来请求权限:
if (ActivityCompat.shouldShowRequestPermissionRationale(TestActivity.this,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)){
}
else {
     ActivityCompat.requestPermissions(TestActivity.this,
                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            100);
}

0

在开始下载之前,请检查您的运行时权限,如果您没有权限,请使用以下方法请求权限

requestStoragePermission()

private void requestStoragePermission(){
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, 
                android.Manifest.permission.READ_EXTERNAL_STORAGE))
        {

        }

        ActivityCompat.requestPermissions(this, 
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            STORAGE_PERMISSION_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, 
                @NonNull String[] permissions, 
                @NonNull int[] grantResults) {

    if(requestCode == STORAGE_PERMISSION_CODE){
        if(grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
        }
        else{
            Toast.makeText(this,
                           "Oops you just denied the permission", 
                           Toast.LENGTH_LONG).show();
        }
    }
}

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