Android 手機間互傳照片/影片 APP 開發心得
一開始想做這個 Android 手機間照片、影片互傳 APP,其實想法很單純:兩支手機靠近,選好照片,傳過去就好了。
真正開始做之後才發現,「把檔案傳過去」反而只是最基本的一步。真正困難的是:傳過去之後,能不能仍然是原本那一張照片。
這個專案一路從 Android 14、Android 15 雙機實測開始,測試手機包括 Redmi Note 12 與 Redmi Note 15。傳輸核心最後採用 Google Nearby Connections,目標也很明確:不依賴雲端、不需要先上傳網路,而是讓兩支 Android 手機直接進行近距離傳輸。
剛開始做到可以「選照片 → 找到另一支手機 → 建立連線 → 傳送成功」時,確實很有成就感。但很快就發現,畫面出現「傳送完成」四個字,跟真正完成一套可靠的照片傳輸系統,根本是兩回事。
第一個體會:Android 權限比想像中複雜
Android 版本愈新,權限管理愈細。
Nearby Connections 本身會牽涉到 Bluetooth、Nearby Wi-Fi、位置資訊以及不同 Android 版本的權限差異。甚至有一次發生 Redmi Note 12 → Redmi Note 15 可以正常傳送,但反方向卻完全連不上,也沒有出現驗證碼。
程式看了半天,最後才發現並不是傳輸邏輯壞掉,而是:
Google Play 服務本身的位置權限沒有開啟。
權限開啟之後,雙向互傳立即恢復正常。
這件事情讓我很有感:Android APP 發生連線問題時,不能只看自己的程式碼。作業系統、Google Play 服務、手機廠商的權限管理,都可能是問題來源。
有時候不是程式寫錯,而是「環境沒有允許程式正常工作」。
第二個體會:傳得到,不代表傳得對
這也是整個專案最重要的一課。
早期版本曾經發生一個很嚴重的問題:
原本手機裡的照片檔名,例如:
IMG_20260808_130957.jpg
經過 Android Photo Picker 或內容 URI 處理後,程式取得的名稱卻可能變成類似:
1000062816.jpg
更麻煩的是,接收端照片雖然看得到,卻發現 GPS 等 EXIF 資訊消失了。
乍看之下照片「有傳到」,實際上它已經不是原本的檔案。
這對一般檔案也許只是檔名不同,但對照片來說非常嚴重,因為照片真正有價值的資訊還包括:
拍攝日期、拍攝時間、GPS 座標、相機資訊、方向資訊,以及完整 EXIF Metadata。
因此後來我把需求重新定義得非常清楚:
傳送端與接收端的照片,檔名、內容與 EXIF 資訊都必須一致。
不能為了方便傳輸,就重新編碼照片;不能只把畫面上的影像內容複製過去;也不能接受「看起來一樣」就算完成。
最後改成以原始檔 Bytes進行傳輸,並加入 SHA-256 驗證,讓接收端收到的真正是原始資料。
這也是我認為這個專案從「Demo」走向「實際可以使用的工具」最重要的一個轉折。
第三個體會:真正困難的是批次傳輸的可靠性
傳一張照片不難。
真正麻煩的是一次選 10 張、30 張甚至更多照片,其中還可能混有影片。
這時候就不能只是:
「送檔案 → 下一張 → 再送檔案」。
還必須知道:
目前是哪一批、總共幾個檔案、目前是哪一個檔案、接收端是否收到 Metadata、檔案本體是否接收完成、SHA-256 是否一致、同名檔案是否已存在、哪一張成功、哪一張失敗,以及整批是否真的完成。
因此後來逐步加入了:
Batch Manifest、file metadata、ACK、receipt、file_meta_ack、batch_complete 等握手機制。
做完之後才真正理解:
檔案傳輸不是單純「Send」,而是一套通訊協定。
如果沒有狀態管理與雙方確認,只要其中任何一步掉包,就可能造成畫面說完成,但實際少了一張照片,甚至留下 0 Bytes 檔案。
第四個體會:UI 防呆其實也是核心功能
開發到後來,我愈來愈不把 UI 問題當成「外觀問題」。
例如接收端進入等待模式後,如果另一支手機最後決定不傳了,接收端是不是要永遠等下去?
如果使用者不小心連續按兩次「傳送照片/影片」,Photo Picker 被叫出兩次怎麼辦?
傳送成功之後,畫面上的舊縮圖要不要清掉?
接收端已經成功收到照片,Android 相簿卻還看不到縮圖,要不要主動通知 MediaStore/MediaScanner?
這些看似都是小問題,但真正拿兩支手機實際操作幾次,就會發現它們非常影響使用體驗。
所以這個專案後來的方向,不只是「功能能不能跑」,而是開始思考:
使用者會不會搞不清楚現在系統正在做什麼?
一個好的傳輸 APP,應該清楚告訴使用者:
「正在等待」、「已找到裝置」、「等待對方確認」、「正在傳第幾張」、「已完成」、「已略過重複檔案」或「連線已逾時」。
讓使用者不用猜。
第五個體會:實機測試永遠比模擬器重要
這個專案很多真正的問題,都是兩支實體手機互傳之後才出現。
模擬器可以測 UI、Photo Picker、基本程式流程,但 Nearby Connections、Google Play 服務權限、藍牙、Wi-Fi Direct、手機廠商系統限制,以及 Gallery 更新速度,很多都必須靠實機才能看見。
而且最有價值的測試往往不是:
「成功一次」。
而是:
傳過去,再反方向傳回來。
Redmi Note 12 → Redmi Note 15 成功,不代表 Redmi Note 15 → Redmi Note 12 就一定成功。
也正因為這樣,這個 APP 才慢慢從單向成功,走到真正可以雙向互傳。
第六個體會:版本備份不是小事
這大概是整個開發過程中最驚險的一課。
後期 PhotoTransfer 已經進入相當穩定的版本,結果有一次不小心把另一個 RobiRemoteApp 的 app 目錄直接覆蓋到 PhotoTransferApp。
那一刻真的可以說是:
嚇死寶寶了。
尤其當一套程式已經累積大量實機除錯成果,裡面包含傳輸流程、權限、Manifest、資源、UI 與各種握手機制時,如果沒有備份,很多細節根本不是重新 Copy 幾段程式就能恢復。
幸好最後把 PhotoTransfer 的核心功能與資源救回來,實機重新測試也確認可以正常操作。
那次之後第一件事就是:
先備份。
這件事情看似與 Android 技術無關,卻可能是整個開發過程最實用的經驗之一。
能執行的版本,不等於安全的版本。 有備份的穩定版本,才是真正的版本。
從「傳照片」變成「保護照片」
一路做到現在,回頭看這個專案,我覺得最有意思的地方,是最初的目標其實已經改變了。
一開始是:
我要做一個可以在 Android 手機間互傳照片的 APP。
後來慢慢變成:
我要確保一張照片從 A 手機到了 B 手機之後,仍然是那一張照片。
不只影像一樣。
而是:
檔名一樣、內容一樣、EXIF 一樣、GPS 還在、日期還在,而且系統知道它確實完整傳輸成功。
這兩句話看起來差不了多少,背後的工程複雜度卻差非常多。
目前 PhotoTransfer 已經歷經多次雙機實測與修正,Nearby Connections 的雙向傳輸、原始檔傳送、EXIF/GPS 保留、SHA-256 驗證、批次握手機制、重複檔案判斷以及接收端正式儲存等核心架構,都已經逐步穩定下來。
對我來說,這次最大的心得不是學會某一個 Android API,而是更清楚一件事情:
真正可靠的軟體,往往不是把功能做出來,而是把所有「萬一」處理掉。
萬一權限沒開。
萬一使用者按了兩次。
萬一對方沒有傳。
萬一連線中斷。
萬一檔名相同。
萬一資料傳了一半。
萬一 EXIF 被吃掉。
甚至——
萬一不小心拿另一個專案把整個 app 覆蓋掉。😂
一路踩坑、修正、實機測試,再繼續往下做,反而也是這個 Android 手機照片/影片互傳專案最好玩的地方。
現在再看到兩支手機把照片完整互傳成功時,那已經不只是「檔案傳過去了」。
而是很清楚知道:
這一路到底解掉了多少問題,才讓它看起來如此簡單。
Android 手機間互傳照片/影片開發心得
原本以為 Android 手機互傳照片、影片,只要把檔案從 A 手機傳到 B 手機就完成了,實際開發後才發現,真正困難的是「完整保留原始檔案」。
開發過程中遇到不少問題,包括 Android 14/15 權限差異、Google Play 服務位置權限、雙向 Nearby Connections 連線、照片檔名被改變、EXIF/GPS 資訊遺失、批次傳輸中斷,以及接收後相簿縮圖沒有立即更新等。
經過多次兩支實機互傳測試與修正後,現在已能保留原始檔名、照片 EXIF/GPS 資訊,並加入 SHA-256 驗證與批次傳輸握手機制,讓接收端真正收到與傳送端一致的檔案。
這次最大的心得是:「傳得過去」不代表「傳得正確」。 真正可靠的檔案傳輸,除了速度與方便,更重要的是檔案完整性、錯誤處理與使用者操作防呆。
當然,中途還曾不小心拿另一個 Android 專案把 app 資料夾覆蓋掉,差點讓之前的成果全部重來。幸好最後成功救回,也讓我深刻體會到另一件事:
程式寫得再好,都沒有先備份來得重要。😂