# 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<String, *></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/<app目錄名>/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)