从源码看Android中sqlite是怎么通过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 }

如果请求查询的位置在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 }

View Code

跟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)泄露。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏落影的专栏

iOS性能优化——图片加载和处理

本文基于WWDC2018-Image and Graphics Best Practices,对图片加载和处理的思考和总结。 本文不是WWDC翻译,如果需要了解...

59017
来自专栏落影的专栏

iOS性能优化——图片加载和处理

本文基于WWDC2018-Image and Graphics Best Practices,对图片加载和处理的思考和总结。 本文不是WWDC翻译,如果需要了...

1303
来自专栏数说工作室

3行代码实现 Python 并行处理,速度提高6倍!

原标题:Here’s how you can get a 2–6x speed-up on your data pre-processing with Pyth...

1295
来自专栏飞雪无情的博客

Go语言实战笔记(二十二)| Go 基准测试

基准测试,是一种测试代码性能的方法,比如你有多种不同的方案,都可以解决问题,那么到底是那种方案性能更好呢?这时候基准测试就派上用场了。

873
来自专栏落影的专栏

H.264学习笔记

H.264组成 1、网络提取层 (Network Abstraction Layer,NAL) 2、视讯编码层 (Video Coding Layer,VCL)...

3115
来自专栏落影的专栏

GPUImage详细解析

从源码的角度分析、学习GPUImage和OpenGL ES,这是第一篇,介绍GPUImageFilter 和 GPUImageFramebuffer。 Open...

3026
来自专栏张善友的专栏

使用Metrics.NET 构建 ASP.NET MVC 应用程序的性能指标

通常我们需要监测ASP.NET MVC 或 Web API 的应用程序的性能时,通常采用的是自定义性能计数器,性能计数器会引发无休止的运维问题(损坏的计数器、权...

1858
来自专栏Linux驱动

16.Linux-LCD驱动(详解)

在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构体: framebuffer_alloc(); 2) 设置...

2519
来自专栏数据结构与算法

cf932E. Team Work(第二类斯特灵数 组合数)

$$m^n = \sum_{i = 0}^m C_{n}^i S(n, i) i!$$

884
来自专栏我是攻城师

图形数据库之Neo4j核心概念介绍(二)

3916

扫码关注云+社区