Android 10 包含可能影響應用程式的行為變更。無論應用程式的 targetSdkVersion
為何,當應用程式在 Android 10 上執行時,本頁所列的變更都會套用至您的應用程式。您應測試應用程式,並視需要修改,以便正確支援這些變更。
如果應用程式的 targetSdkVersion 為 29
以上版本,您也需要支援其他變更。請務必參閱「針對 29 版的應用程式行為變更」一文,瞭解相關詳情。
注意: 除了本頁所列的變更項目外,Android 10 還推出了許多以隱私權為基礎的變更和限制,包括:
- 背景存取裝置位置資訊
- 背景活動開始
- 聯絡人相依性資訊
- MAC 位址隨機化
- 相機中繼資料
- 權限模型
這些變更會影響所有應用程式,並強化使用者隱私權。如要進一步瞭解如何支援這些變更,請參閱「隱私權變更」頁面。
非 SDK 介面限制
為確保應用程式的穩定性和相容性,平台已開始限制應用程式在 Android 9 (API 級別 28) 中可使用的非 SDK 介面。基於與 Android 開發人員合作及最新的內部測試,Android 10 包含更新後的受限制非 SDK 介面清單。我們的目標是在限制非 SDK 介面之前,確保公開替代方案的可用性。
如果您不會將 Android 10 (API 級別 29) 設為目標版本,則此處所述的某些變更可能不會立即對您造成影響。雖然您目前可以使用某些非 SDK 介面 (視應用程式的目標 API 級別而定),但使用任何非 SDK 方法或欄位時,均可能面臨應用程式故障的高度風險。
如果不確定應用程式是否使用非 SDK 介面,您可以測試應用程式來確認。如果您的應用程式仰賴非 SDK 介面,則建議您開始規劃遷移至 SDK 替代方案。不過,我們瞭解有些應用程式可使用非 SDK 介面運作。如果您除了為應用程式中的某個功能使用非 SDK 介面外,已別無他法,則應要求新的公用 API。
如需更多資訊,請參閱「Android 10 的非 SDK 介面限制更新內容」和「非 SDK 介面的限制」。
手勢操作
從 Android 10 開始,使用者可以在裝置上啟用手勢操作功能。如果使用者啟用手勢操作,無論應用程式是否指定 API 級別 29,都會影響裝置上的所有應用程式。舉例來說,如果使用者從螢幕邊緣向內滑動,系統會將該手勢解讀為返回導覽,除非應用程式針對螢幕部分特別覆寫該手勢。
如要讓應用程式與手勢導覽相容,您需要將應用程式內容從邊緣延伸到邊緣,並適當處理衝突的手勢。詳情請參閱「手勢導覽」說明文件。
NDK
Android 10 包含下列 NDK 變更。
共用物件不得包含文字重新安置
Android 6.0 (API 級別 23) 禁止使用共用物件中的文字再定位。程式碼必須維持原樣載入,且不得修改。這項異動可縮短應用程式載入時間並提升安全性。
SELinux 會對指定 Android 10 以上版本為目標版本的應用程式強制執行這項限制。如果這些應用程式繼續使用含有文字重新安置項目的共用物件,就很可能會發生故障。
Bionic 程式庫和動態連結器路徑的變更
自 Android 10 起,多個路徑是符號連結,而非一般檔案。依賴路徑為一般檔案的應用程式可能會中斷:
/system/lib/libc.so
->/apex/com.android.runtime/lib/bionic/libc.so
/system/lib/libm.so
->/apex/com.android.runtime/lib/bionic/libm.so
/system/lib/libdl.so
->/apex/com.android.runtime/lib/bionic/libdl.so
/system/bin/linker
->/apex/com.android.runtime/bin/linker
這些變更也適用於檔案的 64 位元變化版本,lib/
會替換為 lib64/
。
為確保相容性,系統會在舊路徑中提供符號連結。例如,/system/lib/libc.so
是 /apex/com.android.runtime/lib/bionic/libc.so
的符號連結。因此 dlopen(“/system/lib/libc.so”)
會繼續運作,但當應用程式實際嘗試透過讀取 /proc/self/maps
或類似內容來檢查已載入的程式庫時,就會發現差異,這並非常見,但我們發現某些應用程式會在防駭過程中執行這項操作。如果是,請將 /apex/…
路徑新增為 Bionic 檔案的有效路徑。
系統二進位檔/程式庫已對應至僅供執行的記憶體
自 Android 10 起,系統二進位檔和程式庫的可執行部分會對應至記憶體執行 (不可讀取),做為防範程式碼重用攻擊的強化技術。如果您的應用程式執行讀取作業,且記憶體區段標示為僅執行 (無論是因為錯誤、漏洞或有意記憶體檢查),系統會向應用程式傳送 SIGSEGV
信號。
您可以檢查 /data/tombstones/
中的相關空值檔案,判斷這項行為是否導致當機。僅執行相關的當機事件包含以下中止訊息:
Cause: execute-only (no-read) memory access error; likely due to data in .text.
為解決這個問題並執行記憶體檢查等作業,您可以呼叫 mprotect()
,將只執行片段標示為讀取 + 執行。不過,我們強烈建議您之後將其設回「僅執行」權限,因為這項存取權限設定可為您的應用程式和使用者提供更完善的保護。
安全性
Android 10 包含下列安全性變更。
預設啟用 TLS 1.3
在 Android 10 以上版本中,所有 TLS 連線均預設啟用 TLS 1.3。以下是有關實作 TLS 1.3 的幾項重要細節:
- TLS 1.3 加密套件不開放自訂。啟用 TLS 1.3 時,一律會啟用支援的 TLS 1.3 加密套件。若嘗試呼叫
setEnabledCipherSuites()
來停用這些功能,系統會予以忽略。 - 協商 TLS 1.3 時,系統會先呼叫
HandshakeCompletedListener
物件,再將工作階段加入工作階段快取。(在 TLS 1.2 和其他舊版本中,這些物件是在工作階段新增至工作階段快取後才呼叫。) - 在某些情況下,如果
SSLEngine
例項在舊版 Android 上擲回SSLHandshakeException
,這些例項會在 Android 10 以上版本擲回SSLProtocolException
。 - 不支援 0-RTT 模式。
如有需要,您可以呼叫 SSLContext.getInstance("TLSv1.2")
,取得已停用 TLS 1.3 的 SSLContext
。您也可以對適當物件呼叫 setEnabledProtocols()
,根據個別連線啟用或停用通訊協定版本。
使用 SHA-1 簽署的憑證不受 TLS 信任
在 Android 10 中,TLS 連線不會信任使用 SHA-1 雜湊演算法的憑證。根 CA 自 2016 年起就未曾核發這類憑證,Chrome 或其他主要瀏覽器也不再信任這類憑證。
如果連線目標為使用 SHA-1 憑證的網站,則任何連線嘗試都會失敗。
KeyChain 行為變更和強化措施
Google Chrome 等部分瀏覽器會在 TLS 握手期間,趁著 TLS 伺服器傳送憑證要求訊息時,允許使用者選擇憑證。在 Android 10,KeyChain
物件會在呼叫 KeyChain.choosePrivateKeyAlias()
來向使用者顯示憑證選擇提示時,遵循頒發單位和金鑰規格參數。具體來說,此提示含有的選項皆符合伺服器規格。
如果沒有可供使用者選取的憑證,例如在憑證不符合伺服器規格,或裝置未安裝任何憑證的情況下,則系統根本不會顯示憑證選取提示。
此外,Android 10 以上版本不必設定裝置螢幕鎖定,即可將金鑰或 CA 憑證匯入 KeyChain
物件。
其他 TLS 和密碼學變更
TLS 和密碼學程式庫有幾項小幅變更,會在 Android 10 生效:
- AES/GCM/NoPadding 和 ChaCha20/Poly1305/NoPadding 加密演算法會從
getOutputSize()
傳回更準確的緩衝區大小。 - 如果嘗試連線時使用的最高通訊協定版本為 TLS 1.2 以上版本,系統會省略
TLS_FALLBACK_SCSV
加密套件。TLS 伺服器實作程序已經過提升,因此不建議嘗試採用 TLS 外部備用機制,而應採用 TLS 版本協商。 - ChaCha20-Poly1305 是 ChaCha20/Poly1305/NoPadding 的別名。
- 以點結尾的主機名稱並非有效的 SNI 主機名稱。
- 選擇憑證回應的簽署金鑰時,系統會依循
CertificateRequest
中的 supported_signature_algorithms 擴充功能。 - 在 TLS 中,Android KeyStore 金鑰等不透明的簽署金鑰可以與 RSA-PSS 簽署搭配使用。
Wi-Fi Direct 廣播
在 Android 10 中,下列與 Wi-Fi Direct 相關的廣播不會保留:
如果您的應用程式會在註冊時接收這些廣播訊息,因為這些廣播訊息會持續存在,請改為在初始化時使用適當的 get()
方法來取得資訊。
Wi-Fi Aware 功能
Android 10 新增了支援功能,可透過 Wi-Fi Aware 資料路徑輕鬆建立 TCP/UDP 套接字。如要建立連線至 ServerSocket
的 TCP/UDP 套接字,用戶端裝置必須知道伺服器的 IPv6 位址和通訊埠。這項資訊先前必須透過非頻道通訊,例如使用 BT 或 Wi-Fi Aware 層 2 訊息,或是使用 mDNS 等其他通訊協定進行頻道內探索。在 Android 10 中,您可以透過網路設定傳送資訊。
伺服器可以執行下列任一操作:
- 初始化
ServerSocket
,並設定或取得要使用的通訊埠。 - 在 Wi-Fi Aware 網路要求中指定通訊埠資訊。
以下程式碼範例說明如何在網路要求中指定連接埠資訊:
Kotlin
val ss = ServerSocket() val ns = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle) .setPskPassphrase("some-password") .setPort(ss.localPort) .build() val myNetworkRequest = NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE) .setNetworkSpecifier(ns) .build()
Java
ServerSocket ss = new ServerSocket(); WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier .Builder(discoverySession, peerHandle) .setPskPassphrase(“some-password”) .setPort(ss.getLocalPort()) .build(); NetworkRequest myNetworkRequest = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE) .setNetworkSpecifier(ns) .build();
接著,用戶端會執行 Wi-Fi Aware 網路要求,取得伺服器提供的 IPv6 和通訊埠:
Kotlin
val callback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { ... } override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) { ... } override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { ... val ti = networkCapabilities.transportInfo if (ti is WifiAwareNetworkInfo) { val peerAddress = ti.peerIpv6Addr val peerPort = ti.port } } override fun onLost(network: Network) { ... } }; connMgr.requestNetwork(networkRequest, callback)
Java
callback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { ... } @Override public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { ... } @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { ... TransportInfo ti = networkCapabilities.getTransportInfo(); if (ti instanceof WifiAwareNetworkInfo) { WifiAwareNetworkInfo info = (WifiAwareNetworkInfo) ti; Inet6Address peerAddress = info.getPeerIpv6Addr(); int peerPort = info.getPort(); } } @Override public void onLost(Network network) { ... } }; connMgr.requestNetwork(networkRequest, callback);
在 Go 裝置上操作 SYSTEM_ALERT_WINDOW
在 Android 10 (Go edition) 裝置上執行的應用程式無法接收 SYSTEM_ALERT_WINDOW
權限。這是因為繪製疊加層視窗會使用過多記憶體,這對記憶體不足的 Android 裝置效能特別有害。
如果在搭載 Android 9 以下版本的 Go 版裝置上執行的應用程式收到 SYSTEM_ALERT_WINDOW
權限,即使裝置升級至 Android 10,應用程式仍會保留該權限。不過,如果應用程式原本就沒有該權限,裝置升級後就無法授予該權限。
如果 Go 裝置上的應用程式傳送含有動作 ACTION_MANAGE_OVERLAY_PERMISSION
的意圖,系統會自動拒絕要求,並將使用者帶往「設定」畫面,指出權限因會減緩裝置速度而遭到禁止。如果 Go 裝置上的應用程式呼叫 Settings.canDrawOverlays()
,該方法一律會傳回 false。再次提醒,如果應用程式是在裝置升級至 Android 10 之前獲得 SYSTEM_ALERT_WINDOW
權限,則不受這些限制影響。
針對指定舊版 Android 版本的應用程式發出警告
搭載 Android 10 以上版本的裝置會在使用者首次執行任何以 Android 5.1 (API 級別 22) 以下版本為目標的應用程式時發出警告。如果應用程式需要使用者授予權限,在允許應用程式首次執行前,使用者也有機會調整應用程式的權限。
由於 Google Play 的目標 API 級別規定,使用者只有在執行最近未更新的應用程式時,才會看到這些警告。對於透過其他商店發行的應用程式,類似的目標 API 級別規定將於 2019 年生效。如要進一步瞭解這些規定,請參閱「2019 年擴大目標 API 級別規定」。
已移除 SHA-2 CBC 加密套件
下列 SHA-2 CBC 加密組合已從平台中移除:
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
這些加密套件比使用 GCM 的類似加密套件安全性較低,且大多數伺服器都會同時支援 GCM 和 CBC 這些加密套件的變化版本,或是不支援這兩者。
應用程式使用情況
Android 10 引進了與應用程式使用行為相關的下列變更:
HTTPS 連線變更
如果執行 Android 10 的應用程式將 null
傳遞至 setSSLSocketFactory()
,就會發生 IllegalArgumentException
。在先前版本中,將 null
傳遞至 setSSLSocketFactory()
的效果與傳入目前預設工廠的效果相同。
android.preference 程式庫已淘汰
android.preference
程式庫已自 Android 10 起淘汰。開發人員應改用 AndroidX 偏好設定資料庫,這是 Android Jetpack 的一部分。如需其他資源協助遷移和開發作業,請參閱更新版的設定指南,以及我們的公開範例應用程式和參考說明文件。
ZIP 檔案公用程式庫異動
Android 10 對 java.util.zip
套件中的類別 (用於處理 ZIP 檔案) 做出下列變更。這些變更可讓 Android 和其他使用 java.util.zip
的平台之間的程式庫行為更加一致。
Inflater
在先前版本中,如果在呼叫 end()
後叫用 Inflater
類別中的某些方法,這些方法會擲回 IllegalStateException
。在 Android 10 中,這些方法會改為擲回 NullPointerException
。
ZipFile
在 Android 10 以上版本中,如果提供的 ZIP 檔案不含任何檔案,則ZipFile
建構函式 (會採用 File
、int
和 Charset
類型的引數) 不會擲回 ZipException
。
ZipOutputStream
在 Android 10 以上版本中,如果 ZipOutputStream
中的 finish()
方法嘗試為不含任何檔案的 ZIP 檔案寫入輸出串流,就不會擲回 ZipException
。
攝影機變更
許多使用相機的應用程式都會假設,如果裝置處於直向設定,實體裝置也處於直向,如「相機方向」一文所述。這在過去是安全的假設,但隨著可用機型 (例如折疊式裝置) 的擴增,這個假設已發生變化。在這些裝置上,這項假設可能會導致相機觀景窗顯示畫面旋轉或縮放不正確 (或兩者皆是)。
指定 API 級別 24 以上版本的應用程式應明確設定 android:resizeableActivity
,並提供必要功能來處理多視窗作業。
電池用量追蹤
從 Android 10 開始,SystemHealthManager
會在裝置經歷主要充電事件後拔除插頭時,重設電池用量統計資料。一般來說,主要充電事件是指裝置已充飽電,或裝置從幾乎沒電到幾乎充飽電。
在 Android 10 之前,無論電池電量有多少變化,只要拔除裝置插頭,電池用量統計資料就會重設。
已淘汰的 Android Beam
在 Android 10 中,我們正式淘汰 Android Beam,這是一項舊功能,可透過近距離無線通訊 (NFC) 在裝置之間啟動資料分享。我們也將淘汰多個相關的 NFC API。裝置製造商合作夥伴仍可選擇使用 Android Beam,但該功能已不再積極開發。不過,Android 仍會繼續支援其他 NFC 功能和 API,讀取標籤和付款等用途也會繼續正常運作。
java.math.BigDecimal.stripTrailingZeros() 行為變更
如果輸入值為零,BigDecimal.stripTrailingZeros()
就不會再將尾零保留為特殊情況。
java.util.regex.Matcher 和 Pattern 行為變更
當輸入內容開頭有零寬度比對時,split()
的結果已變更為不再以空白 String
開頭 (「"」)。這也會影響 String.split()
。舉例來說,"x".split("")
現在會傳回 {"x"}
,但在舊版 Android 上,它原本會傳回 {"", "x"}
。"aardvark".split("(?=a)"
現在會傳回 {"a", "ardv", "ark"}
,而非 {"", "a", "ardv", "ark"}
。
無效引數的例外狀況行為也已改善:
- 如果替換的
String
結尾為非法的單一反斜線,appendReplacement(StringBuffer, String)
現在會擲回IllegalArgumentException
,而非IndexOutOfBoundsException
。如果替換的String
結尾為$
,現在會擲回相同的例外狀況。先前,在這種情況下不會擲回例外狀況。 - 如果
Matcher
擲回NullPointerException
,replaceFirst(null)
就不會再對其呼叫reset()
。在找不到相符項目時,系統也會擲回NullPointerException
。先前,系統只會在比對成功時擲回此例外狀況。 - 如果群組索引超出範圍,
start(int group)
、end(int group)
和group(int group)
現在會擲回較通用的IndexOutOfBoundsException
。先前,這些方法會擲回ArrayIndexOutOfBoundsException
。
GradientDrawable 的預設角度現在為 TOP_BOTTOM
在 Android 10 中,如果您在 XML 中定義 GradientDrawable
,但未提供角度測量值,漸層方向預設為 TOP_BOTTOM
。這項變更與先前版本的 Android 不同,當時預設值為 LEFT_RIGHT
。
解決方法是,如果您更新至最新版的 AAPT2,且未指定角度測量值,工具會為舊版應用程式設定 0 的角度測量值。
使用預設 SUID 為序列化物件記錄
自 Android 7.0 (API 級別 24) 起,平台修正了可序列化物件的預設 serialVersionUID
。這項修正不會影響以 API 級別 23 以下為目標版本的應用程式。
自 Android 10 起,如果應用程式指定 API 級別 23 以下版本,且依賴舊的、不正確的預設 serialVersionUID
,系統會記錄警告,並建議修正程式碼。
具體來說,如果符合下列所有條件,系統就會記錄警告:
- 應用程式指定 API 級別 23 以下。
- 類別已序列化。
- 序列化的類別會使用預設
serialVersionUID
,而非明確設定serialVersionUID
。 - 預設的
serialVersionUID
與應用程式指定的 API 級別 24 以上級別的serialVersionUID
不同。
系統會針對每個受影響的類別記錄一次這項警告。警告訊息會提供建議的修正方式,也就是將 serialVersionUID
明確設為預設值,如果應用程式指定的 API 級別為 24 以上,系統會計算這個預設值。透過這個修正程式,您可以確保在目標 API 級別為 23 以下的應用程式上序列化該類別的物件時,目標 API 級別為 24 以上版本的應用程式可以正確讀取該物件,反之亦然。
java.io.FileChannel.map() 異動
自 Android 10 起,FileChannel.map()
不支援非標準檔案 (例如 /dev/zero
),因為無法使用 truncate()
變更其大小。先前版本的 Android 會吞噬 truncate()
傳回的 errno,但 Android 10 會擲回 IOException。如果您需要舊版行為,則必須使用原生程式碼。