Android基于Glide加载ImageView的URL图片 下载存外部存储报文件不存在错误
报错根因
你遇到的FileNotFoundException带ENOENT (No such file or directory)提示,核心是调用new FileOutputStream(outFile)时,目标文件的父目录实际不存在,代码没有对目录创建的结果做校验,默认mkdirs()一定能成功。
几个会直接触发这个报错的隐患:
- 目录路径写法不兼容:直接拼接
Environment.getExternalStorageDirectory()的路径,在Android 10以上版本因为分区存储限制,没有对应写入权限,mkdirs()会静默返回失败,不会抛异常,代码后续直接在不存在的目录下创建文件流,自然报错。 - 权限逻辑有漏洞:用户首次触发权限申请时,
checkPermission直接返回false,弹框让用户授权后,没有在权限回调里执行下载逻辑,就算用户最后同意权限,本次点击的下载流程也不会继续。 - 流程控制有缺陷:捕获到文件流创建的异常后,只打印堆栈没有中断后续逻辑,继续执行图片压缩、流操作,会额外触发空指针崩溃。
- 从ImageView拿Bitmap的逻辑有崩溃风险:Glide加载完成后可能为ImageView设置带动画、圆角效果的自定义Drawable,直接强转成
BitmapDrawable会触发类转换异常,根本走不到文件写入步骤。
可落地的修复方案
1. 修正目录创建与写入逻辑
替换原来的手动创建目录、直接拿文件流的代码,必须对目录创建结果做校验:
File sdCardRoot = Environment.getExternalStorageDirectory(); File dir = new File(sdCardRoot, "TirunelveliParithabangal"); // 必须校验目录是否真实存在,创建失败直接中断流程 if (!dir.exists() && !dir.mkdirs()) { TastyToast.makeText(getApplicationContext(), "保存目录创建失败", TastyToast.LENGTH_LONG, TastyToast.ERROR); return; } String fileName = String.format("%d.jpg", System.currentTimeMillis()); File outFile = new File(dir, fileName); // 用try-with-resources自动管理流关闭,避免内存泄漏 try (FileOutputStream outStream = new FileOutputStream(outFile)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); outStream.flush(); TastyToast.makeText(getApplicationContext(), "Meme Downloaded Successfully", TastyToast.LENGTH_LONG*6, TastyToast.SUCCESS); } catch (IOException e) { e.printStackTrace(); TastyToast.makeText(getApplicationContext(), "图片保存失败", TastyToast.LENGTH_LONG, TastyToast.ERROR); }
如果你的应用targetSdkVersion >= 29,不要继续用直接操作外部存储路径的方式,改用MediaStore接口写入公共图片目录,不需要额外申请存储权限,完全适配分区存储规则:
ContentValues fileValues = new ContentValues(); fileValues.put(MediaStore.Images.Media.DISPLAY_NAME, String.format("%d.jpg", System.currentTimeMillis())); fileValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); fileValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/TirunelveliParithabangal"); Uri fileUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, fileValues); try (OutputStream outStream = getContentResolver().openOutputStream(fileUri)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); }
2. 替换Bitmap获取方式,避免强转异常
不要直接从meme_Image_view里强转拿Bitmap,直接用Glide的同步缓存读取接口拿原始Bitmap,清晰度更高也不会崩溃:
// 注意:这段文件IO、Glide同步加载的逻辑必须放在子线程执行,不能在主线程跑 Bitmap saveBitmap = Glide.with(this) .asBitmap() .load(url) .submit() .get();
因为你配置了DiskCacheStrategy.ALL,图片已经被Glide本地缓存,同步读取几乎不会有额外网络耗时。
3. 补全权限流程的逻辑漏洞
原来的权限判断只做了单次校验,没有处理用户授权后的回调:
- 当权限被拒绝、弹框引导用户申请权限后,要重写
onRequestPermissionsResult方法,在用户同意对应权限的回调分支里,重新执行下载逻辑 - Android 10以上如果还想沿用旧的存储写入逻辑,需要在AndroidManifest的application节点下配置
android:requestLegacyExternalStorage="true",否则就算申请了WRITE_EXTERNAL_STORAGE权限,也无法写入公共外部目录
内容的提问来源于stack exchange,提问作者novaw
相关产品推荐
相关产品推荐

