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 件のコメント:
コメントを投稿