Bitmap数组存入SharedPreferences触发OutOfMemoryError,求解决方案
兄弟你这是踩了个典型的坑——直接把Bitmap对象序列化到SharedPreferences里,这绝对会触发内存爆炸!咱们先拆解清楚问题出在哪,再给你两个靠谱的解决方案。
问题根源分析
你现在的做法是把每个Drawable解码成Bitmap,再用Gson把整个Bitmap数组转成JSON字符串,这里有两个致命问题:
- Bitmap本身内存开销大:哪怕是mdpi的图片,解码成ARGB_8888格式的Bitmap后,每个像素要占4字节,26张图加起来内存就已经不小了。
- Gson序列化Bitmap会生成巨量文本:Gson会把Bitmap的所有像素数据转成纯文本格式,这会让体积膨胀好几倍——报错里提到要分配300MB内存,就是这个原因,直接超出了你的可用内存上限。
而且SharedPreferences从设计上就不适合存储大体积数据,官方建议单条数据不要超过100KB,你直接存几百MB的字符串,完全违背了它的使用场景。
解决方案:两种可行思路
思路1:压缩Bitmap为Base64字符串(适合小图/少量图)
如果你的图片本身尺寸不大,可以先把Bitmap压缩成JPEG/PNG字节数组,再转成Base64字符串,这样体积会大幅缩小,再存入SP。
修改你的代码如下:
ArrayList<Model> Combinebitmaps = new ArrayList<>(); int arr[] = {R.drawable.b1, R.drawable.b2, ..., R.drawable.b26}; for (int i = 0; i < arr.length; i++) { // 解码时先压缩,减少内存占用 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; // 宽高缩为原来的1/2,内存占用降到原来的1/4 Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), arr[i], options); // 把Bitmap压缩为JPEG字节数组,质量设为50(可根据需求调整) ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos); byte[] bitmapBytes = baos.toByteArray(); // 转成Base64字符串 String bitmapBase64 = Base64.encodeToString(bitmapBytes, Base64.DEFAULT); Model model = new Model(bitmapBase64); // 注意修改Model类,存储String类型的Base64 Combinebitmaps.add(model); // 及时回收Bitmap,释放内存 if (!bitmap.isRecycled()) { bitmap.recycle(); } } // 序列化Base64数组到SP,用apply()异步提交更安全 SharedPreferences sharedPreferences = MainActivity.this.getSharedPreferences("ArrayCombine", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("Array", new Gson().toJson(Combinebitmaps)); editor.apply();
注意:你需要修改Model类,把原来存储Bitmap的字段改成存储String(Base64)。读取时再把Base64转回Bitmap即可。
思路2:将Bitmap保存为文件,SP存储文件路径(适合多图/大图)
这是更推荐的方案,因为SP根本不是用来存大量二进制数据的。我们把每个Bitmap保存到应用内部存储,然后在SP里存储这些文件的路径列表,这样SP里只存少量字符串,完全不会有内存压力。
代码示例:
ArrayList<String> bitmapPaths = new ArrayList<>(); int arr[] = {R.drawable.b1, R.drawable.b2, ..., R.drawable.b26}; for (int i = 0; i < arr.length; i++) { Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), arr[i]); // 创建存储文件的路径,比如内部存储的files目录下 File file = new File(getFilesDir(), "bitmap_" + i + ".jpg"); try (FileOutputStream fos = new FileOutputStream(file)) { // 压缩Bitmap并写入文件,质量设为80兼顾清晰度和体积 bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); bitmapPaths.add(file.getAbsolutePath()); // 保存文件路径 } catch (IOException e) { e.printStackTrace(); } // 回收Bitmap if (!bitmap.isRecycled()) { bitmap.recycle(); } } // 把路径列表序列化到SP SharedPreferences sharedPreferences = MainActivity.this.getSharedPreferences("ArrayCombine", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("Array", new Gson().toJson(bitmapPaths)); editor.apply();
读取的时候,从SP取出路径列表,再根据路径解码Bitmap:
SharedPreferences sharedPreferences = getSharedPreferences("ArrayCombine", Context.MODE_PRIVATE); String pathsJson = sharedPreferences.getString("Array", ""); ArrayList<String> bitmapPaths = new Gson().fromJson(pathsJson, new TypeToken<ArrayList<String>>(){}.getType()); ArrayList<Bitmap> bitmaps = new ArrayList<>(); for (String path : bitmapPaths) { Bitmap bitmap = BitmapFactory.decodeFile(path); bitmaps.add(bitmap); }
额外注意事项
- 解码Bitmap时的优化:始终用
BitmapFactory.Options设置采样率(inSampleSize),或者设置inPreferredConfig为RGB_565(每个像素占2字节,比ARGB_8888省一半内存)。 - 及时回收Bitmap:不再使用的Bitmap一定要调用
recycle(),并置为null,帮助GC快速回收内存。 - 别依赖largeHeap:
android:largeHeap="true"只是临时缓解手段,会让应用占用更多系统资源,不是根本解决方案,尽量不要依赖它。
内容的提问来源于stack exchange,提问作者M Dilawar
相关产品推荐
相关产品推荐

