【问题标题】:java.io.FileNotFoundException: (Permission denied) Only in Oreojava.io.FileNotFoundException:(权限被拒绝)仅在奥利奥
【发布时间】:2019-02-26 00:59:52
【问题描述】:

我正在将照片下载到智能手机。对于低于奥利奥的版本,没有问题。但是对于奥利奥,我的代码不起作用。我在模拟器中试过这段代码:

我实现了一个将图像保存到外部存储的功能。

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 库的权限。

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

我使用异步任务保存图像

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

当我查看我的应用程序的设置 --> 应用程序时,我可以看到已授予存储权限。但图像未正确保存。事实上图像被保存,但它们都是像这样的绿色正方形。

因此,尽管授予了权限,但它给出了权限被拒绝错误。

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卡吗?
  • 是的,它适用于较低版本的模拟器,但对于 oreo 不起作用。
  • 你不能像那样访问存储卡...你需要使用称为 DocumentTree 的东西
  • 我可以在低于 oreo 的版本中访问。 5.0 或 6.0 模拟器没问题
  • 是的,因为从奥利奥引入它是出于安全原因。

标签: java android permissions android-permissions android-8.0-oreo


【解决方案1】:

访问 SD 卡的文件

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

告知用户如何在对话框中选择sd-card。 (附图片或gif动画)

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

onActivityResult 上,您将拥有选定的目录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;
     }
  }

现在必须检查用户是否,

一个。选择了sd卡

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


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

//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


编辑 SD 卡的文件

要编辑 sd 卡上的现有图像,如果您想调用另一个应用程序为您执行此操作,则不需要上述任何步骤。

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

在您的 editButton 点击​​事件上:

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 的方法:

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-card,因为在这个版本的 Android 上DocumentProvider不适用,因此我们没有机会通过这种方法访问 sd-card . 查看DocumentProvider 的 API 级别 https://developer.android.com/reference/android/provider/DocumentsProvider.html
我找不到任何适用于 Android KitKat(4.4) 的东西。如果您发现任何对 KitKat 有用的信息,请与我们分享。

在低于 KitKat 的版本中,操作系统已经提供了对 sd 卡的访问权限。

【讨论】:

    猜你喜欢
    • 2010-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-30
    • 1970-01-01
    • 2018-08-28
    相关资源
    最近更新 更多