2012-07-18 63 views
7

鄉親們寫的SQLite數據庫推薦的設計模式,在Android電子

我在尋找一個設計模式,使一個UI線程與客戶端的SQLite數據庫進行交互,可能有批量插入(服用10S秒),快速插入和讀取,並且不會阻塞UI線程。

我想諮詢一下我是否正在使用最佳設計模式,因爲我最近一直在調試死鎖和同步問題,並且我對最終產品並不是100%信心十足。

所有數據庫訪問現在都是通過一個單例類的瓶頸。下面是僞代碼顯示我怎麼在我的單身接近寫入,DataManager的:

public class DataManager { 

    private SQLiteDatabase mDb; 
    private ArrayList<Message> mCachedMessages; 

    public ArrayList<Message> readMessages() { 
    return mCachedMessages; 
    } 

    public void writeMessage(Message m) { 
    new WriteMessageAsyncTask().execute(m); 
    } 

    protected synchronized void dbWriteMessage(Message m) { 
    this.mDb.replace(MESSAGE_TABLE_NAME, null, m.toContentValues()); 
    } 

    protected ArrayList<Message> dbReadMessages() { 
    // SQLite query for messages 
    } 

    private class WriteMessageAsyncTask extends AsyncTask<Message, Void, ArrayList<Messages>> { 
    protected Void doInBackground(Message... args) { 
     DataManager.this.mDb.execSQL("BEGIN TRANSACTION;"); 
     DataManager.this.dbWriteMessage(args[0]); 
     // More possibly expensive DB writes 
     DataManager.this.mDb.execSQL("COMMIT TRANSACTION;"); 
     ArrayList<Messages> newMessages = DataManager.this.dbReadMessages(); 
     return newMessages; 
    } 
    protected void onPostExecute(ArrayList<Message> newMessages) { 
     DataManager.this.mCachedMessages = newMessages; 
    } 
    } 
} 

亮點:

  • 第一:通過的AsyncTask所有公共寫操作(的WriteMessage)發生,從來沒有在主 螺紋
  • 下一頁:所有的寫操作是同步的,幷包裹在 BEGIN交易
  • 下一頁:閱讀操作 非同步的,因爲它們不需要寫
  • 期間阻止最後:讀操作的結果顯示在onPostExecute緩存主 螺紋

這是否代表着Android的最佳實踐編寫潛在的大量的數據到SQLite數據庫,同時最大限度地減少對UI線程的影響?上面看到的僞代碼是否存在明顯的同步問題?

更新

有一個在我的代碼顯著錯誤之上,其計算方法如下:

DataManager.this.mDb.execSQL("BEGIN TRANSACTION;"); 

該行獲取數據庫鎖。但是,它是一個DEFERRED鎖,因此在寫入之前,other clients can both read and write

DataManager.this.dbWriteMessage(args[0]); 

該行實際上修改了數據庫。此時,該鎖是一個保留鎖,因此其他客戶端可能無法寫入。

請注意,在第一個dbWriteMessage調用之後,可能會有更昂貴的數據庫寫入操作。假定每個寫操作都發生在受保護的同步方法中。這意味着在DataManager上獲取鎖定,寫入發生,鎖定被釋放。如果WriteAsyncMessageTask是唯一的作者,這沒問題。

現在我們假設還有一些其他任務也可以執行寫操作,但不使用事務(因爲它是快速寫入)。下面是它可能看上去是這樣的:

private class WriteSingleMessageAsyncTask extends AsyncTask<Message, Void, Message> { 
    protected Message doInBackground(Message... args) { 
     DataManager.this.dbWriteMessage(args[0]); 
     return args[0]; 
    } 
    protected void onPostExecute(Message newMessages) { 
     if (DataManager.this.mCachedMessages != null) 
     DataManager.this.mCachedMessages.add(newMessages); 
    } 
    } 

在這種情況下,如果WriteSingleMessageAsyncTask在同一時間WriteMessageAsyncTask執行和WriteMessageAsyncTask至少執行一個寫則已,有可能WriteSingleMessageAsyncTask調用dbWriteMessage,獲得鎖定在DataManager上,但由於RESERVED鎖定而被阻止完成其寫入。 WriteMessageAsyncTask重複獲取並放棄DataManager上的鎖定,這是一個問題。

結論:結合事務和單例對象級鎖定可能導致死鎖。確保在開始事務之前擁有對象級鎖。

的修復到我原來的WriteMessageAsyncTask類:

synchronized(DataManager.this) { 
    DataManager.this.mDb.execSQL("BEGIN TRANSACTION;"); 
    DataManager.this.dbWriteMessage(args[0]); 
    // More possibly expensive DB writes 
    DataManager.this.mDb.execSQL("COMMIT TRANSACTION;"); 
    } 

更新2

看看這個視頻從谷歌I/O 2012: http://youtu.be/gbQb1PVjfqM?t=19m13s

它提出了一種設計模式利用內置的專屬交易,然後使用yieldIfContendedSafely

+0

如果我可以建議在性能部分的東西:有一兩件事我注意到的是,一旦你寫了新的消息,你似乎正在讀取db中的* entire *消息並設置mCachedMessages。除此之外,您可以簡單地將新添加的消息添加到mCachedMessages列表中?這將節省很多數據庫讀取。 – 2012-07-18 06:56:13

+0

@AswinKumar這是真的;我的意思是這是一個簡單的例子來代表圍繞同步的最佳實踐。性能優化的確可以簡單地添加到已存在的緩存列表中。 – esilver 2012-07-18 18:12:16

回答

2

我不能說太多關於同步/死鎖部分,這將非常依賴於其他代碼。由於DataManager類並未真正與UI進行交互,因此您可能需要使用服務(IntentService)而不是AsyncTask。完成同步後,您可以顯示通知。如果您不調用UI代碼,則不需要onPostExecute()

+0

假設UI確實與DataManager交互 - 即當Activity啓動,恢復或接收到有可用更新的新消息時,UI線程將調用dataManager.readMessages()。 – esilver 2012-07-18 06:42:05

+0

重要的是DataManager是否執行任何直接涉及UI線程的任務,即更新TextView上的文本等。如果某些活動方法通過DataManager訪問全局緩存數據,該數據不會與UI進行交互:) – 2012-07-18 06:48:35

+1

我正在尋找一種設計模式,使UI線程可以與可能已批量插入,快速插入和讀取的客戶端SQLite數據庫進行交互,並且不會阻止UI線程。儘管可以讓所有SQLite數據庫訪問都通過IntentService,但似乎可能是矯枉過正。 – esilver 2012-07-18 17:56:39

1

你可能要考慮從SDK(http://developer.android.com/reference/android/os/AsyncTask.html)

當第一次推出,AsyncTasks被連續執行對單個 後臺線程。用DONUT開始,這被更改爲 線程池此信息允許多個任務並行工作,與 蜂窩開始,任務是在單個線程中執行,以避免因並行執行共同 應用程序錯誤。

如果你真的想並行執行,你可以在vke executeOnExecutor(java.util.concurrent.Executor,Object [])與 THREAD_POOL_EXECUTOR。

0

僅供參考,SQLite上運行的每個SQL語句都在事務下運行,即使您沒有指定一個。

檢查下面的線程,如果你在SQLite的做批量插入:

  1. Android Database Transaction
  2. SQLite Bulk Insert
+2

謝謝 - 第一個鏈接是使用事務的好介紹,第二個鏈接對性能有好處。我正在尋找一個嚴格正確的設計模式來處理批量插入,短插入和讀取,並且不會阻塞UI線程。 – esilver 2012-07-18 17:55:15