已编写权限代码但Android应用不请求存储运行时权限问题
解决Android存储权限请求不弹出及图片保存失败问题
听起来你碰到了个挺头疼的偶发权限问题——卸载重装应用后,有时候存储权限请求弹不出来,直接导致保存图片时抛出java.io.FileNotFoundException: /storage/emulated/0/Abc/Abc 1601370822977.png (Permission denied)错误。我来帮你拆解下核心原因,再给出针对性的修复方案:
一、问题根源分析
你的基础权限逻辑框架没问题,但漏掉了两个关键场景:
- 用户勾选「不再询问」后拒绝权限:这种情况下
requestPermissions不会弹出系统权限对话框,直接触发拒绝回调,而你的代码没做任何处理,导致后续保存操作直接报错。 - Android 10+(API 29)的Scoped Storage变更:从Android 10开始,系统限制了直接访问外部存储根目录的行为,即便你申请了
WRITE_EXTERNAL_STORAGE权限,也可能无法在/storage/emulated/0/Abc这类目录下创建文件。
二、具体修复方案
1. 处理「不再询问」的权限永久拒绝场景
修改saveImage方法,在请求权限前判断是否需要向用户解释权限用途,以及是否被永久拒绝:
private void saveImage(){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){ finallySave(); } else { // 检查是否需要向用户解释权限必要性 if(shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)){ new AlertDialog.Builder(this) .setTitle("需要存储权限") .setMessage("保存图片需要访问您的存储,请授予权限") .setPositiveButton("确定", (dialog, which) -> { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},STORAGE_WRITE_REQUEST_CODE); }) .setNegativeButton("取消", null) .show(); } else { // 要么是首次请求,要么是用户勾选了「不再询问」 if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED){ // 永久拒绝,引导用户去设置页开启权限 new AlertDialog.Builder(this) .setTitle("权限被拒绝") .setMessage("存储权限已被永久拒绝,请前往设置开启") .setPositiveButton("去设置", (dialog, which) -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); }) .setNegativeButton("取消", null) .show(); } else { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},STORAGE_WRITE_REQUEST_CODE); } } } } else { finallySave(); } }
2. 适配Android 10+的存储机制
方案A:改用应用专属外部存储目录(推荐,无需权限)
从Android 4.4开始,应用可以访问外部存储的专属目录(/storage/emulated/0/Android/data/[你的包名]/files/),这个目录不需要申请存储权限,且卸载应用时会自动删除:
private File getImageDir() { // 获取应用专属外部存储的图片目录 File directory = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "Abc"); if (!directory.exists()) { if (!directory.mkdirs()) { Toast.makeText(this, "Could not create directory", Toast.LENGTH_SHORT).show(); return null; } } return directory; }
方案B:使用MediaStore保存图片(适用于需要其他应用访问的图片)
如果你的图片需要被相册或其他应用识别,可通过MediaStore插入图片,Android 10+无需申请WRITE_EXTERNAL_STORAGE:
private void finallySave() { try { ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, "Abc " + System.currentTimeMillis() + ".png"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Abc"); } else { values.put(MediaStore.Images.Media.DATA, new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Abc").getAbsolutePath()); } ContentResolver resolver = getContentResolver(); Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (uri != null) { OutputStream outputStream = resolver.openOutputStream(uri); bitmaps[0].compress(Bitmap.CompressFormat.PNG, 100, outputStream); outputStream.flush(); outputStream.close(); Toast.makeText(this, "图片保存成功", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "图片保存失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
3. 完善错误处理逻辑
你的finallySave方法把异常吞掉了,这会导致无法定位具体问题,建议添加日志和用户提示:
private void finallySave(){ try { if(imagesDir == null){ Toast.makeText(this, "存储目录初始化失败", Toast.LENGTH_SHORT).show(); return; } File outFile = new File(imagesDir, "Abc " + System.currentTimeMillis() + ".png"); FileOutputStream outputStream = new FileOutputStream(outFile); bitmaps[0].compress(Bitmap.CompressFormat.PNG, 100, outputStream); outputStream.flush(); outputStream.close(); scanFile(outFile, "image/png"); Toast.makeText(this, "图片保存成功", Toast.LENGTH_SHORT).show(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "图片保存失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } }
三、额外优化建议
- 卸载重装后首次启动时,尽量在进入主页面之前就请求存储权限,避免用户触发保存操作时才弹出,提升体验。
- 针对Android 13+,存储权限拆分为
READ_MEDIA_IMAGES和WRITE_EXTERNAL_STORAGE,若仅需保存图片,申请WRITE_EXTERNAL_STORAGE即可,但需注意Scoped Storage的限制。
内容的提问来源于stack exchange,提问作者Shreemaan Abhishek
相关产品推荐
相关产品推荐

