You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

安卓应用加载Assets内置SQLite数据库首次空指针二次无表异常排查

问题根因分析

1. 首次运行InputStream空指针原因

  • 路径拼接错误:常量DATABASE_DIRECTORY_PATH末尾缺少路径分隔符/,拼接后路径为data/data/com.mo.useexistingdatabasedemo/databasesCustomers.db,父目录databases不存在,创建FileOutputStream时触发异常。同时如果Assets中文件读取失败(比如文件名拼写错误、文件放置位置错误),代码仅打印日志未终止流程,后续直接调用inputStream.read()触发空指针。
  • 异常捕获逻辑缺陷:流打开失败后未做拦截,继续执行读写逻辑,直接调用空对象的方法。

2. 第二次运行找不到CustomerList表原因

  • 首次运行复制数据库失败后,调用getWritableDatabase()时系统会自动创建一个同名的空数据库文件,第二次运行时dbFile.exists()返回true,不会触发资产目录数据库的复制逻辑,使用的是空数据库,自然不存在目标表。
  • 额外隐藏bug:getRecords()方法中使用cursor.getColumnName()获取的是字段名而非字段值,后续显示数据也会异常,需改为cursor.getString()。
修复方案

步骤1:修正路径、版本号等基础配置

删除硬编码的数据库路径,使用系统API获取正确路径,对齐数据库版本号:

// 删除原有硬编码的DATABASE_DIRECTORY_PATH、DATABASE_FILE_PATH常量,替换为动态获取
public static final int DATABASE_VERSION = 1;

public DatabaseHelper(@Nullable Context context) {
    super(context, DATABASE_NAME, null, DATABASE_VERSION); // 修正构造函数传入的版本号参数
    this.context = context;
}

步骤2:重写loadDatabase方法,修复异常逻辑

public void loadDatabase() throws IOException {
    File dbFile = context.getDatabasePath(DATABASE_NAME);
    if (dbFile.exists()) {
        return;
    }
    // 先创建数据库父目录
    File parentDir = dbFile.getParentFile();
    if (!parentDir.exists()) {
        parentDir.mkdirs();
    }
    // try-with-resources自动关闭流,避免资源泄漏
    try (InputStream inputStream = context.getAssets().open(DATABASE_NAME);
         OutputStream outputStream = new FileOutputStream(dbFile)) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, len);
        }
        outputStream.flush();
    } catch (IOException e) {
        // 出现异常直接删除不完整的数据库文件,避免后续误判文件存在
        if (dbFile.exists()) {
            dbFile.delete();
        }
        throw e; // 抛出异常让上层感知加载失败,避免后续访问空库
    }
}

步骤3:修正getRecords方法取值逻辑

public ArrayList<Customer> getRecords() {
    arrayList.clear(); // 每次查询清空历史数据,避免重复添加
    SQLiteDatabase database = getReadableDatabase();
    Cursor cursor = database.rawQuery("SELECT * FROM " + TABLE_NAME, null);
    while (cursor.moveToNext()) {
        Customer customer = new Customer();
        // 替换getColumnName为getString获取实际字段值
        customer.customerName = cursor.getString(cursor.getColumnIndexOrThrow("CustomerName"));
        customer.country = cursor.getString(cursor.getColumnIndexOrThrow("Country"));
        arrayList.add(customer);
    }
    cursor.close(); // 关闭游标避免资源泄漏
    return arrayList;
}

步骤4:调整MainActivity调用顺序

确保先调用loadDatabase()完成数据库复制,再调用getRecords()查询数据,注意捕获loadDatabase的异常处理加载失败场景。


内容的提问来源于stack exchange,提问作者azeemofwalsall

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 06:06:03