在 iOS 上背景記錄 GPS,而不會把這趟路弄丟
iOS 可以在任何一個時刻把你的記錄 App 凍結或直接終止,而且沒有義務先通知你。這對一個「負責把你去過哪裡寫下來」的 App 意味著什麼,以及真正保護得了一趟路的到底是什麼。
你按下開始記錄,把手機放進口袋,騎了三個小時,拿出來一看,App 顯示二十分鐘。這是手機類記錄工具最常見的一種嚴重抱怨,而原因幾乎從來都不是 GPS。
iOS 實際上正在對你的 App 做什麼
一個在背景的 iOS App,不是單純變慢而已。它會在幾種狀態之間移動:還在執行、被凍結(留在記憶體裡,但一行程式都不跑)、以及被終止(整個沒了)。系統依據記憶體壓力、機身溫度、電量,以及它自己的判斷,把你在這幾種狀態之間搬來搬去,而且沒有義務事先告訴你。
宣告了定位背景模式的 App,可以在定位更新持續進來的期間繼續執行,這整件事才有可能成立。比起一般的 App,這已經是一個很強的保證了。但它不是絕對的:在真正的記憶體壓力下——一款大型遊戲、一個相機 App、一支記憶體比較小的手機——你的記錄工具還是可能在半路被砍掉。
這就帶出真正要命的那個失敗。如果 App 是把整趟路握在記憶體裡,等著你按下停止才寫下任何東西,那麼上一次存檔之後的一切就沒了。不是壞掉,是沒了,而且不會有任何錯誤訊息,因為知道這件事的那個行程已經不存在了。
唯一真正的保護:一到就寫下來
這件事沒有什麼聰明的繞法。保護的方式,是讓「還沒存檔的資料」從一開始就不存在。
在 Roam 裡,一次定位回呼就是一個批次,就是一個資料庫交易,而這些點和這趟路的累計數字是一起提交的。沒有一個等著騎完才寫的緩衝區,寫入端也沒有任何一條路徑會安靜地丟掉一個點——一個沒辦法提交的批次會拋出錯誤、被掛進警示裡,而不是被默默吞掉。如果 App 在第四十公里被砍掉,第四十公里已經在手機上了。
把累計數字和點放在同一個交易裡提交,這件事比它看起來重要。它代表摘要那一列,永遠不可能描述一組並不在磁碟上的點,所以被砍掉之後,不會有一個兩邊對不起來的空窗——而且復原也不必重播兩萬八千個點才算得出你當時在哪裡,它讀那一列就好。
同一個想法的另一半是:任何東西都不會被刪除。過濾、平滑化和自動暫停,從來不會把某一列從資料庫裡拿掉。它們是記在點上的標記,而原始的資料流就照它抵達時的樣子留著,這樣當某個啟發式規則對一趟真實的路判斷錯了,原始樣本還在,可以重新推導一次。一個把自己不認同的樣本悄悄丟掉的 App,等於把「它判斷錯了」這件事的證據也一起銷毀了。
被終止之後,怎麼再站起來
如果你給的是「永遠允許」定位,iOS 可以在發生顯著位置變化的時候,把一個已經被終止的 App 在背景重新啟動。這是一條真正的救命索,而它同時帶著一個陷阱,很多記錄工具都栽在上面。
在那次重新啟動裡,App 只有一個很短、而且長度不可預測的視窗,之後系統隨時可能再把它凍結——關鍵是,它可以在任何一個暫停點被凍結,而在現代 Swift 裡,那意味著任何一個 await。一個重新啟動之後先非同步載入一些狀態、然後才去重建定位管理器的 App,可能在中途就被凍住,而一個在重新武裝之前就被凍結的行程,從此再也不會收到 GPS 的任何消息。它看起來還在跑,實際上什麼都收不到。
Roam 的重新武裝刻意寫成同步的,就是為了這個理由:重建定位管理器和重新啟動更新,都發生在第一個 await 之前,讓這個錯誤變成寫不出來,而不只是「不建議這樣寫」。它同時也被寫成可以安全地呼叫兩次,因為背景重啟和前景啟動撞在一起的時候,做的正好就是這件事。
有件事要講清楚:「永遠允許」是保險,不是必要條件。用「使用 App 期間」一樣記錄得好好的,而 App 在權限說明裡就是這樣寫的,不會暗示你非交出那個更強的權限不可。
而且它是保險,不是保證,這個區別值得堅持。Roam 自己的設定裡把這個假設寫成了白紙黑字:它可能永遠不會被重新啟動,而耐久性是它唯一給得起的承諾。iOS 裡沒有任何機制,強制系統把一個被終止的 App 帶回來。從被砍掉到被重新啟動之間發生的事,沒有任何人記錄下來,事後也沒有任何 API 能把它交出來,所以你拿回來的,是被砍之前的那一段,加上重啟之後的那一段,中間是一個誠實的洞。任何把這件事描述成「無縫接續」的 App,描述的是這個平台並不提供的東西。
騎車的人可以做什麼
- 只要要記錄比較長的行程,就把定位給「永遠允許」。它決定了一次終止是完全不痛,還是賠掉剩下的半趟。
- 能不開低耗電模式就不要開。它會讓背景工作更容易被砍掉。
- 記錄途中不要從多工畫面把 App 滑掉。強制關閉等於告訴 iOS 你是認真的,之後不會有任何背景重啟。
- 不要同時跑兩個記錄 App。兩個 App 一起把 GPS 撐著,耗電加倍,把其中一個弄死的那種記憶體壓力也加倍。
- iOS 更新之後回頭檢查一下權限。系統升級偶爾會把「永遠允許」降成「使用 App 期間」,而且不太聲張。
怎麼判斷你的記錄 App 安不安全
這件事十分鐘就測得出來,不需要任何特殊工具。開始記錄,走五分鐘,然後從多工畫面把 App 強制關閉,再重新打開。如果那五分鐘還在,這個 App 是邊走邊寫的。如果那一趟是空的、或者被截斷了,它就是把你的資料握在記憶體裡,而你現在也很清楚,一趟長程你可能會賠掉多少。
一個記錄工具也應該盡可能少宣告東西。每多一項背景模式,就多一個「可能因為濫用而被終止」的理由,也多一件 App Store 審查人員會來問的事;Roam 只宣告一項——定位——而專案裡有一道建置關卡,只要這份清單多出一筆就讓建置失敗。
常見問題
我的 iPhone 為什麼在背景停止記錄了?
幾乎都是因為 iOS 在記憶體壓力下把 App 凍結或終止了,或者它被人從多工畫面強制關閉。定位背景模式能讓記錄工具活得比一般 App 久很多,但它不是絕對的保證,所以 App 拿到每一個定位點之後做了什麼,比它有沒有活著更重要。
記錄一趟路,App 一定要「永遠允許」定位嗎?
不用。「使用 App 期間」就足以在螢幕關閉的狀態下記錄,因為定位背景模式會在更新持續進來的期間讓 App 保持執行。「永遠允許」多出來的,是讓 iOS 在 App 被終止之後有機會把它在背景重新啟動,所以它是長程的保險,不是必要條件。
背景 GPS 很耗電嗎?
它確實會用掉實實在在的電——接收器持續運作,App 每收到一個定位就醒來一次。現在的手機應付幾個小時的行程綽綽有餘;一整天的活動才是它變成一個問題的地方,也正是在那裡,一個邊走邊寫的 App 決定了你是只損失最後幾分鐘,還是損失一整天。
我要怎麼測試我的記錄 App 會不會掉資料?
開始記錄,走五分鐘,從多工畫面把 App 強制關閉,再打開它。如果那五分鐘活下來了,這個 App 是邊走邊寫的。如果沒有,它就是把你的紀錄留在記憶體裡。
一個不會把這趟弄丟的記錄 App
Roam 每收到一個定位點就立刻寫進手機,和當下的累計數字在同一個交易裡落地;訊號斷掉的那一段就標成斷的,不拿一條直線糊過去;爬升讀的是氣壓計,不是 GPS 高度。沒有帳號、不用登入、不做同步。完整記錄一趟、看它、回放它,都是免費的。