1. 程式人生 > >原始碼分析SharePreferences的apply與commit的區別

原始碼分析SharePreferences的apply與commit的區別

apply與commit有什麼區別

還是從原始碼分析來說明問題:
image.png

image.png

接上圖示記B1處,看下:

image.png

所以這個handler傳送訊息後,handleMessage會被呼叫看下:
image.png
image.png
上圖提到的Runnable物件是哪個呢??就是enqueueDiskWrite方法裡的writeToDiskRunnable物件:

image.png

接著分析上圖提到的標記C1處:
image.png
上上圖提到到寫完檔案操作之後,會呼叫postWriteRunnable的run來移除任務:

所以對於apply要有一個重要的認識就是:會建立一個子執行緒來執行寫操作,這個子執行緒是支援訊息迴圈機制的。

這裡還有一個疑問就是在上文開始處標記A提到的,為什麼入隊進行寫操作之前要先 QueuedWork.addFinisher(awaitCommit);,而在寫操作完成後又呼叫 QueuedWork.removeFinisher(awaitCommit),關於這一點你可以這麼理解:QueuedWork.addFinisher(awaitCommit)可以理解為標記開始寫操作狀態,QueuedWork.removeFinisher(awaitCommit)則理解為標記寫操作結束狀態,為何要如此標記呀?要做標記肯定是要給人看的,到底給誰看呢?這裡我舉一個例子當Activity生命週期要回調onStop的時候,會呼叫ActivityThread中的handleStopActivity方法,關於這一點你不明白的話可以參考我的文章

Activity生命週期回撥是如何被回撥的?
image.png
看到上圖會呼叫QueueWork的waitToFinish,而這個方法則是會執行沒有完成的寫操作,導致主執行緒被阻塞。所以這裡可以得到一個結論apply雖然是另開執行緒執行寫操作,仍然有可能阻塞主執行緒,上面的例子就是情景之一。如果在Activity Stop的時候,已經寫入完畢了,那麼萬事大吉,不會有任何等待,這個函式會立馬返回。但是,如果你使用了太多次的apply,那麼意味著寫入佇列會有很多寫入任務,而那裡就只有一個執行緒在寫。

如何解決apply導致的ANR問題

下面是頭條的方案,主要的原理就是Hook ActivityThread中的Handler 物件,這個handler物件是static,
然後再為這個handler物件設定callback,這樣handler會呼叫callback物件的handleMessage方法,只要我們重寫這個callback方法邏輯,就可以跳過原來系統程式碼,詳情參考

頭條解決多次呼叫apply產生ANR的方案

接下來看看commit方法:

image.png

image.png

image.png

到此兩個方法都分析完了,做下總結如下:

  • 如果是在主執行緒直接使用建議使用apply
  • apply和commit都可以導致ANR,apply雖然在處理寫檔案操作時會在另一個執行緒來處理,但是多次呼叫情況下可能導致ANR,而commit則是直接在當前執行檔案寫操作。