No such file or Directory only in Android 6.0

2019-03-06 14:18发布

问题:

Below code is working fine on pre-Marshmallow devices but not in Marshmallow.

These are the permissions in Manifest

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

Here is the code

public void saveImageToSDCard(Bitmap bitmap) {
    File myDir = new File(
            Environment
                    .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            pref.getGalleryName());

    myDir.mkdirs();
    Random generator = new Random();
    int n = 10000;
    n = generator.nextInt(n);
    String fname = "Wallpaper-" + n + ".jpg";
    File file = new File(myDir, fname);
    if (file.exists())
        file.delete();
    try {
        FileOutputStream out = new FileOutputStream(file);
        bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
        out.flush();
        out.close();
        Uri uri = getImageContentUri(_context,file);

        Log.d(TAG, "Wallpaper saved to: " + file.getAbsolutePath());

    } catch (Exception e) {
        e.printStackTrace();
    }
}

And the same code works when I manually allow the storage permission

Here is the solution given by Nitesh Pareek.

private boolean hasPermissions(Context context, String[] permissions) {
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) {
        for (String permission : permissions) {
            if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
    }
    return true;
}
String[] PERMISSIONS = new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE};

    if (!hasPermissions(this, PERMISSIONS)) {
        ActivityCompat.requestPermissions(this, PERMISSIONS, 11);
        return;
    }

回答1:

Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app.

This is why it works in pre-lolipop versions, and doesn't on API 23. Permissions in Android Manifest alone are not enough, you need to add them at runtime as well. Refer here for more details.



回答2:

give read write permissions on run time for marshmallow or newer version. Do like below:-

String[] PERMISSIONS = new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE};

if (!hasPermissions(this, PERMISSIONS)) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, 11);
            return;
        }

private boolean hasPermissions(Context context, String... permissions) {
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) {
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
        }
        return true;
    }


回答3:

You need to take application permissions at runtime instead of taking when install/update as convention

Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. This approach streamlines the app install process, since the user does not need to grant permissions when they install or update the app

For more help: Requesting Permissions at Run Time

By focusing on the documentation and after doing some google searches, finally I have compiled the code below to handle runtime permissions efficiently

To make it work, you need to follow the instructions below:

Call this method to check if storage permission is granted by user? If not, then you need to request for it

public static boolean isStoragePermissionGranted(Activity activity) {
    boolean flag = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        flag = activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }
    return flag;
}

Call this method to request storage permission

public static void requestStoragePermission(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (isStoragePermissionGranted(activity)) {
            return;
        }

        // Fire off an async request to actually get the permission
        // This will show the standard permission request dialog UI
        activity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                REQUEST_CODE_STORAGE_PERMISSION);
    }
}

Implement this method in your activity to handle response of permission callback

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

    switch (requestCode) {
        case REQUEST_CODE_STORAGE_PERMISSION:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (grantResults.length > 0) {
                    if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
                        boolean shouldShowRationale = shouldShowRequestPermissionRationale(permissions[0]);
                        if (!shouldShowRationale) {
                            // user denied flagging NEVER ASK AGAIN, you can either enable some fall back,
                            // disable features of your app or open another dialog explaining again the permission and directing to
                            // the app setting
                            dialogReasonStoragePermissionToSettings(this);
                        } else if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permissions[0])) {
                            // user denied WITHOUT never ask again, this is a good place to explain the user
                            // why you need the permission and ask if he want to accept it (the rationale)
                            dialogReasonStoragePermission(this);
                        }
                    } /*else {
                        // Do on permission granted work here
                    }*/
                }
            }

            break;
    }
}

public static void dialogReasonStoragePermission(final Activity activity) {
    AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    builder.setMessage(activity.getString(R.string.reason_storage_permission));
    builder.setCancelable(false);
    builder.setPositiveButton("Retry", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            requestStoragePermission(activity);
        }
    });
    builder.setNegativeButton("Dismiss", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            dialog.dismiss();
        }
    });

    AlertDialog dialog = builder.create();
    dialog.show();
}

public static void dialogReasonStoragePermissionToSettings(final Activity activity) {
    AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    builder.setMessage(activity.getString(R.string.reason_storage_permission));
    builder.setCancelable(false);
    builder.setPositiveButton("Go to Settings", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            goToAppDetailsForPermissionSettings(activity);
        }
    });
    builder.setNegativeButton("Dismiss", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            dialog.dismiss();
        }
    });

    AlertDialog dialog = builder.create();
    dialog.show();
}

private static final int REQUEST_CODE_APP_DETAILS_PERMISSION_SETTING = 3995;
private static void goToAppDetailsForPermissionSettings(Activity activity) {
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
    intent.setData(uri);
    activity.startActivityForResult(intent, REQUEST_CODE_APP_DETAILS_PERMISSION_SETTING);
}


回答4:

I am not providing you direct code for this but here is a reason API level 23 introduce a new Permission structure for more security below is a short but wast description of thing, in documentation here

Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. This approach streamlines the app install process, since the user does not need to grant permissions when they install or update the app. It also gives the user more control over the app's functionality; for example, a user could choose to give a camera app access to the camera but not to the device location. The user can revoke the permissions at any time, by going to the app's Settings screen.

Code is good just you have to put something additional and that is Runtime Permissions for storage.

Read this blog to know everything from deep inside about Runtime Permissions gave me a clear picture about it, hope it helps you too.

Thanks