Android进行数据库操作时,可能会遇到如下异常:
E AndroidRuntime: android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (code 14)E AndroidRuntime: at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)E AndroidRuntime: at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:553)E AndroidRuntime: at android.database.sqlite.SQLiteSession.beginTransactionUnchecked(SQLiteSession.java:323)E AndroidRuntime: at android.database.sqlite.SQLiteSession.beginTransaction(SQLiteSession.java:298)E AndroidRuntime: at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:505)E AndroidRuntime: at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:416)
由于SQLite底层的代码并不是很好分析,一直以来这个问题的具体原因很难定位。
从我目前接触到的信息来看,关于这个问题的原因,江湖传言有这么三个:
1、内存耗尽(几乎是最显而易见的原因);
2、进程的文件句柄Fd耗尽;
3、SQLite的临时文件不可用(自己没证实过)。
对于内存耗尽的原因比较好定位。
当大家怀疑是内存问题导致SQLite异常时,只需要在crash等日志中搜索memory关键即可:
W art : Throwing OutOfMemoryError "Could not allocate JNI Env"
如果是内存原因,那么应该可以看到类似上述的日志。
这个时候就要考虑系统当时是真的运行了过多的进程,还是存在内存泄露的问题。
对于文件句柄耗尽的原因,可以搜索类似如下的log:
E art : ashmem_create_region failed for 'indirect ref table': Too many open files
SQLite最终会调用ashmem_create_region()来分配共享内存。
如果出现上述log,那么应该就是开启了过多的文件,导致Fd耗尽。
比较常见的原因就是:Cursor开启后没有关闭。
此外,还有一些其它的场景,例如:
进程一直存在,然后不断地新建并运行HandlerThread。
如果HandlerThread在功能执行结束后不主动quit的话,
进程中就会积累出越来越多的HandlerThread。
由于HandlerThread对应的Looper也会持有Fd,
最终也会导致Fd耗尽,出现前文提到的异常。
如果怀疑是这种问题,可以使用root的手机测试你的APK。
利用ps或ps -A得到APK进程对应的pid,然后执行下述命令:
ls -al /proc/pid/fd
执行结果类似于:
图中的数字就是fd编号。
如果随着APK运行(测试),Fd不断变大(正常情况下,释放后会复用),那么就可以确定Fd存在泄露。
对于第三种情况,没仔细研究过,看过很多人写,内容几乎一样。
附上链接以备万一:/p/6ad0491404da