java.io.FileNotFoundException:(权限被拒绝)仅在奥利奥
java.io.FileNotFoundException: (Permission denied) Only in Oreo
我正在将照片下载到智能手机。对于低于 Oreo 的版本,没有问题。但是对于奥利奥,我的代码并没有起作用。我在模拟器中试过这段代码:
我实现了将图像保存到外部存储器的功能。
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();
我用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();
}
}
};
当我查看我的应用程序的设置 --> 应用程序时,我可以看到已授予存储权限。但是图像没有正确保存。事实上图像被保存,但它们都是这样的绿色方块。
因此,虽然授予了权限,但它给出了权限被拒绝的错误。
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[=14=]0(MainActivity.java:62)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err: at xx.xx.MainActivity.doInBackground(MainActivity.java:119)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err: at xx.xx.MainActivity.doInBackground(MainActivity.java:89)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err: at android.os.AsyncTask.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.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-Card的文件
使用 DOCUMENT_TREE
对话框获取 sd 卡的 Uri
。
告知用户如何在对话框中选择ose 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
上,您将拥有 selected 目录 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;
}
}
现在必须检查用户,
一个。 select编辑 SD 卡
b。 select编辑我们的文件所在的 SD 卡(某些设备可能有多个 SD 卡)。
我们通过层次结构查找文件来检查 a 和 b,从 sd 根目录到我们的文件。如果找到文件,则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 提供访问权限。
编辑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) 上不要要求用户 select sd 卡,因为在此版本的 Android 上 DocumentProvider
不适用,因此我们没有机会使用这种方法访问 SD 卡。
查看 DocumentProvider
的 API 水平
https://developer.android.com/reference/android/provider/DocumentsProvider.html
我找不到任何适用于 Android KitKat(4.4) 的东西。如果您发现任何对 KitKat 有用的信息,请与我们分享。
在 KitKat 以下的版本中,OS 已经提供了对 SD 卡的访问权限。
我正在将照片下载到智能手机。对于低于 Oreo 的版本,没有问题。但是对于奥利奥,我的代码并没有起作用。我在模拟器中试过这段代码:
我实现了将图像保存到外部存储器的功能。
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();
我用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();
}
}
};
当我查看我的应用程序的设置 --> 应用程序时,我可以看到已授予存储权限。但是图像没有正确保存。事实上图像被保存,但它们都是这样的绿色方块。
因此,虽然授予了权限,但它给出了权限被拒绝的错误。
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[=14=]0(MainActivity.java:62)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err: at xx.xx.MainActivity.doInBackground(MainActivity.java:119)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err: at xx.xx.MainActivity.doInBackground(MainActivity.java:89)
09-21 13:11:08.033 17636-17765/xx.xx W/System.err: at android.os.AsyncTask.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.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-Card的文件
使用 DOCUMENT_TREE
对话框获取 sd 卡的 Uri
。
告知用户如何在对话框中选择ose 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
上,您将拥有 selected 目录 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;
}
}
现在必须检查用户,
一个。 select编辑 SD 卡
b。 select编辑我们的文件所在的 SD 卡(某些设备可能有多个 SD 卡)。
我们通过层次结构查找文件来检查 a 和 b,从 sd 根目录到我们的文件。如果找到文件,则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 提供访问权限。
编辑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) 上不要要求用户 select sd 卡,因为在此版本的 Android 上 DocumentProvider
不适用,因此我们没有机会使用这种方法访问 SD 卡。
查看 DocumentProvider
的 API 水平
https://developer.android.com/reference/android/provider/DocumentsProvider.html
我找不到任何适用于 Android KitKat(4.4) 的东西。如果您发现任何对 KitKat 有用的信息,请与我们分享。
在 KitKat 以下的版本中,OS 已经提供了对 SD 卡的访问权限。