java.io.FileNotFoundException:(权限被拒绝)仅在Oreo中 [英] java.io.FileNotFoundException: (Permission denied) Only in Oreo

查看:125
本文介绍了java.io.FileNotFoundException:(权限被拒绝)仅在Oreo中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将照片下载到智能手机.对于低于Oreo的版本,没有问题.但是对于Oreo,我的代码无法正常工作.我在模拟器中尝试了以下代码:

I am downloading photos to smartphone. For versions lower than Oreo, there's no problem. But for Oreo, my code isn't not working. I tried this code in Emulator:

我实现了将图像保存到外部存储器的功能.

I implemented a function to save an image to external storage.

private void saveImageToExternalStorage(Bitmap finalBitmap,String name) {
    String root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
    File myDir = new File(root + "/xx");
    myDir.mkdirs();
    String fname = name + ".jpg";
    File file = new File(myDir, fname);
    if (file.exists())
        file.delete();
    try {
        FileOutputStream out = new FileOutputStream(file);
        finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
        out.flush();
        out.close();
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    // Tell the media scanner about the new file so that it is
    // immediately available to the user.
    MediaScannerConnection.scanFile(this, new String[] { file.toString() }, null,
            new MediaScannerConnection.OnScanCompletedListener() {
                public void onScanCompleted(String path, Uri uri) {
                    Log.i("ExternalStorage", "Scanned " + path + ":");
                    Log.i("ExternalStorage", "-> uri=" + uri);
                }
            });

}

我正在请求使用dexter库的权限.

I am requesting permissions with dexter library.

Dexter.withActivity(MainActivity.this)
        .withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
        .withListener(new PermissionListener() {
                          @Override
                          public void onPermissionGranted(PermissionGrantedResponse response) {
                              SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
                              if (!prefs.getBoolean("firstTime", false)) {
                                  task.execute();
                                  SharedPreferences.Editor editor = prefs.edit();
                                  editor.putBoolean("firstTime", true);
                                  editor.commit();
                              }
                          }

                      @Override
                      public void onPermissionDenied(PermissionDeniedResponse response) {
                          Toast.makeText(MainActivity.this, "You need to allow permission if you want to use camera", Toast.LENGTH_LONG).show();

                      }

                      @Override
                      public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
                          token.continuePermissionRequest();
                          Toast.makeText(MainActivity.this, "You need to allow permission if you want to use camera", Toast.LENGTH_LONG).show();

                      }
                  }).check();

我使用asynctask保存图像

I save images with asynctask

final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
        private ProgressDialog dialog;

        @Override
        protected void onPreExecute()
        {
            this.dialog = new ProgressDialog(MainActivity.this);
            this.dialog.setMessage(getString(R.string.newfeature));
            this.dialog.setCancelable(false);
            this.dialog.setOnCancelListener(new DialogInterface.OnCancelListener()
            {
                @Override
                public void onCancel(DialogInterface dialog)
                {
                    // cancel AsyncTask
                    cancel(false);
                }
            });

            this.dialog.show();

        }

        @Override
        protected Void doInBackground(Void... params)
        {
            // do your stuff


            Bitmap myBitmap2 = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.im2);
            saveImageToExternalStorage(myBitmap2,"imag2");
            myBitmap2.recycle();


            return null;
        }


        @Override
        protected void onPostExecute(Void result)
        {
            //called on ui thread
            if (this.dialog != null) {
                this.dialog.dismiss();
            }
        }

        @Override
        protected void onCancelled()
        {
            //called on ui thread
            if (this.dialog != null) {
                this.dialog.dismiss();
            }
        }
    };

当我查看我的应用程序的设置->应用程序时,可以看到已授予存储权限.但是图像保存不正确.实际上,图像已保存,但所有图像都是绿色正方形,如下所示.

I can see Storage permission is granted when I look Settings --> Apps for my app. But images are not saved correctly. In fact images are saved but all of them is green square like this.

结果,尽管授予了权限,但它给出了拒绝权限错误.

As a result, it gives permission denied error although permission is granted.

09-21 13:11:08.023 17636-17765/xx.xx W/System.err: java.io.FileNotFoundException: /storage/emulated/0/Pictures/xx/imag2.jpg (Permission denied)
09-21 13:11:08.024 17636-17765/xx.xx W/System.err:     at java.io.FileOutputStream.open0(Native Method)
09-21 13:11:08.024 17636-17765/xx.xx W/System.err:     at java.io.FileOutputStream.open(FileOutputStream.java:308)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at java.io.FileOutputStream.<init>(FileOutputStream.java:238)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at java.io.FileOutputStream.<init>(FileOutputStream.java:180)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at xx.xx.MainActivity.saveImageToExternalStorage(MainActivity.java:804)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at xx.xx.MainActivity.access$000(MainActivity.java:62)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at xx.xx.MainActivity$1.doInBackground(MainActivity.java:119)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at xx.xx.MainActivity$1.doInBackground(MainActivity.java:89)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at android.os.AsyncTask$2.call(AsyncTask.java:333)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err:     at java.lang.Thread.run(Thread.java:764)

推荐答案

访问SD卡的文件

使用DOCUMENT_TREE对话框获取sd卡的Uri.

Use DOCUMENT_TREE dialog to get sd-card's Uri.

在对话框上通知用户如何选择sd-card. (带有图片或gif动画)

Inform user about how to choose sd-card on the dialog. (with pictures or gif animations)

// call for document tree dialog
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE);

onActivityResult上,您将具有选定的目录Uri. (sdCardUri)

On onActivityResult you'll have the selected directory Uri. (sdCardUri)

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case REQUEST_CODE_OPEN_DOCUMENT_TREE:
            if (resultCode == Activity.RESULT_OK) {
                sdCardUri = data.getData();
             }
             break;
     }
  }

现在必须检查用户,

a.选择了SD卡

b.选择了文件所在的sd卡(某些设备可能有多个sd卡).

b. selected the sd-card that our file is on (some devices could have multiple sd-cards).

我们通过从sd根目录到文件的层次结构查找文件来检查a和b.如果找到文件,则同时获取a和b条件.

We check both a and b by finding the file through the hierarchy, from sd root to our file. If file found, both of a and b conditions are acquired.

//First we get `DocumentFile` from the `TreeUri` which in our case is `sdCardUri`.
DocumentFile documentFile = DocumentFile.fromTreeUri(this, sdCardUri);

//Then we split file path into array of strings.
//ex: parts:{"", "storage", "extSdCard", "MyFolder", "MyFolder", "myImage.jpg"}
// There is a reason for having two similar names "MyFolder" in 
//my exmple file path to show you similarity in names in a path will not 
//distract our hiarchy search that is provided below.
String[] parts = (file.getPath()).split("\\/");

// findFile method will search documentFile for the first file 
// with the expected `DisplayName`

// We skip first three items because we are already on it.(sdCardUri = /storage/extSdCard)
for (int i = 3; i < parts.length; i++) {
    if (documentFile != null) {
        documentFile = documentFile.findFile(parts[i]);
    }
  }

if (documentFile == null) {

    // File not found on tree search
    // User selected a wrong directory as the sd-card
    // Here must inform user about how to get the correct sd-card
    // and invoke file chooser dialog again.

 } else {

    // File found on sd-card and it is a correct sd-card directory
    // save this path as a root for sd-card on your database(SQLite, XML, txt,...)

    // Now do whatever you like to do with documentFile.
    // Here I do deletion to provide an example.


    if (documentFile.delete()) {// if delete file succeed 
        // Remove information related to your media from ContentResolver,
        // which documentFile.delete() didn't do the trick for me. 
        // Must do it otherwise you will end up with showing an empty
        // ImageView if you are getting your URLs from MediaStore.
        // 
        Uri mediaContentUri = ContentUris.withAppendedId(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                longMediaId);
        getContentResolver().delete(mediaContentUri , null, null);
    }


 }

注意:

您必须为清单中的外部存储提供访问权限,并且必须为应用程序中的os> = Marshmallow提供访问权限. https://stackoverflow.com/a/32175771/2123400

You must provide access permission to the external storage inside your manifest and for os>=Marshmallow inside the app. https://stackoverflow.com/a/32175771/2123400

编辑SD卡的文件

要编辑sd卡上的现有图像,如果要调用其他应用程序来执行此操作,则无需执行上述任何步骤.

For editing an existing image on your sd-card you don't need any of above steps if you want to invoke another app to do it for you.

在这里,我们可以调用所有活动(从所有已安装的应用程序中)并具有编辑图像的功能. (程序员在清单中标记了他们的应用,因为它具有提供其他应用(活动)的可访问性的功能.

Here we invoke all the activities (from all the installed apps) with the capability of editing the images. (Programmers mark their apps in the manifest for its capabilities to provide accessibility from other apps (activities)).

在您的editButton单击事件上:

on your editButton click event:

String mimeType = getMimeTypeFromMediaContentUri(mediaContentUri);
startActivityForResult(Intent.createChooser(new Intent(Intent.ACTION_EDIT).setDataAndType(mediaContentUri, mimeType).putExtra(Intent.EXTRA_STREAM, mediaContentUri).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION), "Edit"), REQUEST_CODE_SHARE_EDIT_SET_AS_INTENT);

这是获取mimeType的方法:

and this is how to get mimeType:

public String getMimeTypeFromMediaContentUri(Uri uri) {
    String mimeType;
    if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
        ContentResolver cr = getContentResolver();
        mimeType = cr.getType(uri);
    } else {
        String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
                .toString());
        mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
                fileExtension.toLowerCase());
    }
    return mimeType;
}

注意:

在Android KitKat(4.4)上,不要要求用户选择sd卡,因为在此版本的Android DocumentProvider上不适用,因此我们没有机会通过这种方法访问sd卡. 查看DocumentProvider的API级别 https://developer.android.com/reference/android/provider/DocumentsProvider. html
我找不到适用于Android KitKat(4.4)的任何东西.如果您发现KitKat有用的东西,请与我们分享.

On Android KitKat(4.4) don't ask the user to select the sd-card because on this version of Android DocumentProvideris not applicable, hence we have no chance to have access to the sd-card with this approach. Look at the API level for the DocumentProvider https://developer.android.com/reference/android/provider/DocumentsProvider.html
I couldn't find anything that works on Android KitKat(4.4). If you found anything useful with KitKat please share with us.

在低于KitKat的版本中,操作系统已提供对SD卡的访问权限.

On versions below the KitKat access to sd-card is already provided by the OS.

这篇关于java.io.FileNotFoundException:(权限被拒绝)仅在Oreo中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆