安卓应用加载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
相关产品推荐
相关产品推荐

