Android SQLite图片存储与ListView加载卡顿问题优化咨询
解决SQLite存图片导致ListView卡顿/崩溃的优化方案
嘿,我看你在开发Android物品管理应用时遇到了图片存储和列表展示的内存问题,这确实是个很典型的痛点!咱们从几个核心点来优化你的代码:
1. 别再把图片以Blob存在SQLite里(最关键的优化)
SQLite本身并不适合存储大体积的二进制数据(比如图片),Blob会让数据库文件迅速膨胀,而且读取时一次性加载整个byte[]再解码成Bitmap,会瞬间占用大量内存,这就是你ListView卡顿甚至崩溃的核心原因。
替代方案:存储图片文件路径到数据库
- 从图库选择图片后,把图片复制到你的应用私有目录(比如
getFilesDir()或者getExternalFilesDir()下) - 只把这个文件的路径字符串存入SQLite,而不是整个图片的byte数组
- 读取时直接用Glide加载这个路径,Glide会自动处理图片的缓存、采样、内存管理,完全不用你手动处理Bitmap
代码修改示例(插入部分)
// 先实现一个方法,把选中的Bitmap保存到应用私有目录 private String saveBitmapToFile(Bitmap bitmap) { File filesDir = getFilesDir(); File imageFile = new File(filesDir, "flower_" + System.currentTimeMillis() + ".jpg"); try (FileOutputStream fos = new FileOutputStream(imageFile)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); // 这里可以调整压缩质量 return imageFile.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); return null; } } // 修改insertFlower方法,用异步任务处理图片保存(避免阻塞主线程) private void insertFlower() { new AsyncTask<Bitmap, Void, String>() { @Override protected String doInBackground(Bitmap... bitmaps) { return saveBitmapToFile(bitmaps[0]); } @Override protected void onPostExecute(String imagePath) { if (imagePath != null) { String name = mFlowerName.getText().toString().trim(); String price = mFlowerPrice.getText().toString().trim(); String quantity = mFlowerQuantity.getText().toString().trim(); ContentValues contentValues = new ContentValues(); contentValues.put(FlowersEntry.COLUMN_FLOWER_NAME, name); contentValues.put(FlowersEntry.COLUMN_FLOWER_PRICE, price); contentValues.put(FlowersEntry.COLUMN_FLOWER_TYPE, mType); contentValues.put(FlowersEntry.COLUMN_FLOWER_QUANTITY, quantity); contentValues.put(FlowersEntry.COLUMN_FLOWER_IMAGE_PATH, imagePath); // 存路径 getContentResolver().insert(FlowersEntry.CONTENT_URI, contentValues); } } }.execute(bitmap); }
2. 如果你必须用Blob存储(特殊场景),优化读取逻辑
如果因为某些原因不能改存路径,那至少要优化读取时的Bitmap处理:
- 不要手动解码Bitmap后再给Glide:你现在先把byte[]解码成Bitmap,再传给Glide,这会在内存中保留一个Bitmap实例,完全没必要。Glide本身支持直接加载byte数组,它会自动处理采样和缓存。
- 给Glide设置图片尺寸限制:根据你的ImageView实际大小,用
override()方法指定加载的图片尺寸,避免加载远超控件大小的Bitmap,浪费内存。 - 启用Glide的内存缓存和磁盘缓存:默认是开启的,但确保你没有禁用,这样重复加载图片时不会重复解码。
修改CursorAdapter的bindView方法
@Override public void bindView(View view, Context context, Cursor cursor) { // 先实现ViewHolder模式,减少重复findViewById的开销 ViewHolder holder; if (view.getTag() == null) { holder = new ViewHolder(); holder.nameTextView = view.findViewById(R.id.flowers_name); holder.priceTextView = view.findViewById(R.id.flowers_price); holder.quantityTextView = view.findViewById(R.id.flowers_quantity); holder.imageImageView = view.findViewById(R.id.flowers_image); view.setTag(holder); } else { holder = (ViewHolder) view.getTag(); } String nameFromDb = cursor.getString(cursor.getColumnIndex(FlowersEntry.COLUMN_FLOWER_NAME)); String priceFromDb = String.valueOf(cursor.getFloat(cursor.getColumnIndex(FlowersEntry.COLUMN_FLOWER_PRICE))); String quantityFromDb = String.valueOf(cursor.getInt(cursor.getColumnIndex(FlowersEntry.COLUMN_FLOWER_QUANTITY))); byte[] byteFromDb = cursor.getBlob(cursor.getColumnIndex(FlowersEntry.COLUMN_FLOWER_IMAGE)); holder.nameTextView.setText(nameFromDb); holder.priceTextView.setText(priceFromDb); holder.quantityTextView.setText(quantityFromDb); // 直接用Glide加载byte数组,并且指定控件大小 Glide.with(context) .load(byteFromDb) .override(holder.imageImageView.getWidth(), holder.imageImageView.getHeight()) // 匹配控件尺寸 .centerCrop() // 根据你的需求调整缩放模式 .into(holder.imageImageView); } // 内部ViewHolder类 private static class ViewHolder { TextView nameTextView; TextView priceTextView; TextView quantityTextView; ImageView imageImageView; }
3. 修复AsyncTask的错误用法
你现在直接调用compressImage.doInBackground(bitmap),这会在主线程执行压缩操作,完全没用到AsyncTask的异步能力!正确的做法是调用compressImage.execute(bitmap),然后在onPostExecute回调中完成数据库插入,避免阻塞主线程。
比如修改原来的CompressImage类:
public class CompressImage extends AsyncTask<Bitmap, Void, byte[]> { private InsertCallback callback; public CompressImage(InsertCallback callback) { this.callback = callback; } @Override protected byte[] doInBackground(Bitmap... bitmaps) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); bitmaps[0].compress(Bitmap.CompressFormat.JPEG, 20, outputStream); return outputStream.toByteArray(); } @Override protected void onPostExecute(byte[] bytes) { super.onPostExecute(bytes); if (callback != null) { callback.onCompressDone(bytes); } } // 定义回调接口 public interface InsertCallback { void onCompressDone(byte[] imageBytes); } } // 调用方式 private void insertFlower() { String name = mFlowerName.getText().toString().trim(); String price = mFlowerPrice.getText().toString().trim(); String quantity = mFlowerQuantity.getText().toString().trim(); new CompressImage(new CompressImage.InsertCallback() { @Override public void onCompressDone(byte[] imageBytes) { ContentValues contentValues = new ContentValues(); contentValues.put(FlowersEntry.COLUMN_FLOWER_NAME, name); contentValues.put(FlowersEntry.COLUMN_FLOWER_PRICE, price); contentValues.put(FlowersEntry.COLUMN_FLOWER_TYPE, mType); contentValues.put(FlowersEntry.COLUMN_FLOWER_QUANTITY, quantity); contentValues.put(FlowersEntry.COLUMN_FLOWER_IMAGE, imageBytes); getContentResolver().insert(FlowersEntry.CONTENT_URI, contentValues); } }).execute(bitmap); }
额外小优化
- 压缩图片时不要用20这么低的质量,试试80左右,平衡质量和体积
- 从图库选择图片时,尽量获取原图的缩略图,而不是直接加载原图,减少初始Bitmap的内存占用
- 定期清理应用的缓存,避免图片文件过多占用存储
内容的提问来源于stack exchange,提问作者Kuba




