Android 7.0 行為變更

除了新功能之外,Android 7.0 版還包含多項系統和 API 行為變更。本文件特別介紹了一些您應該瞭解並描述在應用程式中的重要異動。

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

電池與記憶體

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

打盹

在 Android 6.0 (API 級別 23) 中導入後,「打盹」功能會在使用者離開裝置電源、靜止不動、螢幕關閉時,延後 CPU 和網路活動,藉此改善電池壽命。Android 7.0 能在裝置未插電時套用部分 CPU 和網路限制,同時在使用者離開螢幕時套用部分 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 以下版本,系統會自動終止其所有背景程序。也就是說,如果使用者從這類應用程式改為開啟「設定」畫面並變更「顯示大小」設定,系統會按照在記憶體不足的情況下終止應用程式的方式終止應用程式。如果應用程式有任何前景程序,系統會根據「處理執行階段變更」所述,通知這些設定變更的程序,就像裝置螢幕方向已變更一樣。
  • 如果應用程式以 Android 7.0 為目標版本,則其所有程序 (前景和背景) 都會收到設定變更的通知,如「處理執行階段變更」所述。

只要應用程式遵循 Android 最佳做法,就無須進行任何調整,即可支援這項功能。請特別注意以下幾點:

  • 在螢幕寬度 sw320dp 的裝置上測試應用程式,確認應用程式可以正常運作。
  • 裝置設定變更時,請更新所有與密度相關的快取資訊,例如快取的點陣圖或從網路載入的資源。當應用程式從暫停狀態繼續執行時,檢查設定變更。

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

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

設定精靈中的視覺設定

Android 7.0 的歡迎畫面提供 Vision 設定,可讓使用者在新裝置上設定下列無障礙設定:放大手勢字型大小顯示大小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 會產生警告,並在目標裝置上顯示浮動式訊息來通知您。如果您看到這些警告,請更新應用程式,加入其自己的程式庫副本,或是只使用公開 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 中的唯讀工具可讓您執行下列指令,為指定 .so 檔案產生所有動態連結共用程式庫的清單:

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

更新應用程式

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

  • 如果您的應用程式使用私人平台程式庫,則應更新應用程式以加入其專屬的程式庫副本,或使用公用 NDK API
  • 如果您的應用程式使用會存取私人符號的第三方程式庫,請與程式庫作者聯絡以更新程式庫。
  • 請務必將所有非 NDK 程式庫封裝與 APK。
  • libandroid_runtime.so 中使用標準 JNI 函式,而不要使用 getJavaVMgetJNIEnv
    AndroidRuntime::getJavaVM -> GetJavaVM from <jni.h>
    AndroidRuntime::getJNIEnv -> JavaVM::GetEnv or
    JavaVM::AttachCurrentThread from <jni.h>.
    
  • libcutils.so 中使用 __system_property_get 而非私人 property_get 符號。方法是搭配以下項目使用 __system_property_get
    #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() 的結果。如有必要,請以 .crt 或 .cer 副檔名,使用 DER 編碼格式,將 CA 憑證單獨安裝到信任的憑證儲存空間。
  • 從 Android 7.0 開始,每位使用者的指紋註冊和儲存空間都會受到管理。如果設定檔擁有者的 Device Policy 用戶端 (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 工作。
  • 如果 Android 7.0 上具有 DELETE_PACKAGES 權限的應用程式嘗試刪除套件,但安裝了該套件,則系統會要求使用者進行確認。在這種情況下,應用程式叫用 PackageInstaller.uninstall() 時,應預期 STATUS_PENDING_USER_ACTION 為傳回狀態。
  • 名為「Crypto」的 JCA 提供者已淘汰,因為其唯一的演算法 SHA1PRNG 處於弱強度。應用程式無法再將 SHA1PRNG 用於 (不安全的) 衍生金鑰,因為這個提供者已無法使用。詳情請參閱「在 Android N 中淘汰的安全性「加密編譯」供應商」網誌文章。