2013/08/29
SQLiteDatabase.openDatabase で StackOverflowError
java.lang.StackOverflowError at java.util.HashMap.<init>(HashMap.java:138) at java.util.HashMap.<init>(HashMap.java:174) at java.util.LinkedHashMap.<init>(LinkedHashMap.java:119) at android.util.LruCache.<init>(LruCache.java:81) at android.database.sqlite.SQLiteDatabase$1.<init>(SQLiteDatabase.java:2321) at android.database.sqlite.SQLiteDatabase.setMaxSqlCacheSize(SQLiteDatabase.java:2321) at android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:2072) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1129) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1086) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1146) at ...
どうも、
SQLiteDatabase#openDatabase を再帰的に呼んでいる様子。明らかに、SQLiteDatabase 内部で再帰呼び出しに入っているので、Android SDK 内のバグなのは間違いありません。
とはいえ、原因が分からない事には対策しようが無いので、とりあえず AOSP のコードを見てみることにしました。
が…クラッシュレポートには、発生時の端末名も Android のバージョン番号も記載がありません。
仕方がないので、Android 2.1 から順に見ていくことにしました。
参考
バージョン間の違いを見ていくのは結構面倒くさいのですが、以下のサイトを使うと効率的にコードをチェックすることができます。
GrepCode: android - Java Project - Source Code
GrepCode: android - Java Project - Source Code
すると、Android 4.0 の SQLiteDatabase.java に以下のようなコードが
SQLiteDatabase.java (Android 4.0)
/**
* Open the database according to the flags {@link #OPEN_READWRITE}
* {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
*
* <p>Sets the locale of the database to the the system's current locale.
* Call {@link #setLocale} if you would like something else.</p>
*
* <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
* used to handle corruption when sqlite reports database corruption.</p>
*
* @param path to database file to open and/or create
* @param factory an optional factory class that is called to instantiate a
* cursor when query is called, or null for default
* @param flags to control database access mode
* @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption
* when sqlite reports database corruption
* @return the newly opened database
* @throws SQLiteException if the database cannot be opened
*/
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler) {
SQLiteDatabase sqliteDatabase = openDatabase(path, factory, flags, errorHandler,
(short) 0 /* the main connection handle */);
// set sqlite pagesize to mBlockSize
if (sBlockSize == 0) {
// TODO: "/data" should be a static final String constant somewhere. it is hardcoded
// in several places right now.
sBlockSize = new StatFs("/data").getBlockSize();
}
sqliteDatabase.setPageSize(sBlockSize);
sqliteDatabase.setJournalMode(path, "TRUNCATE");
// add this database to the list of databases opened in this process
synchronized(mActiveDatabases) {
mActiveDatabases.add(new WeakReference<sqlitedatabase>(sqliteDatabase));
}
return sqliteDatabase;
}
private static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler, short connectionNum) {
SQLiteDatabase db = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum);
try {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.i(TAG, "opening the db : " + path);
}
// Open the database.
db.dbopen(path, flags);
db.setLocale(Locale.getDefault());
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
db.enableSqlTracing(path, connectionNum);
}
if (SQLiteDebug.DEBUG_SQL_TIME) {
db.enableSqlProfiling(path, connectionNum);
}
return db;
} catch (SQLiteDatabaseCorruptException e) {
db.mErrorHandler.onCorruption(db);
return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
} catch (SQLiteException e) {
Log.e(TAG, "Failed to open the database. closing it.", e);
db.close();
throw e;
}
}
986行目で
openDatabase(String, CursorFactory, int, DatabaseErrorHandler, short) を呼んでいるのですが、そのオープン時に SQLiteDatabaseCorruptException が発生した場合、1024行目で openDatabase(String, CursorFactory, int, DatabaseErrorHandler) を呼んでしまいます。これはそのまま、986行目に行くので、同じメソッドが呼ばれ、そのメソッド内では同じデータベースを開こうとするので、当然
SQLiteDatabaseCorruptException が呼ばれ…という現象が起きてしまうわけです。
報告されたスタックトレースと行番号がずれているので、絶対とは言えませんが、現象からしてこの問題で間違いないと思われます。
この問題の修正方法ですが、 今回の場合、事前にデータベースが壊れているかどうかを知る術がありませんので、
SQLiteDatabase#openDatabase() を try & catchで囲んで、データベースが壊れている旨のエラーメッセージを出すしか回避策はなさそうです。ちなみに、この問題は Android 4.1 では解消しているようですAndroid 3.x 系はソースコードが公開されていないので、わかりません。。
0 件のコメント:
コメントを投稿