Android 9.0 SQLiteCantOpenDatabaseException SQLITE_CANTOPEN(不支持WAL模式)源码分析定位
Posted 新根
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 9.0 SQLiteCantOpenDatabaseException SQLITE_CANTOPEN(不支持WAL模式)源码分析定位相关的知识,希望对你有一定的参考价值。
最近一直忙着处理降低crash率,在Bugly上最新版本中统计到的一个数据库有关的crash:
# pool-20-thread-1(323)
android.database.sqlite.SQLiteCantOpenDatabaseException
unable to open database file (Sqlite code 14 SQLITE_CANTOPEN), (OS error - 2:No such file or directory)
android.database.sqlite.SQLiteConnection.nativeExecuteForLong(Native Method)
android.database.sqlite.SQLiteConnection.executeForLong(SQLiteConnection.java:657)
android.database.sqlite.SQLiteSession.executeForLong(SQLiteSession.java:667)
android.database.sqlite.SQLiteStatement.simpleQueryForLong(SQLiteStatement.java:107)
android.database.DatabaseUtils.longForQuery(DatabaseUtils.java:842)
android.database.DatabaseUtils.longForQuery(DatabaseUtils.java:830)
android.database.sqlite.SQLiteDatabase.getVersion(SQLiteDatabase.java:1036)
android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:390)
android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:337)
com.meta.android.mpg.mix.sG4DaaDs.a4Dgsas.a4Dgsas(Unknown Source:3)
com.meta.android.mpg.mix.gG4Gas.fa$fa.run(Unknown Source:4)
java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
java.lang.Thread.run(Thread.java:784)
发生的机型统计如下:
1.源码定位分析
1.1借助系统日志来辅助定位
查看bugly 捕捉的有效log:
08-23 17:13:28.790 5668 5860 E SQLiteLog: (14) cannot open file at line 36906 of [68b898381a]
4708-23 17:13:28.790 5668 5860 E SQLiteLog: (14) os_unix.c:36906: (2) open(/data/data/com.tools.growth.yhxy/virtual/data/user/0/com.minitech.miniworld.meta/databases/MpgSdk.db-wal) -
4808-23 17:13:28.791 5668 5860 E SQLiteLog: (14) unable to open database file// 有效日志
4908-23 17:13:28.791 5668 5860 E SQLiteDatabase: Failed to open database '/data/data/com.tools.growth.yhxy/virtual/data/user/0/com.minitech.miniworld.meta/databases/MpgSdk.db'.
5008-23 17:13:28.791 5668 5860 E SQLiteDatabase: android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (Sqlite code 14 SQLITE_CANTOPEN): , while compiling: PRAGMA journal_mode, (OS error - 2:No such file or directory)
5108-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
5208-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:948)
5308-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:693)
5408-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:378)
5508-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:327)
5608-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:232)
5708-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:210)
5808-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:552)
5908-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:213)
6008-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:202)
6108-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:958)
6208-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:942)
6308-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:816)
6408-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:806)
6508-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:370)
6608-23 17:13:28.791 5668 5860 E SQLiteDatabase: at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:337)
6708-23 17:13:28.791 5668 5860 E SQLiteDatabase: at com.meta.android.mpg.mix.sG4DaaDs.a4Dgsas.a4Dgsas(Unknown Source:3)
6808-23 17:13:28.791 5668 5860 E SQLiteDatabase: at com.meta.android.mpg.mix.gG4Gas.fa$fa.run(Unknown Source:4)
6908-23 17:13:28.791 5668 5860 E SQLiteDatabase: at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
7008-23 17:13:28.791 5668 5860 E SQLiteDatabase: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
7108-23 17:13:28.791 5668 5860 E SQLiteDatabase: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
当Bugly中的当前crash记录上报没有日志时,可以多查看几条记录,日志是一个很好加速定位问题的辅助手段。
1.2查看源码,锁定报错点
本篇源码基于android 9.0
/frameworks/base/core/java/android/database/sqlite/SQLiteConnection.java
private PreparedStatement acquirePreparedStatement(String sql)
PreparedStatement statement = mPreparedStatementCache.get(sql);
//...
final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
//...
return statement;
private static native long nativePrepareStatement(long connectionPtr, String sql);
接下来看下jni 层的调用。
/frameworks/base/core/jni/android_database_SQLiteConnection.cpp
static jlong nativePrepareStatement(JNIEnv* env, jclass clazz, jlong connectionPtr,
jstring sqlString)
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
jsize sqlLength = env->GetStringLength(sqlString);
const jchar* sql = env->GetStringCritical(sqlString, NULL);
sqlite3_stmt* statement;
int err = sqlite3_prepare16_v2(connection->db,
sql, sqlLength * sizeof(jchar), &statement, NULL);
env->ReleaseStringCritical(sqlString, sql);
if (err != SQLITE_OK)
//构建异常信息
const char *query = env->GetStringUTFChars(sqlString, NULL);
char *message = (char*) malloc(strlen(query) + 50);
if (message)
strcpy(message, ", while compiling: "); // less than 50 chars
strcat(message, query);
env->ReleaseStringUTFChars(sqlString, query);
//抛出异常
throw_sqlite3_exception(env, connection->db, message);
free(message);
return 0;
return reinterpret_cast<jlong>(statement);
这里刚好和日志中的while compiling: PRAGMA journal_mode
匹配,说明 调用sqlite3_prepare16_v2 返回失败结果。
接着继续, 查看sqlite3_prepare16_v2()
:
/external/sqlite/dist/orig/sqlite3.c
SQLITE_API int sqlite3_prepare16_v2(
sqlite3 *db, /* Database handle. */
const void *zSql, /* UTF-16 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const void **pzTail /* OUT: End of parsed string */
)
int rc;
rc = sqlite3Prepare16(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,ppStmt,pzTail);
assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
return rc;
因SQLite的源码太过于庞大,只能考虑逆推方式,先根据报错日志,检索关键位置。
根据unable to open database file
匹配到SQLITE_CANTOPEN
字段。
SQLITE_PRIVATE const char *sqlite3ErrStr(int rc)
static const char* const aMsg[] =
/* SQLITE_CANTOPEN */ "unable to open database file",
//...
根据cannot open file at
和SQLITE_CANTOPEN
匹配到sqlite3CantopenError()
,用于报错时,拼接打印报错信息:
接下来看下,sqlite3CantopenError()
:
#define SQLITE_CANTOPEN_BKPT sqlite3CantopenError(__LINE__)
SQLITE_PRIVATE int sqlite3CantopenError(int lineno)
testcase( sqlite3GlobalConfig.xLog!=0 );
return sqlite3ReportError(SQLITE_CANTOPEN, lineno, "cannot open file"); // 对应日志:(14) cannot open file at line 36906 of [68b898381a]
全局检索SQLITE_CANTOPEN
关键字,耗费脑细胞的逐步分析调用链:sqlite3Prepare16()->省略部分流程->sqlite3BtreeBeginTrans()->lockBtree()->sqlite3PagerOpenWal()
。
接下来看下,sqlite3BtreeBeginTrans():
SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag)
//....
do
//循环检查,直到lockBtree返回相应状态
while( pBt->pPage1==0 && SQLITE_OK==(rc = lockBtree(pBt)) );
if( rc==SQLITE_OK && wrflag )
if( (pBt->btsFlags & BTS_READ_ONLY)!=0 )
rc = SQLITE_READONLY;
else
rc = sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db));
if( rc==SQLITE_OK )
rc = newDatabase(pBt);
if( rc!=SQLITE_OK )
unlockBtreeIfUnused(pBt);
while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
btreeInvokeBusyHandler(pBt) )
return rc;
接着继续查看,lockBtree()
:
static int lockBtree(BtShared *pBt)
int rc; /* Result code from subfunctions */
MemPage *pPage1; /* Page 1 of the database file */
int nPage; /* Number of pages in the database */
int nPageFile = 0; /* Number of pages in the database file */
int nPageHeader; /* Number of pages in the database according to hdr */
//先获取一个share lock
rc = sqlite3PagerSharedLock(pBt->pPager);
if( rc!=SQLITE_OK ) return rc;
//...
//当write version 是2,数据库应该使用wal 模式。若是检查到log文件没有被打开,则进行打开。
if( page1[19]==2 && (pBt->btsFlags & BTS_NO_WAL)==0 )
int isOpen = 0;
rc = sqlite3PagerOpenWal(pBt->pPager, &isOpen);
if( rc!=SQLITE_OK )
//若是打开失败,则进行释放资源操作
goto page1_init_failed;
else
setDefaultSyncFlag(pBt, SQLITE_DEFAULT_WAL_SYNCHRONOUS+1);
if( isOpen==0 )
releasePageOne(pPage1);
return SQLITE_OK;
// 若是打开正常,则返回 SQLITE_NOTADB
rc = SQLITE_NOTADB;
else
setDefaultSyncFlag(pBt, SQLITE_DEFAULT_SYNCHRONOUS+1);
//....
page1_init_failed:
releasePageOne(pPage1);
pBtAndroid 9.0/P(android p指安卓9.0版本) okhttp3网络请求出错
为啥 Android Studio 不会在 Android Pie (9.0) 上运行应用程序?
AdapterView 不支持 Android 8.1/9.0 removeView(View)