前言
Android中数据的持久化经常会使用到SqliteDatabase或是ContentProvider,查询数据的时候可以通过SqliteDatabase.query或是ContentResolver.query方法来获取一个Cursor对象,这个Cursor对象里面就包含了我们所要查找的数据。在之前的认知里Cursor在使用完毕后必须要主动调用close来关闭释放资源。最近发现项目里有很多地方并没有做回收处理,编译器也没有任何提示。为了搞清楚这个问题,把以前写的Demo找了出来,并结合ContentProvider和SqliteDatabase相关源码来分析一下具体原因。
SqliteDatabase
SqliteDatabase中提供的query开头的查询方法最后都会通过queryWithFactory方法来查询,而queryWithFactory最终会调用rawQueryWithFactory来执行查询,我们直接来跳转到rawQueryWithFactory方法。
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
acquireReference();
try {
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
cancellationSignal);
return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
selectionArgs);
} finally {
releaseReference();
}
}
发现最后返回的地方会去调用SQLiteDirectCursorDriver的query方法,继续跳。
public Cursor query(CursorFactory factory, String[] selectionArgs) {
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
final Cursor cursor;
try {
// 把所有数据绑定到SQLiteQuery的对象query中
query.bindAllArgsAsStrings(selectionArgs);
// 封装成Cursor对象
if (factory == null) {
cursor = new SQLiteCursor(this, mEditTable, query);
} else {
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
} catch (RuntimeException ex) {
query.close();
throw ex;
}
mQuery = query;
return cursor;
}
SQLiteDirectCursorDriver.query方法中会先把所有数据绑定到SQLiteQuery的对象query中,然后根据CursorFactory对象是否为空来决定如何创建Cursor对象,如果为空则直接new一个SQLiteCursor对象,如果非空则需要实现CursorFactory接口类的newCursor方法来返回一个Cursor对象。
public interface CursorFactory {
/**
* See {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}.
*/
public Cursor newCursor(SQLiteDatabase db,
SQLiteCursorDriver masterQuery, String editTable,
SQLiteQuery query);
}
最后,由SQLiteCursor类的构造方法把相关数据封装起来并创建Cursor对象。
public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
if (query == null) {
throw new IllegalArgumentException("query object cannot be null");
}
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
} else {
mStackTrace = null;
}
mDriver = driver;
mEditTable = editTable;
mColumnNameMap = null;
mQuery = query;
mColumns = query.getColumnNames();
}
Object类是类层次结构的根类,每个类都使用Object作为父类,Object类中定义了finalize方法,当Java触发GC就会回调finalize方法。SQLiteCursor类中重写了finalize方法,在这个方法中会主动调用Cursor.close方法来释放资源。
/**
* Release the native resources, if they haven't been released yet.
*/
@Override
protected void finalize() {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
if (mStackTrace != null) {
String sql = mQuery.getSql();
int len = sql.length();
StrictMode.onSqliteObjectLeaked(
"Finalizing a Cursor that has not been deactivated or closed. " +
"database = " + mQuery.getDatabase().getLabel() +
", table = " + mEditTable +
", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
mStackTrace);
}
close();
}
} finally {
super.finalize();
}
}
ContentProvider
ContentProvider一般是用于存储应用间可共享的数据,应用可以通过ContentResolver提供的方法来对数据进行增删改查操作,同SqliteDatabase仍然从ContentResolver的query方法入手。
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal) {
...
// Wrap the cursor object into CursorWrapperInner object.
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
stableProvider = null;
qCursor = null;
return wrapper;
...
}
相较于SqliteDatabase的查找过程,使用ContentResolver查找相对简单,在ContentResolver.query方法中经过一些处理后会返回一个CursorWrapperInner类对象。从其构造方法可知,这个CursorWrapperInner对象包含了IContentProvider接口对象的信息。
CursorWrapperInner(Cursor cursor, IContentProvider contentProvider) {
super(cursor);
mContentProvider = contentProvider;
mCloseGuard.open("close");
}
同样,在CursorWrapperInner类中重写了Object类的finalize方法,在这个方法中同样会调用close方法来释放资源。
@Override
public void close() {
mCloseGuard.close();
super.close();
if (mProviderReleased.compareAndSet(false, true)) {
ContentResolver.this.releaseProvider(mContentProvider);
}
}
@Override
protected void finalize() throws Throwable {
try {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
close();
} finally {
super.finalize();
}
}
总结
从对SqliteDatabase和ContentResolver相关源码的分析可以看出,两种方法最终返回的Cursor类对象都重写了Object类的finalize方法,即使不主动调用Cursor.close方法也不会造成内存泄露。虽然不会造成泄露,但是会消耗IO资源有可能导致native运行异常,所以最好还是手动调用Cursor.close方法及时回收内存。
如果不小心忘记关闭Cursor了怎么办?有什么方法来快速确定是否有泄漏?可以通过一下几种方法来快速定位问题。
- 编写代码的时候注意编译器提示
如下图所示,如果我把cursor.close注销掉编译器就会提示我需要调用close()方法来释放资源。
- 编写完代码跑一遍Lint
跑Lint的时候,生成的报告中会有相应的警报,可以直接定位问题代码。
- 在项目中集成leakcanary
leakcanary github地址:https://github.com/square/leakcanary
网友评论