使用Picasso库下载图片并保存到本地存储失败问题求助
我帮你排查了代码和配置里的几个关键问题,这些大概率是导致onBitmapFailed触发的原因,咱们一步步来解决:
1. 明文HTTP请求被拦截(Android 9+)
你用的图片URL是http://开头的,而Android 9(API 28)及以上系统默认禁止明文网络请求,这会直接导致Picasso没法下载图片,触发失败回调。
解决办法:在AndroidManifest.xml的<application>标签里加上android:usesCleartextTraffic="true",允许明文请求:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> <!-- 新增这一行 --> <!-- 原有内容不变 --> </application>
2. 存储路径拼接完全错误
你代码里的路径逻辑有问题:
final File myImageFile = new File(Environment.getExternalStorageDirectory().getPath() + "/" + path, "example");
这里的path是"/data/data/ops.com.imagefetcher/images",拼完之后会得到/storage/emulated/0//data/data/ops.com.imagefetcher/images/example这种不合法的路径——外部存储根目录下根本不存在/data/data这个文件夹,自然没法创建文件,甚至可能导致Picasso的回调异常。
调整路径的两种方案:
- 要是想存到外部公共目录(比如相册):
// 先创建自定义文件夹,在Pictures目录下 File storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyAppImages"); if (!storageDir.exists()) { storageDir.mkdirs(); // 递归创建目录 } // 记得给文件名加后缀,比如.jpg final File myImageFile = new File(storageDir, "example.jpg");
- 要是想存到应用私有内部存储(只有你的APP能访问):
ContextWrapper cw = new ContextWrapper(mContext); // 创建内部存储的私有images目录 File directory = cw.getDir("images", Context.MODE_PRIVATE); final File myImageFile = new File(directory, "example.jpg");
3. 动态存储权限没申请(Android 6.0+)
如果是存到外部存储,Android 6.0(API 23)及以上只在Manifest里声明WRITE_EXTERNAL_STORAGE是没用的,必须动态申请权限。
在调用下载前先检查权限(比如在你的MyService或者MainActivity里):
// 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 申请权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1001); // 自定义请求码 } else { // 权限已允许,执行下载 new DownloadImage().getImage(this); } // 处理权限申请结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1001) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { new DownloadImage().getImage(this); } else { Toast.makeText(this, "需要存储权限才能保存图片哦", Toast.LENGTH_SHORT).show(); } } }
4. 确保Target的强引用有效
你已经把Target作为成员变量,但可以优化一下,避免重复创建导致引用丢失:
public class DownloadImage { Context mContext; // 保持强引用的Target成员 private Target mSaveTarget; public void getImage(Context context) { mContext = context; // 初始化Target,确保只创建一次 if (mSaveTarget == null) { mSaveTarget = buildSaveTarget(); } Picasso.with(context) .load("http://blog.concretesolutions.com.br/wp-content/uploads/2015/04/Android1.png") .into(mSaveTarget); Log.e("Download Image: ", "inside getImage()"); } private Target buildSaveTarget() { return new Target() { @Override public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) { new Thread(new Runnable() { @Override public void run() { Log.e("Download Image: ", "inside run"); // 这里用上面调整后的正确路径 File storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyAppImages"); if (!storageDir.exists()) { storageDir.mkdirs(); } final File myImageFile = new File(storageDir, "example.jpg"); FileOutputStream ostream = null; try { Log.e("Download Image: ", "inside try"); myImageFile.createNewFile(); ostream = new FileOutputStream(myImageFile); bitmap.compress(Bitmap.CompressFormat.JPEG, 80, ostream); ostream.flush(); Log.e("Download Image: ", "image saved to >>>" + myImageFile.getAbsolutePath()); } catch (Exception e) { e.printStackTrace(); Log.e("Download Image: ", "save error: " + e.getMessage()); } finally { try { if (ostream != null) { ostream.close(); Log.e("Download Image: ", "inside finally"); } } catch (Exception e) { e.printStackTrace(); } } } }).start(); } @Override public void onBitmapFailed(Drawable errorDrawable) { Log.e("Download Image: ", "error!"); // 打开Picasso日志,看具体失败原因 Picasso.with(mContext).setLoggingEnabled(true); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } }; } }
5. 开启Picasso日志排查细节
在onBitmapFailed里开启Picasso的日志,能看到更详细的下载失败原因(比如网络超时、图片不存在等):
@Override public void onBitmapFailed(Drawable errorDrawable) { Log.e("Download Image: ", "error!"); Picasso.with(mContext).setLoggingEnabled(true); }
总结
先优先解决明文HTTP限制和路径错误这两个问题,这是最可能导致失败的核心原因。然后再确认权限和Target引用的问题,应该就能正常下载保存图片了。
内容的提问来源于stack exchange,提问作者sak

