安卓绘图应用编辑图库图片保存时私有存储空Bitmap、共享存储缺原图问题
问题定位&修复方案
问题1:私有存储保存空文件
你在API<28的分支中创建了私有目录的文件输出流,但从未将Bitmap数据写入该流,仅做了空流的flush和关闭,所以得到空文件。同时API 28+分支完全没有写入私有目录的逻辑,导致高版本系统私有存储也没有对应文件。
问题2:保存结果缺失原图仅保留绘制内容
你从图库加载并缩放得到的loadedBitmap默认是**不可变(Immutable)**类型,直接将其绑定到Canvas后,调用draw(canvas)产生的绘制内容无法写入到loadedBitmap中,导致最终保存的文件没有合并原图和绘制修改。
具体修复代码
第一步:修改Bitmap初始化逻辑,确保loadedBitmap是可变副本
if (bitmap != null) { // 缩放原图后转为可变Bitmap,才能写入绘制内容 Bitmap scaled = scale(bitmap, width, height); // 创建可变副本,配置和原缩放图一致 loadedBitmap = scaled.copy(Bitmap.Config.ARGB_8888, true); // 回收无用的原图和临时缩放图,避免内存泄漏 bitmap.recycle(); scaled.recycle(); } else { this.bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true); }
第二步:修复保存方法逻辑
@SuppressLint("WrongThread") public void saveImage() throws IOException { String filename = "appName" + System.currentTimeMillis(); Bitmap targetBitmap; // 先合并原图和绘制内容到最终要保存的Bitmap if(loadedBitmap != null){ // 用可变的loadedBitmap创建Canvas Canvas canvas = new Canvas(loadedBitmap); // 调用View的draw方法把用户绘制的内容画到loadedBitmap上,合并原图和修改 draw(canvas); targetBitmap = loadedBitmap; } else{ Canvas canvas = new Canvas(bitmap); draw(canvas); targetBitmap = bitmap; } // 先统一写入应用私有存储,解决空文件问题 ContextWrapper cw = new ContextWrapper(getContext()); File directory = cw.getDir("files", Context.MODE_PRIVATE); pathString = directory.getAbsolutePath(); File privateFile = new File(directory, filename + ".png"); FileOutputStream privateFos = new FileOutputStream(privateFile); // 把最终Bitmap写入私有流 targetBitmap.compress(Bitmap.CompressFormat.PNG, 100, privateFos); privateFos.flush(); privateFos.close(); // 再处理公共存储保存逻辑 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { OutputStream fos; ContentResolver resolver = context.getContentResolver(); ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, filename + ".png"); // 统一MIME类型和压缩格式,避免格式不匹配 contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); fos = resolver.openOutputStream(Objects.requireNonNull(imageUri)); // 直接用已经合并好的targetBitmap压缩 targetBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); Objects.requireNonNull(fos).close(); imageSaved = true; } else { if(ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){ // 直接把已经合并好的Bitmap插入MediaStore MediaStore.Images.Media.insertImage(context.getContentResolver(), targetBitmap, filename, "made with appName"); addImageToGallery(privateFile.getPath(), context); imageSaved = true; }else{ requestPermissions((Activity) context, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 100); } } }
第三步:优化图库选图逻辑(可选,避免分区存储下路径获取失败)
Android 10以上通过MediaStore.Images.Media.DATA获取路径的方式已经被废弃,建议直接将选中图片的Uri传递给绘图页,用ContentResolver.openInputStream加载Bitmap,避免部分机型拿不到路径导致加载失败:
// onActivityResult中直接传Uri而不是路径 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && requestCode == IMAGE && data != null) { Uri selectedImageUri = data.getData(); Intent intent = new Intent(getApplicationContext(), ArtActivity.class); intent.putExtra("imageUri", selectedImageUri.toString()); startActivity(intent); } }
绘图页接收Uri后用如下方式加载Bitmap:
Uri imageUri = Uri.parse(getIntent().getStringExtra("imageUri")); Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
内容的提问来源于stack exchange,提问作者Grim Reaper
相关产品推荐
相关产品推荐

