Android 7.0 行為變更

除了新功能外,Android 7.0 也提供多種系統和 API 行為變更。本文件特別說明您應瞭解的幾項重要異動,並考量到應用程式中。

如果您先前曾發布 Android 應用程式,請注意,您的應用程式可能會受到這些平台變更的影響。

電池和記憶體

Android 7.0 包含系統行為變更,旨在改善裝置的電池續航力,並減少 RAM 用量。這些變更可能會影響應用程式對系統資源的存取權,以及應用程式透過特定隱含意圖與其他應用程式互動的方式。

打盹

已在 Android 6.0 (API 級別 23) 中導入,當使用者離開裝置未插電、靜止以及關閉螢幕時,打盹功能會延後 CPU 和網路活動,藉此延長電池續航力。Android 7.0 會在裝置未接上電源的情況下套用部分 CPU 和網路限制,藉此進一步強化打盹功能,但在使用者自行關閉螢幕時,裝置並不會插到電源,而是會套用部分 CPU 和網路限制。

插圖:Doze 如何套用第一層系統活動限制,以延長電池續航力

圖 1. 插圖:打盹功能如何套用第一層的系統活動限制,藉此延長電池續航力。

當裝置使用電池供電,且螢幕已關閉一段時間後,裝置就會進入 Doze 模式,並套用第一組子集限制:關閉應用程式網路存取權,並延遲工作和同步作業。如果裝置進入 Doze 模式後處於閒置狀態一段時間,系統會將其他 Doze 限制套用至 PowerManager.WakeLockAlarmManager 鬧鐘、GPS 和 Wi-Fi 掃描。無論是否套用部分或所有打盹限制,系統都會喚醒裝置的短暫維護期間。在此期間,應用程式會獲得網路存取權,並能執行任何延遲工作/同步處理作業。

圖示:當裝置閒置一段時間後,Doze 如何套用第二層系統活動限制

圖 2. 圖示說明 Doze 在裝置閒置一段時間後,如何套用第二層系統活動限制。

請注意,啟用螢幕開啟或插入裝置會退出 Doze 模式,並移除這些處理限制。如「 針對打盹和應用程式待命最佳化」一文所述,這項額外行為不會影響針對 Android 6.0 (API 級別 23) 推出的舊版打盹功能,讓應用程式適應這項功能的最佳做法和最佳做法。您仍應遵循這些建議,例如使用 Firebase 雲端通訊 (FCM) 傳送及接收訊息,並開始規劃更新,以便配合額外的 Doze 行為。

Project Svelte:背景最佳化

Android 7.0 移除了三個隱含廣播,以便同時最佳化記憶體使用量和耗電量。這項變更是必要的,因為隱含廣播經常會啟動已註冊在背景監聽的應用程式。移除這些廣播可大幅提升裝置效能和使用者體驗。

行動裝置經常發生連線情況變動,例如切換 Wi-Fi 和行動數據時。目前,應用程式可以在資訊清單中為隱式 CONNECTIVITY_ACTION 廣播註冊接收器,以便監控連線狀態變化。由於許多應用程式都註冊了這項廣播,因此單一網路切換動作就可能會喚醒所有應用程式,並一次處理廣播。

同樣地,在舊版 Android 中,應用程式可以註冊接收來自其他應用程式 (例如相機) 的隱含 ACTION_NEW_PICTUREACTION_NEW_VIDEO 廣播。當使用者透過相機應用程式拍照時,系統會喚醒這些應用程式來處理廣播。

為減輕這些問題的影響,Android 7.0 會套用下列最佳化調整:

如果應用程式有使用上述任一意圖,建議您盡快移除相關依附元件,才能正確指定 Android 7.0 裝置。Android 架構提供數種解決方案,可降低對於隱式廣播的需求。舉例來說,JobScheduler API 提供強大的機制,可在符合指定條件 (例如連線至非計量付費網路) 時排定網路作業。您甚至可以使用 JobScheduler 回應內容供應器的變更。

如要進一步瞭解 Android 7.0 (API 級別 24) 的背景最佳化功能,以及如何調整應用程式,請參閱「背景最佳化」。

權限變更

Android 7.0 包含對應用程式可能會有影響的權限變更。

檔案系統權限變更

為了提升私人檔案的安全性,以 Android 7.0 以上版本為目標版本的應用程式,其私人目錄會設有存取限制 (0700)。這項設定可防止私人檔案的中繼資料外洩,例如檔案大小或存在狀態。這項權限異動會產生多項副作用:

在應用程式之間共用檔案

針對指定 Android 7.0 為目標版本的應用程式,Android 架構會強制執行 StrictMode API 政策,禁止在應用程式外部公開 file:// URI。如果含有檔案 URI 的意圖離開應用程式,應用程式會失敗並顯示 FileUriExposedException 例外狀況。

如要在應用程式之間分享檔案,您應傳送 content:// URI,並授予 URI 臨時存取權。如要授予這項權限,最簡單的方法就是使用 FileProvider 類別。如要進一步瞭解權限和共用檔案,請參閱「共用檔案」。

無障礙功能再進化

Android 7.0 版內含改善平台,讓視障使用者更容易使用的平台。一般來說,這類變更不需要變更應用程式的程式碼,但建議您檢查這些功能,並使用應用程式進行測試,評估對使用者體驗的潛在影響。

畫面縮放

Android 7.0 可讓使用者設定顯示大小,放大或縮小畫面上的所有元素,進而改善低視能使用者的裝置無障礙性。使用者無法將畫面縮放至螢幕寬度下限 sw320dp,這是 Nexus 4 (中型手機) 的寬度。

螢幕顯示搭載 Android 7.0 系統映像檔的裝置未經縮放的顯示大小
螢幕畫面顯示對搭載 Android 7.0 系統映像檔的裝置增加顯示大小的效果

圖 3. 右側畫面顯示增加搭載 Android 7.0 系統映像檔的裝置顯示大小的效果。

當裝置密集度變更時,系統會以以下方式通知執行中的應用程式:

  • 如果應用程式指定的 API 級別為 23 以下,系統會自動終止所有背景程序。也就是說,如果使用者從這類應用程式切換至「設定」畫面,然後變更「螢幕大小」設定,系統會以記憶體不足時相同的方式終止應用程式。如果應用程式有任何前景程序,系統會通知這些程序設定變更,如「處理執行階段變更」一文所述,就像裝置方向變更一樣。
  • 如果應用程式指定 Android 7.0,則會通知所有前景和背景程序 (處理執行階段變更中所述) 設定變更。

只要應用程式遵循 Android 最佳做法,大多數應用程式都不需要變更任何內容即可支援這項功能。具體檢查項目:

  • 在螢幕寬度為 sw320dp 的裝置上測試應用程式,確保應用程式運作正常。
  • 當裝置設定有所變更時,請更新任何密度相關的快取資訊,例如從網路載入的快取位圖或資源。在應用程式從暫停狀態恢復時,檢查是否有設定變更。

    注意:如果您快取設定依附資料,建議您加入相關中繼資料,例如適當的螢幕大小或像素密度。儲存這項中繼資料可讓您決定是否需要在設定變更後重新整理快取資料。

  • 請勿使用 px 單位指定尺寸,因為這類單位不會隨著螢幕密度調整。請改用密度獨立像素 (dp) 單位指定尺寸。

設定精靈中的視覺輔助設定

Android 7.0 在歡迎畫面中提供「視覺設定」,使用者可以在新裝置上設定下列無障礙設定:放大手勢字型大小顯示大小TalkBack。這項變更可提高與不同螢幕設定相關的錯誤可見度。如要評估這項功能的影響,請在啟用這些設定的情況下測試應用程式。你可以在「設定」>「無障礙設定」下方找到這些設定。

連結至平台程式庫的 NDK 應用程式

從 Android 7.0 開始,系統會禁止應用程式以動態方式連結至非 NDK 程式庫,這可能導致應用程式異常終止。這項行為異動旨在跨平台更新和不同裝置提供一致的應用程式體驗。即使您的程式碼可能不會連結至私人程式庫,但應用程式中的第三方靜態程式庫可能會連結至私人程式庫。因此,所有開發人員都應檢查,確保應用程式不會在執行 Android 7.0 的裝置上當機。如果應用程式使用原生程式碼,您應僅使用公開 NDK API

應用程式可能會嘗試存取私人平台 API 的三種方式如下:

  • 應用程式直接存取私人平台程式庫。您應更新應用程式,以便納入這些程式庫的副本,或使用公開 NDK API
  • 您的應用程式使用第三方程式庫存取私人平台程式庫。即使您確定應用程式不會直接存取私人程式庫,仍應針對這類情況測試應用程式。
  • 您的應用程式參照的程式庫未包含在 APK 中。舉例來說,如果您嘗試使用自己的 OpenSSL 副本,但忘記將其與應用程式的 APK 捆綁,就可能會發生這種情況。應用程式可能會在包含 libcrypto.so 的 Android 平台版本上正常執行。不過,如果應用程式在未包含此程式庫的較新 Android 版本 (例如 Android 6.0 以上版本) 上執行,就可能會當機。如要修正這個問題,請務必將所有非 NDK 程式庫與 APK 一起封裝。

應用程式不應使用 NDK 中未包含的原生程式庫,因為這些程式庫可能會在不同 Android 版本之間變更或移除。從 OpenSSL 改用 BoringSSL 就是這類變更的例子。此外,由於 NDK 中不包含平台程式庫的相容性規定,因此不同裝置的相容性可能會有所差異。

為減少這項限制可能對目前發布的應用程式造成的影響,針對目標 API 級別 23 以下的應用程式,Android 7.0 (API 級別 24) 會暫時存取一系列經常使用的程式庫 (例如 libandroid_runtime.solibcutils.solibcrypto.solibssl.so)。如果應用程式載入其中一個程式庫,logcat 會產生警告,並在目標裝置上顯示 Toast 通知。如果您看到這些警告,請更新應用程式,以便納入這些程式庫的專屬副本,或是只使用公開的 NDK API。未來 Android 平台的版本可能會全面限制私人程式庫的使用,並導致應用程式當機。

當應用程式呼叫的 API 既非公開,也無法暫時存取時,所有應用程式都會產生執行階段錯誤。因此,System.loadLibrarydlopen(3) 都會傳回 NULL,可能導致應用程式異常終止。您應檢查應用程式程式碼,移除私人平台 API 的使用情形,並使用搭載 Android 7.0 (API 級別 24) 的裝置或模擬器,徹底測試應用程式。如果不確定應用程式是否使用私人程式庫,您可以檢查 Logcat,找出執行階段錯誤。

下表說明應用程式使用私人原生程式庫和目標 API 級別 (android:targetSdkVersion) 時,應會出現的行為。

程式庫 目標 API 級別 透過動態連結器存取執行階段 Android 7.0 (API 級別 24) 行為 未來 Android 平台的行為
NDK 公開 任何 容易存取 可正常運作 運作正常
私人 (可暫時存取的私人程式庫) 23 以下 暫時可用 可正常運作,但您會收到 Logcat 警告。 執行階段錯誤
私人 (可暫時存取的私人程式庫) 24 歲以上 受限制 執行階段錯誤 執行階段錯誤
私人 (其他) 任何 受限制 執行階段錯誤 執行階段錯誤

檢查應用程式是否使用私人程式庫

為協助您找出載入私人程式庫的問題,logcat 可能會產生警告或執行階段錯誤。舉例來說,如果應用程式的目標版本為 API 級別 23 以下,且嘗試在執行 Android 7.0 的裝置上存取私人程式庫,您可能會看到類似以下的警告:

03-21 17:07:51.502 31234 31234 W linker  : library "libandroid_runtime.so"
("/system/lib/libandroid_runtime.so") needed or dlopened by
"/data/app/com.popular-app.android-2/lib/arm/libapplib.so" is not accessible
for the namespace "classloader-namespace" - the access is temporarily granted
as a workaround for http://b/26394120

這些 Logcat 警告會告訴您哪個程式庫嘗試存取私人平台 API,但不會導致應用程式停止運作。不過,如果應用程式指定的 API 級別為 24 以上,Logcat 會產生下列執行階段錯誤,且應用程式可能會當機:

java.lang.UnsatisfiedLinkError: dlopen failed: library "libcutils.so"
("/system/lib/libcutils.so") needed or dlopened by
"/system/lib/libnativeloader.so" is not accessible for the namespace
"classloader-namespace"
  at java.lang.Runtime.loadLibrary0(Runtime.java:977)
  at java.lang.System.loadLibrary(System.java:1602)

如果應用程式使用以動態方式連結至私人平台 API 的第三方程式庫,也可以看到這些 Logcat 輸出內容。Android 7.0DK 中的 readelf 工具可以執行下列指令,產生特定 .so 檔案所有動態連結共用程式庫的清單:

aarch64-linux-android-readelf -dW libMyLibrary.so

更新應用程式

您可以採取下列步驟修正這類錯誤,並確保應用程式不會在日後的平台更新中當機:

  • 如果應用程式使用私人平台程式庫,您應更新應用程式,以便納入這些程式庫的專屬副本,或使用公開 NDK API
  • 如果您的應用程式使用會存取私人符號的第三方程式庫,請與程式庫作者聯絡,請他們更新程式庫。
  • 請務必將所有非 NDK 程式庫與 APK 一併封裝。
  • 請使用標準 JNI 函式,而非 libandroid_runtime.so 中的 getJavaVMgetJNIEnv
    AndroidRuntime::getJavaVM -> GetJavaVM from <jni.h>
    AndroidRuntime::getJNIEnv -> JavaVM::GetEnv or
    JavaVM::AttachCurrentThread from <jni.h>.
    
  • 請使用 __system_property_get,而不要使用 libcutils.so 中的私密 property_get 符號。如要這麼做,請使用 __system_property_get 搭配下列 include:
    #include <sys/system_properties.h>

    注意:系統屬性的可用性和內容並未透過 CTS 進行測試。更好的修正方式是完全避免使用這些屬性。

  • 使用 libcrypto.so 的本機版本 SSL_ctrl 符號。舉例來說,您應在 .so 檔案中靜態連結 libcyrpto.a,或納入 BoringSSL/OpenSSL 的動態連結 libcrypto.so 版本,並將其封裝在 APK 中。

Android for Work

Android 7.0 針對以 Android for Work 為目標版本的應用程式變更內容,包括變更憑證安裝、密碼重設、次要使用者管理和裝置 ID 存取權。如果您是為 Android for Work 環境建構應用程式,請查看這些變更,並據此修改應用程式。

  • 您必須先安裝委派憑證安裝程式,才能讓 DPC 設定憑證。對於指定 Android 7.0 (API 級別 24) 的設定檔和裝置擁有者應用程式,您應在裝置政策控制器 (DPC) 呼叫 DevicePolicyManager.setCertInstallerPackage() 之前,先安裝委派憑證安裝程式。如果未安裝安裝程式,系統會擲回 IllegalArgumentException
  • 裝置管理員的重設密碼限制現在也適用於設定檔擁有者。裝置管理員無法再使用 DevicePolicyManager.resetPassword() 清除密碼或變更已設定的密碼。裝置管理員仍可設定密碼,但前提是裝置沒有密碼、PIN 碼或解鎖圖案。
  • 即使已設定限制,裝置和設定檔擁有者仍可管理帳戶。即使已設定 DISALLOW_MODIFY_ACCOUNTS 使用者限制,裝置擁有者和設定檔擁有者仍可呼叫 Account Management API。
  • 裝置擁有者可以更輕鬆地管理次要使用者。當裝置在裝置擁有者模式下執行時,系統會自動設定 DISALLOW_ADD_USER 限制。這可防止使用者建立未受管理的次要使用者。此外,CreateUser()createAndInitializeUser() 方法已淘汰,並由新的 DevicePolicyManager.createAndManageUser() 方法取代。
  • 裝置擁有者可以存取裝置 ID。裝置擁有者可以使用 DevicePolicyManager.getWifiMacAddress() 存取裝置的 Wi-Fi MAC 位址。如果裝置從未啟用 Wi-Fi,這個方法會傳回 null 的值。
  • 「工作模式」設定可控管工作應用程式的存取權。關閉工作模式後,系統啟動器會將工作應用程式設為灰色,表示無法使用。再次啟用工作模式後,系統會恢復正常運作。
  • 從「設定」使用者介面安裝含有用戶端憑證鏈結和相應私密金鑰的 PKCS #12 檔案時,鏈結中的 CA 憑證將不再安裝至信任的憑證儲存空間。當應用程式稍後嘗試擷取用戶端憑證鏈結時,這不會影響 KeyChain.getCertificateChain() 的結果。如有需要,請透過設定 UI 將 CA 憑證分別安裝至信任的憑證儲存空間,並以 .crt 或 .cer 副檔名下的 DER 編碼格式進行。
  • 從 Android 7.0 開始,指紋註冊和儲存空間會由個別使用者管理。如果設定檔擁有者的裝置政策用戶端 (DPC) 指定的 API 級別為 23 以下 (或更低),且裝置搭載 Android 7.0 (API 級別 24),使用者仍可在裝置上設定指紋,但工作應用程式無法存取裝置指紋。如果 DPC 指定的 API 級別為 24 以上,使用者可以前往「設定」>「安全性」>「工作資料夾安全性」,針對工作資料夾設定指紋。
  • DevicePolicyManager.getStorageEncryptionStatus() 會傳回新的加密狀態 ENCRYPTION_STATUS_ACTIVE_PER_USER,表示加密功能已啟用,且加密金鑰已與使用者綁定。DPC 指定 API 級別 24 以上時,才會傳回新狀態。針對以舊版 API 級別為目標的應用程式,即使加密金鑰是特定使用者或設定檔的,也會傳回 ENCRYPTION_STATUS_ACTIVE
  • 在 Android 7.0 中,如果裝置安裝了工作資料夾,且有個別的工作驗證,則通常會影響整部裝置的多種方法,會以不同的方式運作。這些方法只會套用至工作資料夾,不會影響整部裝置。如需這類方法的完整清單,請參閱 DevicePolicyManager.getParentProfileInstance() 說明文件。舉例來說,DevicePolicyManager.lockNow() 只會鎖定工作資料夾,不會鎖定整個裝置。針對這些方法,您可以呼叫 DevicePolicyManager 的父項例,取得舊行為。您可以呼叫 DevicePolicyManager.getParentProfileInstance() 取得此父項。舉例來說,如果您呼叫父項例項的 lockNow() 方法,整部裝置就會上鎖。

註解保留

Android 7.0 修正了忽略註解瀏覽權限的錯誤。這個問題可讓執行階段存取不應無法存取的註解。這些註解包括:

  • VISIBILITY_BUILD:僅在建構期間顯示。
  • VISIBILITY_SYSTEM:這個屬性旨在讓基礎系統在執行階段顯示,但僅限於基礎系統。

如果您的應用程式已依賴這項行為,請為必須在執行階段提供的註解新增保留政策。您可以使用 @Retention(RetentionPolicy.RUNTIME) 執行這項操作。

TLS/SSL 預設設定異動

Android 7.0 對應用程式用於 HTTPS 和其他 TLS/SSL 流量的預設 TLS/SSL 設定進行了以下變更:

  • RC4 加密套件現已停用。
  • 現已啟用 CHACHA20-POLY1305 加密套件。

如果伺服器未協商新式加密套件,則預設停用 RC4 可能會導致 HTTPS 或 TLS/SSL 連線中斷。建議的修正方式是改善伺服器設定,啟用更強大、更現代的加密套件和通訊協定。理想情況下,應啟用 TLSv1.2 和 AES-GCM,並啟用並優先使用前向保密加密套件 (ECDHE)。

另一種做法是修改應用程式,以便使用自訂 SSLSocketFactory 與伺服器通訊。工廠應設計為建立 SSLSocket 例項,除了預設的加密套件外,還具備伺服器啟用的部分加密套件。

注意:這些變更與 WebView 無關。

鎖定 Android 7.0 的應用程式

這些行為變更僅適用於指定 Android 7.0 (API 級別 24) 以上版本的應用程式。如果應用程式針對 Android 7.0 編譯或 targetSdkVersion 設為 Android 7.0 以上版本,必須修改應用程式,確保能正確支援這些行為 (如適用)。

序列化變更

Android 7.0 (API 級別 24) 修正了預設 serialVersionUID 與規格不符的錯誤。

實作 Serializable 且未指定明確 serialVersionUID 欄位的類別,可能會看到預設 serialVersionUID 的變更,這會導致在嘗試將較舊版本序列化或由指定較舊版本的應用程式序列化的類別例項時,擲回例外狀況。錯誤訊息看起來會像這樣:

local class incompatible: stream classdesc serialVersionUID = 1234, local class serialVersionUID = 4567

如要修正這些問題,請將 serialVersionUID 欄位新增至任何受影響的類別,並使用錯誤訊息中的 stream classdesc serialVersionUID 值,例如本例中的 1234。這項異動遵循所有寫入序列化程式碼的良好做法建議,並且適用於所有 Android 版本。

修正的特定錯誤與靜態初始化方法 (即 <clinit>) 的存在有關。根據規格,如果類別中沒有靜態初始化器方法,則會影響為該類別計算的預設 serialVersionUID。 在錯誤修正之前,如果類別沒有靜態初始化器,也會檢查父類別是否有靜態初始化器。

請注意,這項變更不會影響以 API 級別 23 以下為目標的應用程式、具有 serialVersionUID 欄位的類別,或是具有靜態初始化方法的類別。

其他重要事項

  • 如果應用程式在 Android 7.0 版本執行,但指定較低的 API 級別,且使用者變更了顯示大小,系統就會終止應用程式程序。應用程式必須能妥善處理這種情況。否則,當使用者從「最近」還原時,應用程式就會當機。

    您應測試應用程式,確保不會發生這種行為。透過 DDMS 手動終止應用程式時,也可以造成相同的當機。

    指定 Android 7.0 (API 級別 24) 以上版本的應用程式不會在密度變更時自動終止,但仍可能會對設定變更做出不佳的回應。

  • Android 7.0 中的應用程式應能妥善處理設定變更,且不應在後續啟動時異常終止。您可以變更字型大小 (依序點選「設定」 >「顯示」 >「字型大小」),然後從「近期使用」還原應用程式,藉此確認應用程式的行為。
  • 由於舊版 Android 中的錯誤,系統並未將寫入主執行緒的 TCP 通訊端標示為違反嚴格模式的事件。Android 7.0 已修正此錯誤。顯示此行為的應用程式現在會擲回 android.os.NetworkOnMainThreadException。一般來說,在主執行緒執行網路作業並不理想,因為這類作業通常具有高度延遲,進而導致 ANR 和卡頓。
  • Debug.startMethodTracing() 方法系列現在預設會將輸出內容儲存在共用儲存空間的套件專屬目錄中,而非 SD 卡的頂層。也就是說,應用程式不再需要請求 WRITE_EXTERNAL_STORAGE 權限才能使用這些 API。
  • 許多平台 API 現已開始檢查跨 Binder 交易所傳送的大型酬載,而系統現在會將 TransactionTooLargeExceptions 調回 RuntimeExceptions,而不會無訊息地記錄或隱藏。一個常見的例子是,在 Activity.onSaveInstanceState() 中儲存過多資料,導致應用程式在指定 Android 7.0 時,ActivityThread.StopInfo 擲回 RuntimeException
  • 如果應用程式將 Runnable 工作發布至 View,而 View 未附加至視窗,系統會將 Runnable 工作與 View 排入佇列;Runnable 工作必須附加至視窗,才會執行。View這項行為修正了下列錯誤:
    • 如果應用程式從預定視窗的 UI 執行緒以外的執行緒發布至 ViewRunnable 就可能會在錯誤的執行緒上執行。
    • 如果 Runnable 工作是從迴圈器執行緒以外的執行緒發布,應用程式可能會公開 Runnable 工作。
  • 如果 Android 7.0 上具備 DELETE_PACKAGES 權限的應用程式嘗試刪除套件,但已安裝該套件的其他應用程式,則系統會要求使用者確認。在這種情況下,應用程式在叫用 PackageInstaller.uninstall() 時,應預期 STATUS_PENDING_USER_ACTION 為傳回狀態。
  • 名為 Crypto 的 JCA 提供者已淘汰,因為其唯一的演算法 SHA1PRNG 在密碼編譯方面較為薄弱。由於這個提供者已無法使用,因此應用程式無法再使用 SHA1PRNG 來產生 (不安全的) 金鑰。詳情請參閱「Android N 已淘汰安全性「Crypto」提供者」網誌文章。