SQLite數據庫學習小結——Frameworks層實現

3. SQLite的Frameworks層實現

3.1 Frameworks層架構

    Android系統方便應用使用,在Frameworks層中封裝了一套Content框架,之因此叫Content框架而不叫數據庫框架之類的,是由於這裏Content不必定是來自數據庫的內容,也能夠是來自其餘數據源的內容,開發人員只須要知道如何使用ContentResovler和ContentProvider就能夠在應用進程之間共享數據了。java

  這裏咱們只討論數據源是數據庫的ContentProvider,開發人員須要實現一個SQLiteOpenHelper的派生類,它使用了一系列SQLite相關的類封裝了native層的SQLite動態庫的接口方法,那麼SQLite在Frameworks層是如何封裝的,咱們在使用SQLite時又須要注意些什麼呢?android

    咱們先來看一看基於SQLite的Content框架的總體架構:sql

    Android系統在Frameworks層數據庫

  一、ContentResolver:Content框架的客戶端,對應用提供增、刪、改、查的接口方法,經過Authority指定目標Content服務,並鏈接到服務端執行數據操做。 
  二、ContentProvider:Content框架的服務端,Android應用四大組件之一,經過Authority註冊到PMS,在運行時有客戶端請求時由AMS調度來響應客戶端的數據操做。 
  三、SQLiteOpenHelper:管理SQLite的幫助類,提供獲取SQLIteDatabase實例的方法,它會在第一次使用數據庫時調用獲取實例方法時建立SQLiteDatabase實例,而且處理數據庫版本變化,開發人員在實現ContentProvider時都要實現一個自定義的SQLiteOpenHelper類,處理數據的建立、升級和降級。
   注意: 

    (1)不論是調用getWritableDatabase方法仍是getReadableDatabase方法,SQLIteOpenHelper都會以可讀寫模式打開數據庫。編程

    (2)若是應用程序想以WAL模式打開數據庫,可在自定義SQLiteOpenHelper類的構造方法中調用setWriteAheadLoggingEnabled(true)。 設計模式

    SQLiteOpenHelper.java 架構

     

  四、SQLiteDatabase:表明一個打開的SQLite數據庫,提供了執行數據庫操做的接口方法。若是不須要在進程之間共享數據,應用程序也能夠自行建立這個類的實例來讀寫SQLite數據庫。 
  五、SQLiteSession:SQLiteSession負責管理數據庫鏈接和事務的生命週期,經過SQLiteConnectionPool獲取數據庫鏈接來執行具體的數據庫操做。 

  六、SQLiteConnectionPool:數據庫鏈接池,管理全部打開的數據庫鏈接(Connection)。全部數據庫鏈接都是經過它來打開,打開後會加入鏈接池,在讀寫數據庫時須要從鏈接池中獲取一個數據庫鏈接來使用。 併發

  七、SQLiteConnection:表明了數據庫鏈接,每一個Connection封裝了一個native層的sqlite3實例,經過JNI調用SQLite動態庫的接口方法操做數據庫,Connection要麼被Session持有,要麼被鏈接池持有。 框架

  八、CursorFactory:可選的Cursor工廠,能夠提供自定義工廠來建立Cursor。 ide

  九、DatabaseErrorHandler:可選的數據庫異常處理器(目前僅處理數據庫Corruption),若是不提供,將會使用默認的異常處理器。 

  十、SQLiteDatabaseConfiguration:數據庫配置,應用程序能夠建立多個到SQLite數據庫的鏈接,這個類用來保證每一個鏈接的配置都是相同的。 

  十一、SQLiteQuery和SQLiteStatement:從抽象類SQLiteProgram派生,封裝了SQL語句的執行過程,在執行時自動組裝待執行的SQL語句,並調用SQLiteSession來執行數據庫操做。這兩個類的實現應用了設計模式中的命令模式。 

3.2 關鍵模塊實現

    本節介紹幾個關鍵模塊的實現和使用時須要注意的事項。

3.2.1 SQLiteSession

  Android系統Frameworks層的數據庫讀寫操做都是經過SQLiteSession完成的,SQLiteSession負責管理數據庫鏈接和事務的生命週期。

  一個SQLiteDatabse實例能夠同時持有多個活躍的Session(可是爲防止死鎖,每一個線程只能持有一個DB的Session),每一個Session在執行SQL語句時獲取數據庫鏈接,在SQL語句執行結束後釋放數據庫鏈接,Session只有在只執行SQL語句期間保持數據庫鏈接,執行完後就釋放了。這個特性也是鏈接池的實現基礎。

SQLiteSession.java

  若是鏈接池中全部鏈接都已分配出去了,那麼獲取鏈接的SQLiteSession會阻塞直到有可用鏈接爲止。

  因此,在使用SQLite時須要注意如下幾點: 

  (1)不要在UI線程中執行數據庫操做。 

  (2)執行的事務儘可能短。 

  (3)若是讀寫事務很長,能夠考慮使用yieldTransaction()方法先提交部分事務,給其餘事務執行的機會。

3.2.2 SQLiteConnectionPool

  數據庫鏈接池保持全部打開的數據庫鏈接,在任什麼時候候,一個數據庫鏈接要麼被鏈接池持有,要麼被一個SQLiteSession持有,若是SQLiteSession使用完數據庫鏈接,必須把它還給鏈接池。若是鏈接池中全部的鏈接都已被佔用,則待執行的事務要等待有空閒的鏈接才能執行。

  目前Android系統的實現中,若是以非WAL模式打開數據庫,鏈接池中只會保持一個數據庫鏈接,若是以WAL模式打開數據庫,鏈接池中的最大鏈接數量則根據系統配置決定,默認配置是兩個。

SQLiteConnectionPool.java:

  雖然名爲鏈接池,可是從源碼來看,目前實現的池中只有一個數據庫鏈接(之後的Android版本可能會擴展),因此若是應用程序中有大量的併發數據庫讀和寫操做的話,每一個操做的時長均可能受到影響,因此數據庫操做應放在工做線程中執行,以避免影響UI響應。

3.2.4 Cursor

  從ContentProvider查詢的數據結果是放在Cursor中返回給客戶端的,在客戶端看來Cursor就是一個數據容器,但隱藏在Cursor後面的實現方式很靈活,它的數據既能夠不是從數據庫返回的,也能夠是在使用時才真正加載的,很好的體現了面向對象編程的特性和優勢。

  在Frameworks中把Cursor定義爲了一個接口,它的定位是能夠隨機訪問的數據集,在接口中定義了訪問數據集的通用方法。業務能夠根據本身的須要實現一個Cursor,具體怎麼實現接口的方法由具體實現決定,因此Cursor有不少子類來知足不一樣場景的須要。

  經過SQLiteDatabase返回的就是其中的SQLiteCursor,若是數據不是從數據庫返回的,開發人員也能夠在ContentProvider中動態建立一個MatrixCursor,而後填充數據並返回給客戶端。

  從Cursor接口和其派生類的定義來看它們都沒有實現Parcelable接口,那麼它是怎麼跨進程傳遞的呢?這須要Cursor首先要解決兩個問題。

  第一個問題:Cursor沒有實現Parcelable接口,一個Cursor實例怎麼跨進稱傳遞呢?答案是傳遞的不是具體數據,而是Binder引用,即在ContentProvider端建立Cursor的Binder服務端實例,而後把Binder應用傳遞給客戶端,在客戶端經過這個Binder引用跨進程獲取查詢到的數據的。這裏Frameworks定義了一個接口:IBulkCursor。

  IBulkCursor定義了跨進程的Cursor須要實現的接口方法,其中getWindow()用來得到數據窗口,onMove()用來移動Cursor的位置。

  那麼第二個問來了:咱們知道經過Binder傳遞的數據大小有限(1MB),而查詢到的數據大小可能超出限制,那麼怎麼跨進程傳遞數據呢?既然數據大小不定,那麼咱們就不經過Binder傳遞數據了,而是經過共享內存傳遞數據,這塊共享內存是封裝在CursorWindow中的。

  CursorWindow就是數據窗口,它在服務端分配(窗口大小有Android系統配置決定)並傳遞到客戶端,客戶端再映射到本身的進程空間中,這樣,服務端填充的數據就能夠被客戶端讀取到了。上面IBulkCursor接口中定義的getWindow()方法就是獲取CursorWindow的。

  CursorWindow在初始化時是空的,在調用Cursor的moveToXXX方法時會經過IBulkCursor的onMove()方法調用服務端的Cursor去填充數據窗口的內容。

CursorWindow.java

frameworks\base\libs\androidfw\CursorWindow.cpp

  服務端建立共享內存。

CursorWindow.java

frameworks\base\libs\androidfw\CursorWindow.cpp

  客戶端映射共享內存到進程內存空間。

  上面知道了Frameworks解決跨進程傳遞Cursor數據的思路,咱們再來看下具體執行跨進程傳遞數據的類:CursorToBulkCursorAdapter(服務端)和BulkCursorToCursorAdapter(客戶端)。

服務端:

客戶端:

  在ContentProviderNatvie類中能夠看到Cursor的專遞過程。

服務端:

ContentProviderNative.onTransact()

CursorToBulkCursorAdaptor.java

  服務端在經過ContentProvider獲得Cursor後,用它建立一個CursorToBulkCursorAdaptor實例,而後把adaptor封裝在一個實現了Parcelable接口的BulkCursorDescriptor實例中返回給客戶端。

  雖然說是在使用時才填充數據窗口,可是實際上傳遞Cursor的過程當中,從上面代碼能夠看到服務端已經替應用程序填充過一次數據了:mCursor.getCount()。

SQLiteCursor.java

客戶端:

ContentProviderProxy.query()

BulkCursorToCursorAdaptor.java 

    在獲得服務端返回的數據後建立一個BulkCursorDescriptor實例,在用它初始化一個BulkCursorToCursorAdapter實例返回給應用程序使用。 

相關文章
相關標籤/搜索