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

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

火山引擎 最新活动