Guideline 2.5.4 被拒:背景模式遭 Apple 退件
作者:Mehdi Azizi
發布於 ,9 分鐘
Guideline 2.5.4:宣告了背景模式卻沒有功能使用它,Apple 就會退件。移除這個鍵,或向審查員展示聲音、位置或通話功能。
Guideline 2.5.4 規定背景模式只能用在它原本的用途:聲音、位置、VoIP 通話、完成任務、本機通知。當你的 Info.plist 宣告了其中一種模式,審查員卻找不到任何需要它的功能,就會被退件。如果沒有東西用到,就移除這個鍵;否則就讓審查員在三十秒內看到這個功能。
我用 Expo 發布自己的 iOS app。Solys 是我做的小遊戲鬧鐘,它宣告了 audio 模式:我曾經把它移除,後來又因為另一個原因加回去。這篇指南根據 Apple 的官方文字、其他開發者公開的退件案例,以及我自己的專案。想看所有退件原因的總覽,請讀Apple 為什麼退件你的 app。
Guideline 2.5.4 說了什麼?
這條規則只有一句話,位於 App Review Guidelines 第 2 節「Performance」的 2.5「Software Requirements」:「Multitasking apps may only use background services for their intended purposes: VoIP, audio playback, location, task completion, local notifications, etc.」
白話來說:背景服務只能用在它存在的目的上。所以標題才會是「Guideline 2.5.4 - Performance - Software Requirements」。
預設情況下,app 進入背景後就會被暫停。想繼續執行,就要在 Info.plist 的 UIBackgroundModes 鍵裡宣告一個或多個模式。Apple 關於背景模式的文件要求謹慎使用,因為會耗電,有其他做法時應優先採用。每個宣告的模式都是一個審查員可以驗證的承諾。
背景模式與它們的預期用途
| 模式 | 鍵 | 預期用途 |
|---|---|---|
| Audio、AirPlay、PiP | audio | 背景中可聽見的聲音 |
| 位置 | location | 持續定位 |
| VoIP | voip | 搭配 CallKit 的通話 |
| 背景擷取 | fetch | 定期取得網路內容 |
| 背景處理 | processing | 排程任務 |
| 遠端通知 | remote-notification | 推播新內容 |
這張表整理自 Apple 的頁面。在論壇上公開的退件案例中,最常出現的是 audio、location 和 voip。
2.5.4 退件訊息是什麼意思?
訊息會點名有問題的模式。以下是開發者在 Apple 論壇公開的兩個例子:
- Audio:「Your app still declares support for audio in the UIBackgroundModes key in your Info.plist but did not include features that require persistent audio.」接下來說明
audio鍵是給在背景播放內容的 app 用的,例如音樂播放器。它提供兩條出路:加入這類內容,或移除這個鍵(論壇討論串)。 - 位置:在一個 2024 年 12 月的討論串中,Apple 除了員工追蹤之外,找不到任何需要持續定位的功能,並表示光是這個用途並不恰當。
我的解讀是:審查員不會讀你的程式碼,他找的是功能。他必須能啟動這個功能、把 app 切到背景,然後確認它還在運作。
也有相反的情況。在一個 2026 年 5 月的討論串裡,一款真的有用到 audio 模式的朗讀 app 被退了好幾次:審查員一直無法觸發聲音。另一位開發者在裡面寫說,他認為附上影片讓他的案子過關。這是個人經驗,不是 Apple 的規則。
你沒加這個鍵,它是從哪裡來的?
用 Expo 的話,是設定外掛(config plugin)在 prebuild 時填寫 Info.plist。所以背景模式可能在你沒決定的情況下就進了你的二進位檔。
expo-audio 外掛的預設設定
expo-audio 的文件寫得很清楚:enableBackgroundPlayback 選項預設為 true,在 iOS 上會加入 audio 模式。所以只要用簡寫形式 "expo-audio" 宣告外掛,就等於宣告了這個模式。
我在 TradaZen 第一次送審前重新檢查時就發現了這件事:只有前景的短介面音效,外掛卻是用簡寫宣告的。修正只要改 app.json:
{
"expo": {
"plugins": [["expo-audio", { "enableBackgroundPlayback": false }]]
}
}
其他套件也會用各自的方式加入模式。expo-location 的 isIosBackgroundLocationEnabled 選項只有在你啟用時才會加入 location。expo-background-task 則是一安裝,就會在 prebuild 時加入 processing。
保留舊鍵的 ios/ 資料夾
第二個陷阱,是我 2026 年 7 月在 Solys 上踩到的。我已經從 app.json 移除了 audio,但這個鍵還留在之前產生的 ios/Solys/Info.plist 裡。不加 --clean 的 prebuild 會修改這個專案而不是重建,外掛只會加入鍵,從來不會移除。本機建置就會把它帶進去。如果 ios/ 被 git 忽略(我就是這樣),雲端的 EAS 建置會從全新的 prebuild 開始。
每次建置前,我都會檢查實際會送出去的內容:
npx expo config --type introspect --json | grep -o '"UIBackgroundModes":\[[^]]*\]'
npx expo prebuild --clean --platform ios
第一行列出 prebuild 會寫進 Info.plist 的模式。第二行會刪除 ios/ 資料夾,為本機建置重建原生專案。在 Solys 上,第一行顯示 "UIBackgroundModes":["audio"],在 TradaZen 上什麼都沒有:兩者都是我要的結果。
新增或移除模式會改動 Info.plist,也就是二進位檔:需要新的建置,遠端更新不夠。EAS Update 的文件把這類變更歸在需要新二進位檔的類別,跟 ITMS-90683 一樣。
Solys:有正當理由宣告 audio 模式
Solys 是鬧鐘,但 audio 模式不是用來讓鬧鐘響的:鈴聲是透過 AlarmKit,這是 Apple 從 iOS 26 開始提供的鬧鐘服務(AlarmKit 文件)。響鈴的是系統而不是 app,就算在靜音模式也一樣。
這個鍵的歷程如下,因為背後的思路比那一行設定更重要:
- 2026 年 7 月底,我把它移除。沒有任何東西在背景播放聲音:小遊戲的音效都在前景,鬧鐘也不依賴 app。
- 兩天後,我把它加回來,因為加了夜間聆聽功能。這個選項會用麥克風錄下你夜裡的聲音,並在 iPhone 上分析。螢幕鎖定時錄音必須持續,而 expo-audio 的文件指出,背景錄音選項同樣會加入
audio模式。
理由的性質變了:這個模式是用來錄音,不是播放。app 裡看得到三道防線:
- 聆聽是預設關閉的選項,從設定或睡前儀式中開啟;
- 它有自己的同意畫面,符合 規則 2.5.14 對所有錄音的要求,而且資料完全不會離開手機;
- 麥克風會在鬧鐘響前十分鐘關閉,讓聆聽和鈴聲永遠不會互相干擾。
我不能保證審查員每次都會接受這個宣告。但每個元素是做什麼用的、在哪裡看得到,我都說得出來。
讓 app 保持清醒的鬧鐘
在 AlarmKit 出現之前,第三方鬧鐘 app 沒辦法像 Apple 的鬧鐘那樣響鈴。所以有些函式庫會讓 app 保持清醒:Flutter 套件 alarm2 的文件描述了一個一直跑到鬧鐘響起的靜音音訊播放器。但靜音的聲音根本聽不到。從 iOS 26 起,AlarmKit 讓這種繞路變得沒必要。
保留還是移除模式:判斷表
| 你的情況 | 該怎麼做 |
|---|---|
| 前景的短音效 | 移除 audio |
| 音樂或有聲書 | 保留,並展示出來 |
| 長時間錄音 | 保留+取得同意 |
| 鬧鐘或計時器 | AlarmKit(iOS 26) |
| 偶爾需要位置 | 重大位置變更 |
| 不急的同步 | fetch 或 processing |
| 語音或視訊通話 | voip 搭配 CallKit |
關於位置,Apple 的頁面提到重大位置變更服務可以取代 location 模式。
2.5 的其他條款:2.5.1 與 2.5.2
2.5.1:公開 API,框架用在該用的地方
「Apps may only use public APIs and must run on the currently shipping OS.」app 只能使用公開 API、要能在目前的系統版本上執行,並淘汰已棄用的東西。跟 2.5.4 一樣,每個框架都要用在它的預期用途上,並在描述中提到:HealthKit 用於健康,HomeKit 用於智慧家庭。如果某個函式庫呼叫了私有 API,被退件的是你的 app。
2.5.2:不能下載會改變 app 的程式碼
app 必須在自己的套件內自給自足。它不能「download, install, or execute code which introduces or changes features or functionality of the app」。只有教人寫程式的教育類 app 有有限的例外,而且程式碼必須完全可見、可由使用者修改。
用 Expo 的話,這牽涉到 EAS Update,它的文件提醒更新必須遵守 App Store 規則。遠端更新只拿來修 bug,絕對不要用來推出審查員沒看過的功能。跟審查有關的修正要走新的建置,就像我在 guideline 2.1 指南裡說明的那樣。
怎麼回覆 2.5.4 退件?
對話在你 app 的 App Review 區塊進行,你在那裡回覆 App Review 的訊息。分兩種情況:
- 模式沒有用處:移除它、重新建置、檢查 introspection 結果,然後用兩句話回覆。
- 模式支撐真正的功能:附上確切的操作路徑和一段短影片回覆,並在每個版本的審查備註中放入這段文字。
這是我會用的範本,請自行調整(如果你的 app 沒有鬧鐘,就刪掉鬧鐘那段):
Hello,
The "audio" background mode is used only by [feature], an
optional feature that [records / plays] audio while the
device is locked. It is off by default.
How to test:
1. Open [screen] and turn on [feature].
2. Accept the microphone permission if asked.
3. Tap [button], then lock the device.
[What the reviewer will hear or see.]
Our alarm does not use this mode: it is scheduled with
AlarmKit and rings through the system.
Attached: a short screen recording of these steps.
Best regards,
送審前的 2.5.4 檢查清單
- Expo 的 introspection 只顯示你要的模式,而且每個模式對應的功能你都說得出來。
- 會預設加入模式的外掛(例如 expo-audio),都沒有用簡寫形式宣告。
- 本機的
ios/資料夾沒有殘留舊鍵,或者你是從乾淨的 prebuild 編譯。 - 功能能快速觸發、不需要隱藏設定,審查備註也寫明每個模式的用途和測試方式。
- 所有錄音都要取得明確同意,並顯示可見的指示。權限請求的規則請看 guideline 5.1.1 指南。
- 如果是鬧鐘,鈴聲要透過 AlarmKit,而不是由 app 播放聲音。
送出前檢查你的資料
Review Kit 收錄了我的送審前檢查清單、給審查員的備註範本,以及會檢查你專案的 Claude Code skills。想要真人幫你看,跟我預約一小時:我們透過視訊一起看 Apple 的訊息和你的 Info.plist。完整流程請看被 Apple 退件的 app 頁面。
正在準備你的第一次上架嗎?做免費診斷:它會幫你把步驟排好順序,並寄給你我的 Apple 防退件檢查清單。
沒有人能保證 Apple 會接受你的 app。但一個理由充分的背景模式,三十秒就能說清楚。
常見問題
「Guideline 2.5.4 - Performance - Software Requirements」是什麼意思?
這是 Apple 認為你 Info.plist 中的某個背景模式沒有用在預期用途時的退件標題。訊息會點名該模式,並建議你展示使用它的功能,或移除這個鍵。
2.5.4 退件後需要重新建置嗎?
如果你移除或修改了模式,需要:這個鍵在 Info.plist 裡,也就是在二進位檔裡。如果模式有正當理由,附上確切操作路徑和影片的回覆可能就夠了。
可以為了幾個介面音效保留 audio 模式嗎?
不行。在前景播放的短音效不需要它。用 Expo 的話,在 expo-audio 外掛中把 enableBackgroundPlayback 設為 false。
鬧鐘 app 需要 audio 模式嗎?
如果它使用 iOS 26 起提供的 AlarmKit,響鈴就不需要:播放鬧鐘的是系統。在 Solys 上,audio 模式是用於夜間聆聽,在螢幕鎖定時錄音。
我什麼都沒宣告,為什麼 Apple 引用 2.5.4?
可能是某個外掛幫你宣告了:expo-audio 預設會加入 audio 模式,舊的 ios/ 資料夾也可能保留你已從設定中移除的鍵。用 npx expo config --type introspect 檢查。

