Android 7.0 行為變更

除了新功能以外,Android 7.0 還包含多種系統和 API 行為變更。本文重點介紹應用程式中應瞭解並考量的一些重要異動。

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

電池與記憶體

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

打盹

導入於 Android 6.0 (API 級別 23) 時,「打盹」功能可在使用者離開裝置時啟動未接上電源、靜止不動,並在螢幕關閉時延遲 CPU 和網路活動,藉此提升電池續航力。Android 7.0 可進一步強化打盹功能,方法是在裝置未接上電源的情況下套用部分 CPU 和網路限制。

插圖:打盹功能套用第一級系統活動限制以延長電池續航力

圖 1 插圖:打盹功能套用第一層系統活動限制以延長電池續航力。

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

說明打盹功能如何在裝置閒置特定時間後套用第二層的系統活動限制

圖 2. 說明打盹功能如何在裝置閒置特定時間後套用第二層的系統活動限制。

請注意,啟用裝置螢幕或接上電源會結束打盹功能,系統會解除這些處理限制。其他行為不會影響應用程式根據 Android 6.0 (API 級別 23) 中導入的「打盹」功能,採用這個專區所推出的建議和最佳做法,詳情請參閱「 針對打盹和應用程式待命進行最佳化」。您仍應遵循這些建議,例如使用 Firebase 雲端通訊 (FCM) 收發訊息,並開始規劃更新以配合額外的打盹行為。

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 以下的版本,系統會自動終止所有背景程序。這表示當使用者離開這類應用程式來開啟「Settings」畫面並變更「Display size」設定時,系統會採用等同於在記憶體不足的情況下終止應用程式的方式。如果應用程式有任何前景程序,系統會按照「處理執行階段變更」中所述的方式通知設定變更的程序,就像裝置的螢幕方向已變更一樣。
  • 如果應用程式以 Android 7.0 為目標版本,則其所有程序 (前景和背景) 都會收到設定變更的通知,如「處理執行階段變更」一文所述。

大多數應用程式不需要進行任何變更即可支援這項功能,前提是應用程式必須遵循 Android 最佳做法。檢查事項:

  • 在螢幕寬度為 sw320dp 的裝置上測試應用程式,確定其效能良好。
  • 裝置設定變更時,請更新所有與密度相關的快取資訊,例如快取點陣圖或從網路載入的資源。在應用程式從暫停狀態恢復運作時,檢查設定變更。

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

  • 請勿使用 px 單位來指定尺寸,因為這類單位無法配合螢幕密度縮放。而是改用密度獨立像素 (dp) 單位來指定尺寸。

設定精靈中的影像設定

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

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

從 Android 7.0 開始,系統會禁止應用程式動態連結至非 NDK 程式庫,這可能導致應用程式異常終止。這項異動的目的在於確保平台更新和不同裝置的使用者享有一致的應用程式體驗。即使程式碼未連結至私人程式庫,應用程式中仍可能有第三方靜態程式庫。因此,所有開發人員都應檢查應用程式,確保應用程式不會在搭載 Android 7.0 的裝置上當機。如果您的應用程式使用原生程式碼,建議您只使用公開 NDK API

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

  • 您的應用程式會直接存取私人平台程式庫。建議您更新應用程式,以納入這些程式庫的副本,或使用公開 NDK API
  • 應用程式使用可存取私人平台程式庫的第三方程式庫。即使您確定應用程式不會直接存取私人程式庫,您仍應在此情況下測試應用程式。
  • 應用程式參照的程式庫不在其 APK 中。舉例來說,如果您使用自己的 OpenSSL 副本,但忘記將 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 會產生警告,並在目標裝置上顯示浮動式訊息以通知您。如果看到這類警告,建議您更新應用程式,以加入自己的程式庫副本,或僅使用公開 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.sogetJavaVMgetJNIEnv
    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 <sys/system_properties.h>
    

    注意:系統屬性的可用性和內容不會透過 CTS 測試。更理想的做法是避免同時使用這些屬性。

  • 使用 libcrypto.soSSL_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 的值。
  • 「工作模式」設定可控管工作應用程式的存取權。工作模式關閉時,系統啟動器會將工作應用程式顯示為灰色,表示無法使用。只要再次啟用工作模式,即可恢復一般行為。
  • 安裝含有用戶端憑證鏈結和設定 UI 中對應私密金鑰的 PKCS #12 檔案時,鏈結中的 CA 憑證將不再安裝至信任的憑證儲存空間。這不會影響應用程式之後嘗試擷取用戶端憑證鏈結時 KeyChain.getCertificateChain() 的結果。如有需要,請透過「設定 UI」將 CA 憑證分別安裝至信任的憑證儲存空間,並使用 .crt 或 .cer 副檔名做為 DER 編碼格式。
  • 從 Android 7.0 開始,每位使用者都會管理指紋註冊和儲存空間。如果設定檔擁有者的裝置政策用戶端 (DPC) 在搭載 Android 7.0 (API 級別 24) 的裝置上指定 API 級別 23 (或以下) 為目標,則使用者仍可在裝置上設定指紋,但工作應用程式無法存取裝置指紋。如果 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() 中儲存過多資料,導致 ActivityThread.StopInfo 在應用程式指定 Android 7.0 時擲回 RuntimeException
  • 如果應用程式將 Runnable 工作發布至 View,且 View 未附加至視窗,系統會使用 ViewRunnable 工作排入佇列;除非 View 附加至視窗,否則不會執行 Runnable 工作。這項行為修正了下列錯誤:
    • 如果應用程式從所需視窗的 UI 執行緒以外的執行緒發布至 ViewRunnable 可能會在錯誤的執行緒上執行。
    • 如果 Runnable 工作是從迴圈執行緒以外的執行緒發布,應用程式可能會公開 Runnable 工作。
  • 如果搭載 DELETE_PACKAGES 權限的 Android 7.0 應用程式嘗試刪除套件,但其他應用程式已安裝該套件,則系統會要求使用者確認。在這種情況下,應用程式叫用 PackageInstaller.uninstall() 時應預期 STATUS_PENDING_USER_ACTION 為傳回狀態。
  • 名為「Crypto」的 JCA 供應商已淘汰,因為其唯一的演算法 SHA1PRNG 強度不足。這個提供者已無法使用,因此應用程式無法再使用 SHA1PRNG 擷取 (不安全的) 金鑰。詳情請參閱網誌文章「Android N 中淘汰的安全性「Crypto」提供者」這篇網誌文章。