You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Picasso库点击按钮无法保存图片的修复请求

问题排查与修复方案

我仔细看了你的代码,发现几个关键问题导致图片只弹提示却没实际保存,咱们一步步来解决:

核心问题分析

  1. Picasso的Target被垃圾回收:你用的是匿名内部类Target,Picasso对Target是弱引用,很可能在Bitmap加载完成前就被GC回收,导致onBitmapLoaded根本没执行(不过你看到Toast弹出,说明这段代码确实跑了,那大概率是权限或存储路径的问题)
  2. 权限不足:你只请求了READ_EXTERNAL_STORAGE,但写入存储需要WRITE_EXTERNAL_STORAGE(Android 10及以下);Android 11+还需要特殊的全存储权限
  3. 存储路径兼容性差Environment.getExternalStorageDirectory()在Android Q及以上已被废弃,直接写入该目录会失败
  4. 错误处理缺失:保存失败时只打印日志,没有给用户提示,你没法直观知道哪里出问题

分步修复代码

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

火山引擎 最新活动