iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0

配合分支:day11/start,Repo: 按我

昨天注意到 App 一關閉,id 資料都會消失。這些 id 在 DiaryEntry 建立時才會被塞入 UUID。用現實一點來考量,每次儲存筆記後 id 都會變,那代表 id 不能保證就是代表某一則日記。

將 App 或是任何程式在電腦中跑起來,系統會把它們載入到記憶體 (RAM) 之中。隨著程式因生命週期到頭被系統移除(通常是關閉後被系統移除),這些記憶體裡頭的資料就會消失。必須用某一種方法儲存在硬碟或是其他的儲存空間之中(寫過網頁的話可以存在類似 Local Storage 的東西裡頭),才能將這些 id 永久保存成同一份,這招叫做 Local persistence。只要在本機有一份資料,就不用重新建立。

推論

今天的修正,會將現在的日記儲存成 JSON 的格式,放在 Android 系統的內部儲存空間。JSON 格式的樣式長成這樣:

[
  {
	"id": "abc",
	"title": "夜裡的共享空間",
	"note": "雨夜裡留下來整理想法",
	"mood": "專注",
	"createdAt": "2026-04-04T21:04:51",
	"photoResId": 123
  }
]

為常用的網路傳輸時,使用的格式。讀者可能會注意到這個一個配一個的資料樣式,很適合 data class。沒錯,通常在實務上收到 JSON 資料後,會轉成 Data class 來存取操作。

補充

  • 這類的一個配一個的形式,稱之為:key - value,一個 key (想像成 id) 配上一個 value (也就是數值)。像是 "mood": "專注" 代表 mood 這個 key 配到 "專注" 這個文字
  • 這類的東西在實務上,有幾個專有名詞專門意指這種「骨架」的 class:
    • POJO (Plain Old Java Object)
    • Entity

嘿名稱裡有 Java,代表 Android 開發和 Java 的淵源難分難捨。

JSON

  • 包在 {} 大括弧裡頭,都視為一個物件,通常轉換為 Android 的 POJO。
  • 包在 [] 裡頭的是 Array (陣列),也就是一個列表的物件。
  • 格式常見的有文字:String,數字:Int (常見,但不全然是),和布林值 (Boolean)

實作

如何寫入檔案?

新建一個 Class,他的工作是在 Android 的儲存空間寫入 json 檔案。

https://ithelp.ithome.com.tw/upload/images/20260830/201416151QpokXMTXQ.png

參考上面的 JSON 格式,接著就來逐行講解這東西做了啥:

class LocalDiaryStorage(context: Context)

建構子需要一個 Context,這個東西來自於 Android,如果需要動到和 Android 系統有關的行為,像是寫入資料,就需要這個東西。
放心,Google 的 viewModel() 方法,會自動把這個東西給我們 (貼心 💐)。

private val diaryFile = File(context.filesDir, "diaries.json")

這行就是請系統建立一個 "diaries.json" 檔案。接著下面的 save() 方法,看到熟悉的格式:

val jsonArray = JSONArray()  // 👈🏼 很多筆,所以包在 Array 裡,也就是 []

for (entry in entries) {
	val jsonObject = JSONObject()  // 👈🏼 建立一個物件 {},將 key-value 塞入
	jsonObject.put("id", entry.id)
	jsonObject.put("photoResId", entry.photoResId)
	jsonObject.put("title", entry.title)
	jsonObject.put("note", entry.note)
	jsonObject.put("mood", entry.mood)
	jsonObject.put("createdAt", entry.createdAt.toString())
	jsonArray.put(jsonObject)  // 👈🏼 把物件加到 Array 裡頭
}

diaryFile.writeText(jsonArray.toString()) // 👈🏼 最後寫入檔案

ViewModel 調整

接著,在儲存日記時,要把結果寫入儲存空間之中。 但是 LocalDiaryStorage 需要 Context 怎麼辦? ViewModel 有個變體,叫 AndroidViewModel 可以取得 Application ,只要有這個,基本上就可以取得 Context

所以先將 ViewModel 調整成 AndroidViewModel

class PhotoDiaryViewModel(application: Application) : AndroidViewModel(application)

接著將 LocalDiaryStorage 黏在 ViewModel 上。

private val storage = LocalDiaryStorage(application.applicationContext)

寫入新日記時,順便寫入儲存空間之中:

https://ithelp.ithome.com.tw/upload/images/20260830/20141615uqDJnQiJNn.png

除了這裡寫入以外,移除日記也需要移除相關的資訊。不過,現在日記還很少的情況之下,就直接重新寫一份回去:

https://ithelp.ithome.com.tw/upload/images/20260830/20141615wslOOwI3UN.png

驗證

寫入後怎麼驗證呢?Android Studio 可以藉由 Device Explorer 開啟裝置的內部結構。PhotoDiary 來說,位置在:

data/data/com.rayliu.myphotodiary/files/diaries.json

有些模擬器會在:

data/user/0/com.rayliu.myphotodiary/files/diaries.json

https://ithelp.ithome.com.tw/upload/images/20260830/20141615IoqpxuqoIg.png

可以將這個檔案匯出到本機,就可以預覽他囉。例如,這是本次的預覽:

https://ithelp.ithome.com.tw/upload/images/20260830/20141615S2Z4Eb29kI.png


資料型態補充

在介紹 JSON 時,提到數字的資料型態(Type),說到 Int 是常見但不全然是。就藉此利用機會來介紹其他的可能型態:

資料型態 (Type) 記憶體大小 (Size) 包含位元數 (Bits) 數值範圍 (Range) 適用情境
Byte 1 Byte 8 bits -128 到 127 讀取檔案、二進位串流、串流媒體傳輸
Short 2 Bytes 16 bits -32,768 到 32,767 大型陣列中節省記憶體(現已較少使用)
Int 4 Bytes 32 bits 約 -21.4 億 到 21.4 億 最常用的預整數型態,如迴圈計數、一般計算
Long 8 Bytes 64 bits 約 -922 億億 到 922 億億 時間戳記、全域唯一識別碼 (UID)、大型資料庫 ID

現在實務最常用的是 Int 和 Long,其他比較少用。如果要計算的數值,超過數值範圍,可能會發生溢位(arithmetic overflow)造成最後的結果錯誤。所以要計算天文數字,用 Long 可能會是一個比較好的選擇。

TL;DR

  • Local persistence(本機持久化):把資料存進硬碟等儲存空間,App 關閉後資料還在;今天的 bug 就是 id 存在記憶體裡,一關 App 就消失。
  • 記憶體(RAM):程式跑起來時資料暫存的地方,程式被系統移除後內容就清空,所以需要持久化才能保住日記 id。
  • JSON:常用的資料交換格式,今天用它把日記存成 diaries.json 檔案。
  • key-value:一個 key 配一個 value 的資料樣式,像 "mood": "專注",是 JSON 的基本組成方式。
  • 物件與陣列({}[]:JSON 裡 {} 是一個物件、[] 是陣列(一列表物件),對應程式裡的 JSONObjectJSONArray
  • data class / POJO / Entity:JSON 一配一的骨架很適合轉成 data class 來操作,這類「骨架」class 在實務上叫 POJO 或 Entity。
  • Context:動到 Android 系統行為(例如寫入檔案)時需要的東西,LocalDiaryStorage 的建構子就靠它取得儲存位置。
  • AndroidViewModel 與 Application:ViewModel 的變體,能取得 Application 進而拿到 Context,解決 ViewModel 裡無法建立 storage 的問題。
  • 整數型態與溢位(Int / Long):Int 是 32 bits 常用整數型態,Long 是 64 bits 適合大數字;數值超過範圍會發生溢位導致結果錯誤。

上一篇
[Day10] 請給他一個 id
系列文
我的第一個手作りAndroid App!快樂學習物件導向程式設計!11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言