执行query
执行SQLiteDatabase类中query系列函数时,只会构造查询信息,不会执行查询。
(query的源码追踪路径)
执行move(里面的fillwindow是真正打开文件句柄并分配内存的地方)
当执行Cursor的move系列函数时,第一次执行,会为查询结果集创建一块共享内存,即cursorwindow
moveToPosition源码路径
fillWindow----真正耗时的地方
然后会执行sql语句,向共享内存中填入数据,
fillWindow源码路径
在SQLiteCursor.java中可以看到
代码语言:javascript复制 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 }
如果请求查询的位置在cursorWindow的范围内,不会执行fillWindow,
而超出cursorwindow的范围,会调用fillWindow,
而在nativeExecuteForCursorWindow中,
获取记录时,如果要请求的位置超出窗口范围,会发生CursorWindow的清空:
代码语言:javascript复制 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 }
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()的理由)
追踪源码看关闭
代码语言:javascript复制 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 }
View Code
跟CursorWindow有关的路径里,最终调用nativeDispose()清空cursorWindow;
当Cursor被GC回收时,会调用finalize:
代码语言:javascript复制 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 }
然而finalize()并没有释放CursorWindow,而super.finalize();里也只是解绑了观察者,没有去释放cursorwindow
所以不调用cursor.close(),最终会导致cursorWindow所在的共享内存(1M或2M)泄露。