---
# System prepended metadata

title: Android筆記--SharedPreferences(共享偏好設定)
tags: [資料, 共享, 儲存, android, 偏好, xml, SharedPreferences, Preferences, 資料儲存, Shared]

---

# Android筆記--SharedPreferences(共享偏好設定)

SharedPreferences 是 Android 提供的==快速、簡單的輕量級資料存儲方案，適合少量設定與偏好資料。它以鍵值對 (key-value pair) 形式儲存簡單資料==，如設定選項、使用者偏好或狀態，底層使用 XML 檔案 保存，並提供簡單的 讀寫 API，方便快速存取資料。

<b><u>優勢</u></b>:
- 簡單易用：提供簡單 API，輕鬆存取鍵值對數據，無需建立複雜資料庫。
- 輕量級：適合小量資料，如應用設定、用戶偏好或狀態資訊。
- 支援基本類型：String、Int、Boolean、Float、Long。

<b><u>缺點</u></b>:
- 不適合大量資料：對大量或複雜資料不適用，需使用 SQLite 或其他存儲方案。
- 僅支持基本類型：可存 String、Int、Boolean 等，對自定義物件或複雜結構不友好。
- 無內建加密：是明碼儲存於 XML 檔案，不適合儲存敏感資料，敏感資訊需額外保護（如使用 EncryptedSharedPreferences）。

---
基本使用
---
<details><summary><font size=4>取得SharedPreferences實例</font></summary>
<div>   

```kotlin=
// 取得 SharedPreferences 實例，"MyPrefs" 為檔名
val prefs = getSharedPreferences(name: String, mode: Int)
val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
```
- <code>getSharedPreferences()</code>: 取得或建立一個 SharedPreferences 實例，需填入檔案名稱及存取模式做為參數。
  - <code>Context.MODE_PRIVATE</code>: 此模式代表資料只對本應用程式可見，如果檔案不存在會自動建立，是<code>getSharedPreferences()</code>最常用的模式。
    <div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><details><summary>其他的模式(不推薦使用，僅作為補充資訊)</summary>
     
    - MODE_APPEND（舊版）: 將資料追加到已存在檔案的末尾。實際使用上很少見，容易造成資料混亂。
    - MODE_WORLD_READABLE / MODE_WORLD_WRITEABLE（舊版，不推薦）允許其他應用讀取或寫入此 SharedPreferences。因為安全風險，從 Android 7.0 開始已不再支援。
    - MODE_MULTI_PROCESS（舊版，有限支持）允許多個進程讀取同一份 SharedPreferences。實務上不穩定，官方建議使用 ContentProvider 或 DataStore 代替。
      
    </details>
    </div>
    
</div>
</details>

<details><summary><font size=4>寫入</font></summary>
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>步驟1：取得 SharedPreferences.Editor</summary>
    
想要進行寫入，必須先拿到SharedPreferences.Editor物件
```kotlin=
// 取得必須先拿到SharedPreferences
val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
// 從SharedPreferences取得SharedPreferences.Editor物建
val editor = prefs.edit()
```
- <code>edit()</code>:會回傳 SharedPreferences.Editor，用它來修改或新增鍵值對。
    
</details>
    
<details><summary>步驟2：使用 putXXX() 寫入資料</summary>
    
SharedPreferences 支援基本資料類型
```kotlin=
editor.putString("username", "PU")    // 儲存字串
editor.putInt("age", 28)             // 儲存整數
editor.putBoolean("isFirstRun", true) // 儲存布林值
editor.putFloat("score", 95.5f)      // 儲存浮點數
editor.putLong("userId", 123456L)    // 儲存長整數
```
    
</details>
    
<details><summary>步驟3：提交資料</summary>

提交資料有分兩種<code>commit()</code>和<code>apply()</code>
- <code>commit()</code>: 同步操作，會立即寫入磁碟，但因是同步，所以會阻塞主線程，==完成後會回傳一個Boolean值表示是否成功==。
  - 適合場景：需要確保資料已經寫入磁碟，例如重要設定或登出時立即清除敏感資訊。
  ```kotlin=
  val isSuccess = prefs.edit().putString("username", "PU").commit()
  ```
- <code>apply()</code>: 非同步操作，會在背景寫入磁碟，不阻塞主線程，不回傳結果，因此無法立即得知是否寫入成功。
  ```kotlin=
  prefs.edit().putString("username", "PU").apply()
  ```

<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary><code>apply()</code> VS <code>commit()</code></summary>

SharedPreferences 異動資料後，必需呼叫 apply() 或 commit() 資料才會實際寫入檔案。
- <code>apply()</code>:此方法是將要修改的資料提交到記憶體，再使用非同步方法寫入硬碟，因此效率較高，但並不保證資料一定寫入成功。此外，<code>apply()</code>不會回傳值，執行失敗也不會顯示任何訊息。因此在某些特定情況下，資料並沒有正確寫入到硬碟，開發人員也很難發現此問題，建議在主線程中使用。
- <code>commit()</code>:此方法會同步將資料寫入硬碟，因此在多執行緒使用時，會互相等待<code>commit()</code>執行完成，因此效率較差。此外，<code>commit()</code>會回傳 boolean 值，表示本次修改是否提交成功，對資料是否正確寫入至硬碟有比較高的保障。

※參見參考資料:
[SharedPreferences 的commit和apply分析](https://blog.csdn.net/u010198148/article/details/51706483)
[Android儲存資料--使用SharedPreferences](https://blog.tarswork.com/post/android-storage-using-shared-preferences/)

</details>
</div>
    
</details>
    
<details><summary>進階</summary>
    
可以採用[ScopeFunction](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/kotlin_ScopeFunction#apply)(推薦使用)或鍊式呼叫來一次大量提交
    
<b><u>ScopeFunction</u></b>
```kotlin=
val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)

// 使用 Kotlin apply { } 進入 Editor 的作用域
prefs.edit().apply {
    putString("username", "PU")
    putInt("age", 28)
    putBoolean("isFirstRun", true)
    putFloat("score", 95.5f)
    putLong("userId", 123456L)
    apply()  // 提交修改
}
```
<b><u>鍊式呼叫: </u></b>
```kotlin=
val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)

// 直接鍊式呼叫 putXXX()，最後 apply() 提交
prefs.edit()
    .putString("username", "PU")
    .putInt("age", 28)
    .putBoolean("isFirstRun", true)
    .putFloat("score", 95.5f)
    .putLong("userId", 123456L)
    .apply()  // 非同步提交
```

</details>
    
</div>
</details>

<details><summary><font size=4>讀取</font></summary>
<div>
    
SharedPreferences 提供 getXXX() 方法，依資料類型不同使用不同方法:
```kotlin=
val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)

// 讀取資料
val username = prefs.getString("username", "預設值")     // 字串
val age = prefs.getInt("age", 0)                         // 整數
val isFirstRun = prefs.getBoolean("isFirstRun", true)    // 布林值
val score = prefs.getFloat("score", 0f)                 // 浮點數
val userId = prefs.getLong("userId", 0L)                // 長整數
```
- <code>getXXX()</code>: 選擇和資料對應類型的<code>getXXX()</code>，內部需要填入鍵值和一個預設值作為參數，當對應的鍵值不存在時會返回這個預設值。    

</div>
</details>

<details><summary><font size=4>刪除</font></summary>
<div>

>[!Warning]使用刪除後要記得使用<code>commit()</code>或<code>apply()</code>來提交變更

- <code>remove()</code>: 刪除指定的單個鍵值對
  ```kotlin=
  val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)

  // 刪除單個鍵
  prefs.edit().remove("username").apply()
    
  // 連續刪除多個--鍊式
  prefs.edit()
      .remove("username")
      .remove("age")
      .apply() // 最後一次提交
    
  // 連續刪除多個--scopeFunction
  prefs.edit().apply {
      remove("username")
      remove("age")
      apply()             // 這裡的 apply() 是提交資料
  }
  ```
- <code>clear()</code>: 清除所有資料
  ```kotlin=
  prefs.edit().clear().apply()
  ```
    
</div>
</details>

<details><summary><font size=4>查詢</font></summary>
<div>

- <code>contains()</code>: 判斷某個 key 是否存在，內部需填入一個鍵值作為參數，會回傳一個Boolean來表示該鍵值是否存在
  ```kotlin=
  val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)

  if (prefs.contains("username")) {
      val username = prefs.getString("username", "")
      println("使用者名稱：$username")
  } else {
      println("username 尚未設定")
  }
  ```
- <code>all</code>: SharedPreferences實例的屬性，可用於查詢所有 key-value，會回傳一個<code>Map&lt;String, *&gt;</code>，內含所有鍵值對
  ```kotlin=
  val prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
  // 用perfs.all獲取所有的鍵值對，並將其賦予變數
  val allPrefs: Map<String, *> = prefs.all
  for ((key, value) in allPrefs) {
      println("$key = $value")
  }
  ```
    
</div>
</details>

---
儲存位置
---
SharedPreference 儲存在裝置的<code>data/data/&lt;app目錄名&gt;/shared_prefs</code>之中

---
比較
---
    
<details><summary><font size=5>SharedPreferences和其他常見儲存法的比較</font></summary>

| 儲存方式                        | 特點                                                                          | 適用情境                                                        |
| --------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------- | 
| **SharedPreferences**       | - 鍵值對 (key-value) 儲存<br>- 輕量級<br>- XML 檔案存放                                 | - 使用者設定<br>- 偏好值 (例如：深色模式、語言選項)<br>- 登入狀態 (token、布林值)       |
| **Internal Storage (內部儲存)** | - 存在 app 專屬空間<br>- 其他 app 不能存取<br>- 支援檔案操作                                  | - 文字檔、圖片、JSON 資料                                            |
| **External Storage (外部儲存)** | - 使用者可存取（需權限）<br>- 可跨 app 共用                                                | - 大型檔案（影片、音樂、相片）<br>- 需被其他 app 使用的檔案                        |
| **SQLite 資料庫**              | - 支援 SQL 查詢<br>- 適合結構化資料<br>- 支援關聯                                          | - 聊天訊息、記事本、聯絡人、交易紀錄                                         | 
| **[Room (SQLite 封裝)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/Android筆記_ROOM_1)**        | - SQLite 的進階封裝<br>- 支援 ORM（物件關聯映射）<br>- 型別安全                                | - 中大型應用<br>- 需要資料表、關聯、查詢                                    |
| **DataStore (Jetpack 新方案)** | - 取代 SharedPreferences<br>- 支援同步/非同步<br>- 型別安全 (Proto DataStore)<br>- 適合新專案 | - 需要偏好設定（Preferences DataStore）<br>- 結構化資料（Proto DataStore） | 

</details>

---
下一篇: [Android筆記--DataStore](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_DataStore)
    
---
參考資料:
---
- [Android Developer--共享首選項](https://developer.android.com/reference/android/content/SharedPreferences)
- [Android儲存資料--使用SharedPreferences](https://blog.tarswork.com/post/android-storage-using-shared-preferences/)
- ==★==[使用SharedPreferences存取設定資料 ==(JAVA)== ](https://litotom.com/2017/06/27/ch7-1-sharedpreferences/)
- [[Android 十全大補] Room](https://ithelp.ithome.com.tw/articles/10222906?sc=rss.iron)
  - [Day28 - Jetpack DataStore (上) | Preferences DataStore](https://ithelp.ithome.com.tw/articles/10306543?sc=iThelpR)
- [SharedPreferences 的commit和apply分析](https://blog.csdn.net/u010198148/article/details/51706483)