closuresContentProvider中的数据库
本周我一直在学习关于ContentProvider的所有内容,并使用SQLiteOpenHelper类来pipe理提供者内部数据库的创build和升级。 具体来说,我一直在通过sdk的samples目录中的NotePad例子。
现在,我可以看到SQLiteOpenHelper有一个close()方法。 我知道,将闲置的数据库打开是不好的做法,可能导致内存泄漏,而不是(除非这个讨论是朝着正确的方向进行的)。 如果我在一个Activity中使用这个类,那么我只需要在onDestroy()方法中调用close()方法,但据我所知,ContentProvider没有活动所用的生命周期。 NotePad的代码似乎永远不会调用close(),所以我想假设它由SQLiteOpenHelper或其他一些难题处理,但我真的很想知道。 我不太相信样例代码,或者…
问题摘要:我们应该什么时候closures供应商的数据库?
根据Dianne Hackborn (Android框架工程师),不需要closures内容提供者中的数据库。
内容提供者是在创build主机进程时创build的,只要进程执行,内容提供者就会保留,所以不需要closures数据库 – 当内核提供者进程被杀害。
感谢@bigstones指出这一点。
这个问题有点老,但是还是相当有用的。 请注意,如果你正在做'现代'的方式(例如使用LoaderManager并创buildCursorLoaders来在后台线程中查询ContentProvider),请确保在ContentProvider实现中不要调用db.close() 。 当它试图访问后台线程中的ContentProvider时,我得到了与CursorLoader / AsyncTaskLoader有关的各种崩溃,通过删除db.close()调用来解决这个问题。
所以如果你遇到像这样的崩溃(果冻豆4.1.1):
Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed. at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962) at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:677) at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348) at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894) at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834) at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133) at android.content.ContentResolver.query(ContentResolver.java:388) at android.content.ContentResolver.query(ContentResolver.java:313) at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:147) at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:1) at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) ... 4 more
或(ICS 4.0.4):
Caused by: java.lang.IllegalStateException: database /data/data/com.hindsightlabs.paprika/databases/Paprika.db (conn# 0) already closed at android.database.sqlite.SQLiteDatabase.verifyDbIsOpen(SQLiteDatabase.java:2215) at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:436) at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:422) at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:79) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156) at android.content.ContentResolver.query(ContentResolver.java:318) at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:49) at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:35) at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) ... 4 more
或者,如果您看到LogCat中的错误消息,如下所示:
Cursor: invalid statement in fillWindow()
然后检查你的ContentProvider实现,并确保你没有提前closures数据库。 据此,当进程被终止时,ContentProvider将被自动清除,所以你不需要提前closures它的数据库。
这就是说,确保你仍然是正确的:
- closures从ContentProvider.query()返回的游标。 (CursorLoader / LoaderManager自动为你做这件事,但是如果你在LoaderManager框架之外进行直接查询,或者你已经实现了一个自定义的CursorLoader / AsyncTaskLoader子类,那么你必须确定你正在清理你的游标正确的。)
- 以线程安全的方式实现您的ContentProvider。 (最简单的方法是确保您的数据库访问方法包装在同步块中。)
我遵循Mannaz的回答,看到SQLiteCursor(database, driver, table, query);
构造函数已被弃用。 然后我find了getDatabase()
方法,并用它来代替mDatabase
指针; 并保持build设者的落后能力
public class MyOpenHelper extends SQLiteOpenHelper { public static final String TAG = "MyOpenHelper"; public static final String DB_NAME = "myopenhelper.db"; public static final int DB_VESRION = 1; public MyOpenHelper(Context context) { super(context, DB_NAME, new LeaklessCursorFactory(), DB_VESRION); } //... } public class LeaklessCursor extends SQLiteCursor { static final String TAG = "LeaklessCursor"; public LeaklessCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { super(db, driver, editTable, query); } @Override public void close() { final SQLiteDatabase db = getDatabase(); super.close(); if (db != null) { Log.d(TAG, "Closing LeaklessCursor: " + db.getPath()); db.close(); } } } public class LeaklessCursorFactory implements CursorFactory { @Override public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { return new LeaklessCursor(db,masterQuery,editTable,query); } }
如果你想让你的数据库自动closures,你可以在打开时提供一个CursorFactory
:
mContext.openOrCreateDatabase(DB_NAME, SQLiteDatabase.OPEN_READWRITE, new LeaklessCursorFactory());
这里是类:
public class LeaklessCursorFactory implements CursorFactory { @Override public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { return new LeaklessCursor(db,masterQuery,editTable,query); } } public class LeaklessCursor extends SQLiteCursor { static final String TAG = "LeaklessCursor"; final SQLiteDatabase mDatabase; public LeaklessCursor(SQLiteDatabase database, SQLiteCursorDriver driver, String table, SQLiteQuery query) { super(database, driver, table, query); mDatabase = database; } @Override public void close() { Log.d(TAG, "Closing LeaklessCursor: " + mDatabase.getPath()); super.close(); if (mDatabase != null) { mDatabase.close(); } } }
完成后closures它,最好在finally块中,这样可以确保它发生。 我知道这听起来有点古怪,但是这是我知道的唯一答案。 如果您打开数据库并执行某个操作,请在完成该操作后closures该操作,除非您知道该操作将再次需要(在这种情况下,确保在不再需要时closures该操作)。
如果你在一个活动中使用你的内容提供者,那么我不认为你必须保持内容提供者的连接。 你可以pipe理使用startManagingCursor返回的游标对象。 在onPause活动方法中,您可以释放内容提供者。 (你可以重新加载它onResume)。 假设活动的生命周期通常是有限的,这就足够了。 (至less根据我;))