【源码】CursorWindow读DB
执行QUERY
执行SQLiteDatabase类中query系列函数时,只会构造查询信息,不会执行查询。
(query的源码追踪路径)
执行MOVE(里面的FILLWINDOW是真正打开文件句柄并分配内存的地方)
当执行Cursor的move系列函数时,第一次执行,会为查询结果集创建一块共享内存,即cursorwindow
moveToPosition源码路径
FILLWINDOW----真正耗时的地方
然后会执行sql语句,向共享内存中填入数据,
fillWindow源码路径
在SQLiteCursor.java中可以看到
1
@Override
2
public boolean onMove(int oldPosition, int newPosition) {
3
// Make sure the row at newPosition is present in the window
4
if (mWindow == null || newPosition < mWindow.getStartPosition() ||
5
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
6
fillWindow(newPosition);
7
}
8
9
return true;
10
}
Copied!
如果请求查询的位置在cursorWindow的范围内,不会执行fillWindow,
而超出cursorwindow的范围,会调用fillWindow,
而在nativeExecuteForCursorWindow中,
获取记录时,如果要请求的位置超出窗口范围,会发生CursorWindow的清空:
1
CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
2
if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {
3
// We filled the window before we got to the one row that we really wanted.
4
// Clear the window and start filling it again from here.
5
// TODO: Would be nicer if we could progressively replace earlier rows.
6
window->clear();
7
window->setNumColumns(numColumns);
8
startPos += addedRows;
9
addedRows = 0;
10
cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
11
}
Copied!
CursorWindow的清空机制会影响到多线程读(通常认为不可以并发读写,sqlite的并发实际上是串行执行的,但可以并发读,这里要强调的是多线程读也可能有问题),具体见稍后一篇文章“listview并发读写数据库”。
上面说的这些直观的感受是什么样的呢?大概是这样,
执行query,读10000条数据,很快就拿到了cursor,这里不会卡,
执行moveToFirst,卡一下(fillwindow(0))
moveToPosition(7500),卡一下,因为已经超了cursorwindow的区域,又去fillwindow(7500),
关于fillwindow还有一些奇特的细节,比如4.0以后,fillwindow会填充position前后各一段数据,防止读旧数据的时候又需要fill,感兴趣的同学可以看看各个版本fillwidow的源码。
这里还可以延伸一下,因为高版本的android sqlite对旧版有许多改进,
所以实际开发里我们有时候会把sqlite的源码带在自己的工程里,使得低版本的android也可以使用高版本的特性,并且避开一部分兼容性问题。
CURSOR关闭(显式调用CLOSE()的理由)
追踪源码看关闭
1
//SQLiteCursor
2
3
super.close();
4
synchronized (this) {
5
mQuery.close();
6
mDriver.cursorClosed();
7
}
8
9
10
//AbstractCursor
11
12
public void close() {
13
mClosed = true;
14
mContentObservable.unregisterAll();
15
onDeactivateOrClose();
16
}
17
18
protected void onDeactivateOrClose() {
19
if (mSelfObserver != null) {
20
mContentResolver.unregisterContentObserver(mSelfObserver);
21
mSelfObserverRegistered = false;
22
}
23
mDataSetObservable.notifyInvalidated();
24
}
25
26
27
//AbstractWindowedCursor
28
29
/** @hide */
30
@Override
31
protected void onDeactivateOrClose() {
32
super.onDeactivateOrClose();
33
closeWindow();
34
}
35
36
protected void closeWindow() {
37
if (mWindow != null) {
38
mWindow.close();
39
mWindow = null;
40
}
41
}
42
43
44
45
//SQLiteClosable
46
47
public void close() {
48
releaseReference();
49
}
50
51
public void releaseReference() {
52
boolean refCountIsZero = false;
53
synchronized(this) {
54
refCountIsZero = --mReferenceCount == 0;
55
}
56
if (refCountIsZero) {
57
onAllReferencesReleased();
58
}
59
}
60
61
//CursorWindow
62
63
@Override
64
protected void onAllReferencesReleased() {
65
dispose();
66
}
67
68
private void dispose() {
69
if (mCloseGuard != null) {
70
mCloseGuard.close();
71
}
72
if (mWindowPtr != 0) {
73
recordClosingOfWindow(mWindowPtr);
74
nativeDispose(mWindowPtr);
75
mWindowPtr = 0;
76
}
77
}
Copied!
跟CursorWindow有关的路径里,最终调用nativeDispose()清空cursorWindow;
当Cursor被GC回收时,会调用finalize:
1
@Override
2
protected void finalize() {
3
try {
4
// if the cursor hasn't been closed yet, close it first
5
if (mWindow != null) {
6
if (mStackTrace != null) {
7
String sql = mQuery.getSql();
8
int len = sql.length();
9
StrictMode.onSqliteObjectLeaked(
10
"Finalizing a Cursor that has not been deactivated or closed. " +
11
"database = " + mQuery.getDatabase().getLabel() +
12
", table = " + mEditTable +
13
", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
14
mStackTrace);
15
}
16
close();
17
}
18
} finally {
19
super.finalize();
20
}
21
}
Copied!
然而finalize()并没有释放CursorWindow,而super.finalize();里也只是解绑了观察者,没有去释放cursorwindow
所以不调用cursor.close(),最终会导致cursorWindow所在的共享内存(1M或2M)泄露。
复制链接