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 版本中,應用程式可以註冊,以便接收來自其他應用程式 (例如 Camera) 的隱含 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) 單位指定尺寸。

設定精靈中的 Vision 設定

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 中的平台程式庫相容性要求,因此不同的裝置可能會提供不同等級的相容性。

為降低這項限制可能對目前發布的應用程式造成的影響,這類程式庫在 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 Public 不限 可存取 可正常運作 可正常運作
私人 (可暫時存取的私人程式庫) 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 警告會指出哪些程式庫嘗試存取 Private Platform 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 <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() 的結果。如有需要,請透過「設定」使用者介面另外將 CA 憑證安裝在信任的憑證儲存空間中,副檔名為 .crt 或 .cer 的副檔名使用 DER 編碼格式。
  • 從 Android 7.0 開始,系統會依使用者管理指紋註冊和儲存空間。如果設定檔擁有者的 Device Policy 用戶端 (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 上的應用程式應能夠妥善處理設定變更,而且不會在後續啟動時異常終止。如要驗證應用程式行為,請變更字型大小 (依序選取「Setting」(設定) >「Display」(顯示) >「Font size」(字型大小)),然後從「最近」還原應用程式。
  • 由於舊版 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 工作排入佇列;而 Runnable 工作要等到 View 附加至視窗之後才會執行。此行為修正了下列錯誤:
    • 如果應用程式從目標視窗的 UI 執行緒以外的執行緒發布至 ViewRunnable 可能會因此在錯誤的執行緒上執行。
    • 如果 Runnable 工作是從迴圈執行緒以外的執行緒發布,則應用程式可能會公開 Runnable 工作。
  • 如果搭載 DELETE_PACKAGES 權限的 Android 7.0 應用程式嘗試刪除套件,但其他應用程式安裝了該套件,系統會要求使用者確認。在這種情況下,應用程式應在叫用 PackageInstaller.uninstall() 時將 STATUS_PENDING_USER_ACTION 做為傳回狀態。
  • 名為 Crypto 的 JCA 提供者已淘汰,因為該供應商唯一的演算法 SHA1PRNG 經過加密強度不足。這個供應器已無法使用,因此應用程式無法再使用 SHA1PRNG 進行 (不安全) 產生的金鑰。詳情請參閱在 Android N 中淘汰安全性「Crypto」提供者的網誌文章。