更多內容在這里查看
https://ahangchen.gitbooks.io/windy-afternoon/content/
執行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 }
如果請求查詢的位置在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 }
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 }
跟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 }
然而finalize()並沒有釋放CursorWindow,而super.finalize();里也只是解綁了觀察者,沒有去釋放cursorwindow
所以不調用cursor.close(),最終會導致cursorWindow所在的共享內存(1M或2M)泄露。