2014-02-27 149 views
0

我有一個簡單的應用程序,一個活動和一個服務。SharedPreferences沒有保存或完全清除時間

該活動有一個切換按鈕來啓動和停止服務。該服務有通知顯示它何時啓動,何時啓動。通知文本由活動設置,存儲在SharedPreferences中,並在服務內讀取onCreate()

切換按鈕在啓動/停止服務時保存布爾型serviceStarted,所以當活動啓動時,如果之前啓動了服務,我可以重新啓動服務。

我的錯誤是:有時,serviceStarted未正確保存。我可以通過查看sharedPreferences文件來看到這一點。它大部分時間都在工作,但是從時間到時間,它都不起作用。 SharedPreferences實例具有正確的值,但該文件不正確。而且事件發生的頻率較低,但它發生時,首選項xml文件完全消失。

我的首選項文件只有兩個值:serviceStarted布爾值和一個包含通知文本的短字符串。

肘的點擊監聽器:

public void onClick(View v) 
{ 
    ToggleButton toggle = (ToggleButton) v; 
    boolean mustStartService = toggle.isChecked(); 
    if(mustStartService) 
    { 
     Intent serviceIntent = new Intent(MainActivity.this, MainService.class); 
     startService(serviceIntent); 
    } 
    else 
    { 
     Intent serviceIntent = new Intent(MainActivity.this, MainService.class); 
     stopService(serviceIntent); 
    } 

    // Save to SharedPreferences 
    SharedPreferences settings = getSharedPreferences("prefs", Context.MODE_MULTI_PROCESS); 
    SharedPreferences.Editor editor = settings.edit(); 
    editor.putBoolean("serviceStarted", mustStartService); 
    editor.commit(); 
} 

注:

  • 服務放在另一個進程,這樣系統就可以從活動relese資源一旦消失,從而用較少的資源保持服務/通知。因此,我使用國旗MODE_MULTI_PROCESS

  • commit()調用返回true,即使寫入看起來不正確或文件被刪除。我在logcat中從SharedPreferencesImpl沒有任何錯誤。

  • 唯一的其他寫入首選項的地方是來自活動中的偵聽器(當TextView發生更改時),但是當發生錯誤時我從不調用此代碼,所以不應該存在寫衝突。該服務從不寫入首選項。

兩個對象的定義是這樣的清單:

<activity 
    android:name=".MainActivity" 
    android:label="@string/app_name" 
    android:launchMode="singleTask"> 
    <intent-filter> 
     <action android:name="android.intent.action.MAIN"/> 
     <category android:name="android.intent.category.LAUNCHER"/> 
    </intent-filter> 
</activity> 
<service 
    android:name=".MainService" 
    android:process=":mainService" 
    android:exported="false"> 
</service> 

任何幫助將apreciated。我知道我可以使用另一種方式來存儲偏好設置,建議here,但我想知道我是不是錯過了一些東西。

+0

首先,如果這有助於嘗試http://stackoverflow.com/questions/10186215/sharedpreferences-value-is-not-updated – AndyFaizan

+0

不,它不會改變 – biskitt

+0

如果你寫一個pref _with null key_,首選項將是目前正在重新加載清除。看看那 –

回答

0

我通過查看來自Android sourcesSharedPreferencesImpl代碼發現了該錯誤。我會盡力解釋它,因爲它可能對其他人有用。

我應該採取更多的關注在這個筆記,從SharedPreferences doc

注:目前這一類不支持使用多個 過程。這將在稍後添加。

我以爲文檔不是最新的,並且使用MODE_MULTI_PROCESS標誌就足夠了。我錯了。雖然需要使SharedPreferences在同一應用程序的多個進程上工作,但它與真正的「多進程支持」無關,處理所有情況。

SharedPreferences幾點說明:

  • 基本上,SharedPreferences被存儲在設備上的XML文件。首次加載時,Android會將您的SharedPreferences保存在內存中。所以每次你用相同的名稱調用getSharedPreferences()時,它會返回相同的對象,並避免多次讀取/解析文件。

  • 如果您使用MODE_MULTI_PROCESS flag(或版本< = Android 2.3),它將檢查自上次訪問以來xml文件是否未被修改。因此,如果另一個進程修改了該文件,該文件將被重新讀取。

  • 保存或加載首選項時,系統會使用SharedPreferences xml的備份文件。我的所有醜事都發生在這裏。

  • 當您在您的SharedPreferences中保存修改時,使用commit()或apply(),系統首先備份先前的文件,刪除它,保存新文件,然後刪除備份(假設沒有錯誤)。在僞代碼中,它提供了:

  • 當您從文件中讀取首選項時,如果有備份,則首先用備份替換「正常文件」,然後讀取該文件。

的保存()的僞碼中,從SharedPreferencesImpl.java簡化爲:

void SavePreferenceFile() 
{ 
    // Rename the current file so it may be used as a backup during the next read 
    if(mFile.exists()) 
    { 
     if(!mBackupFile.exists()) 
     { 
      // file without backup (as usual first time): current file will be a backup 
      mFile.renameTo(mBackupFile); 
     } 
     else 
     { 
      // file AND backup: delete the file and keep only the backup 
      mFile.delete(); 
     } 
    } 

    // Attempt to write the file, delete the backup and return true as atomically as 
    // possible. If any exception occurs, delete the new file; next time we will restore 
    // from the backup. 
    mFile.WriteData(); 
    if(success) 
    { 
     // Writing was successful, delete the backup file if there is one. 
     mBackupFile.delete(); 
    } 
    else 
    { 
     // error 
     mFile.delete(); 
    } 
} 

而現在的負荷()函數:

void LoadPreferenceFile() 
{ 
    if(mBackupFile.exists()) 
    { 
     mFile.delete(); 
     mBackupFile.renameTo(mFile); 
    } 
    mFile.ReadData(); 
} 

在一個 「正常」 的用法中,負載當上一次寫入結束時發生,所以沒有問題。在我的情況下發生了什麼事,是我有:

Process 1: Start service (Process 2) 
Process 1: SavePreferenceFile() begin 
Process 2 startup: LoadPreferenceFile() 
Process 1: SavePreferenceFile() end 

如果LoadPreferenceFile()只是磁盤(mFile.WriteData())的實際寫入後,但我們刪除備份之前,那麼從流程2 LoadPreferenceFile()將取代我們新的文件由舊的備份。

我的解決方案,針對我的特定問題:在啓動服務之前使用editor.commit()。通過這樣做,我可以確保在讀取之前寫入結束。 當我想更改首選項並告訴其他進程重新加載首選項時,我的做法與此相同:commit()我的更改,然後向服務發送信號以加載新文件。

希望它可以幫助人們。

+0

我會將所有對首選項的訪問都放在ContentProvider中,以確保始終在同一進程中訪問它。 – njzk2