使用Picasso库点击按钮无法保存图片的修复请求
问题排查与修复方案
我仔细看了你的代码,发现几个关键问题导致图片只弹提示却没实际保存,咱们一步步来解决:
核心问题分析
- Picasso的Target被垃圾回收:你用的是匿名内部类Target,Picasso对Target是弱引用,很可能在Bitmap加载完成前就被GC回收,导致
onBitmapLoaded根本没执行(不过你看到Toast弹出,说明这段代码确实跑了,那大概率是权限或存储路径的问题) - 权限不足:你只请求了
READ_EXTERNAL_STORAGE,但写入存储需要WRITE_EXTERNAL_STORAGE(Android 10及以下);Android 11+还需要特殊的全存储权限 - 存储路径兼容性差:
Environment.getExternalStorageDirectory()在Android Q及以上已被废弃,直接写入该目录会失败 - 错误处理缺失:保存失败时只打印日志,没有给用户提示,你没法直观知道哪里出问题
分步修复代码
1. 解决Target被回收的问题
把匿名Target改成类的成员变量,用强引用持有,避免被GC回收:
在PicassoDisplayImageAdapter类顶部添加成员变量:
private Target imageSaveTarget;
然后重写SaveImage方法:
private void SaveImage(String url) { // 先取消之前的请求,避免内存泄漏 if (imageSaveTarget != null) { Picasso.with(this).cancelRequest(imageSaveTarget); } imageSaveTarget = new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { // 把保存逻辑抽离,单独处理 boolean saveSuccess = saveBitmapToStorage(bitmap); Toast.makeText(getApplicationContext(), saveSuccess ? "图片下载成功!" : "图片保存失败,请检查权限", Toast.LENGTH_LONG).show(); } @Override public void onBitmapFailed(Drawable errorDrawable) { Toast.makeText(getApplicationContext(), "图片加载失败", Toast.LENGTH_LONG).show(); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { // 可选:这里可以显示加载中提示 } }; Picasso.with(this).load(url).into(imageSaveTarget); }
2. 适配多版本的Bitmap保存逻辑
单独写一个保存方法,处理不同Android版本的存储规则:
private boolean saveBitmapToStorage(Bitmap bitmap) { try { OutputStream outputStream = null; String fileName = System.currentTimeMillis() + ".jpg"; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android Q及以上,用MediaStore写入公共图库 ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/11zon"); Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (uri != null) { outputStream = getContentResolver().openOutputStream(uri); } } else { // Android Q以下,直接写入公共图片目录 File myDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "11zon"); if (!myDir.exists() && !myDir.mkdirs()) { return false; } File imageFile = new File(myDir, fileName); outputStream = new FileOutputStream(imageFile); } if (outputStream == null) { return false; } // 压缩保存图片,质量设为90平衡体积和清晰度 boolean compressSuccess = bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream); outputStream.flush(); outputStream.close(); // 通知媒体库刷新,让图片在图库显示 if (compressSuccess && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { MediaScannerConnection.scanFile(this, new String[]{new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "11zon/" + fileName).getAbsolutePath()}, null, null); } return compressSuccess; } catch (IOException e) { e.printStackTrace(); return false; } }
3. 修正权限检查与请求逻辑
更新权限检查方法,适配Android 11+的权限规则:
public boolean checkPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11及以上,检查全存储权限 if (!Environment.isExternalStorageManager()) { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivityForResult(intent, PERMISSION_WRITE); return false; } } else { // Android 10及以下,检查读写权限 int writePerm = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); int readPerm = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); if (writePerm != PackageManager.PERMISSION_GRANTED || readPerm != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_WRITE); return false; } } return true; }
同时更新权限回调和Android 11+的权限结果处理:
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_WRITE) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted) { // 权限获取成功,重新执行下载 String URL = getIntent().getStringExtra("imageUrl"); SaveImage(URL); } else { Toast.makeText(this, "需要存储权限才能下载图片", Toast.LENGTH_SHORT).show(); } } } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PERMISSION_WRITE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Environment.isExternalStorageManager()) { // 权限获取成功,重新执行下载 String URL = getIntent().getStringExtra("imageUrl"); SaveImage(URL); } else { Toast.makeText(this, "需要存储权限才能下载图片", Toast.LENGTH_SHORT).show(); } } }
4. 清理内存避免泄漏
在Activity销毁时取消Picasso请求,释放Target:
@Override protected void onDestroy() { super.onDestroy(); if (imageSaveTarget != null) { Picasso.with(this).cancelRequest(imageSaveTarget); imageSaveTarget = null; } }
最后补充:Manifest权限配置
在AndroidManifest.xml中添加必要的权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
内容的提问来源于stack exchange,提问作者Coder




