Android O 8.0 新特性(二) 行爲變更

Android 8.0 行爲變更

Android 8.0 除了提供諸多新特性和功能外,還對系統和 API 行爲做出了各種變更。本文重點介紹您應該瞭解並在開發應用時加以考慮的一些主要變更。

其中大部分變更會影響所有應用,而不論應用針對的是何種版本的 Android。不過,有幾項變更僅影響針對 Android 8.0 的應用。爲清楚起見,本頁面分爲兩個部分:針對所有 API 級別的應用針對 Android 8.0 的應用

針對所有 API 級別的應用


這些行爲變更適用於 在 Android 8.0 平臺上運行的 所有應用,無論這些應用是針對哪個 API 級別構建。所有開發者都應查看這些變更,並修改其應用以正確支持這些變更(如果適用)。

後臺執行限制

Android 8.0 爲提高電池續航時間而引入的變更之一是,當您的應用進入已緩存狀態時,如果沒有活動的組件,系統將解除應用具有的所有喚醒鎖。

此外,爲提高設備性能,系統會限制未在前臺運行的應用的某些行爲。具體而言:

  • 現在,在後臺運行的應用對後臺服務的訪問受到限制。
  • 應用無法使用其清單註冊大部分隱式廣播(即,並非專門針對此應用的廣播)。

默認情況下,這些限制僅適用於針對 O 的應用。不過,用戶可以從 Settings 屏幕爲任意應用啓用這些限制,即使應用並不是以 O 爲目標平臺。

Android 8.0 還對特定函數做出了以下變更:

  • 如果針對 Android 8.0 的應用嘗試在不允許其創建後臺服務的情況下使用 startService() 函數,則該函數將引發一個 IllegalStateException
  • 新的 Context.startForegroundService() 函數將啓動一個前臺服務。現在,即使應用在後臺運行,系統也允許其調用 Context.startForegroundService()。不過,應用必須在創建服務後的五秒內調用該服務的 startForeground() 函數。

如需瞭解詳細信息,請參閱後臺執行限制

Android 後臺位置限制

爲節約電池電量、保持良好的用戶體驗和確保系統健康運行,在運行 Android 8.0 的設備上使用後臺應用時,降低了後臺應用接收位置更新的頻率。此行爲變更會影響包括 Google Play 服務在內的所有接收位置更新的應用。

此類變更會影響以下 API:

  • Fused Location Provider (FLP)
  • Geofencing
  • GNSS Measurements
  • Location Manager
  • Wi-Fi Manager

爲確保您的應用按預期方式運行,請完成以下步驟:

  • 查看您的應用的邏輯,並確保您使用的是最新的位置 API。
  • 測試您的應用是否在每個用例中都表現出預期行爲。
  • 考慮使用 Fused Location Provider (FLP) 或地理圍欄來處理依賴於用戶當前位置的用例。

如需瞭解此類變更的詳細信息,請參閱後臺位置限制

應用快捷鍵

Android 8.0 對應用快捷方式做出了以下變更:

  • com.android.launcher.action.INSTALL_SHORTCUT 廣播不再會對您的應用有任何影響,因爲它現在是私有的隱式廣播。相反,您應使用 ShortcutManager 類中的 requestPinShortcut() 函數創建應用快捷方式。
  • 現在,ACTION_CREATE_SHORTCUT Intent 可以創建可使用 ShortcutManager 類進行管理的應用快捷方式。此 Intent 還可以創建不與 ShortcutManager 交互的舊版啓動器快捷方式。在以前,此 Intent 只能創建舊版啓動器快捷方式。
  • 現在,使用 requestPinShortcut() 創建的快捷方式和在處理 ACTION_CREATE_SHORTCUT Intent 的操作組件中創建的快捷方式均已轉換爲功能齊全的應用快捷方式。因此,應用現在可以使用 ShortcutManager 中的函數來更新這些快捷方式。
  • 舊版快捷方式仍然保留了它們在舊版 Android 中的功能,但您必須在應用中手動將它們轉換成應用快捷方式。

如需瞭解有關應用快捷方式變更的更多信息,請參閱固定快捷方式和微件預覽功能指南

語言區域和國際化

Android 7.0(API 級別 24)引入能指定默認類別語言區域的概念,但是某些 API 在本應使用默認 DISPLAY 類別語言區域時,仍然使用不帶參數的通用 Locale.getDefault() 函數。現在,在 Android 8.0 中,以下函數使用 Locale.getDefault(Category.DISPLAY) 來代替 Locale.getDefault()

當爲 Locale 參數指定的 displayScript 值不可用時,Locale.getDisplayScript(Locale) 同樣回退到 Locale.getDefault()

與語言區域和國際化有關的其他變更如下:

  • 調用 Currency.getDisplayName(null) 會引發 NullPointerException,以與文檔規定的行爲保持一致。
  • 時區名稱的分析方法發生變化。之前,Android 設備使用在啓動時取樣的系統時鐘值,緩存用於分析日期時間的時區名稱。因此,如果在啓動時或其他較爲罕見的情況下系統時鐘出錯,可能對分析產生負面影響。

    現在,一般情況下,在分析時區名稱時分析邏輯將使用 ICU 和當前系統時鐘值。此項變更可提供更加準確的結果,如果您的應用使用 SimpleDateFormat 等類,此結果可能與之前的 Android 版本不同。

  • Android 8.0 將 ICU 的版本更新至版本 58。

提醒窗口

如果應用使用 SYSTEM_ALERT_WINDOW 權限並且嘗試使用以下窗口類型之一來在其他應用和系統窗口上方顯示提醒窗口:

...那麼,這些窗口將始終顯示在使用 TYPE_APPLICATION_OVERLAY 窗口類型的窗口下方。如果應用針對的是 Android 8.0,則應用會使用 TYPE_APPLICATION_OVERLAY 窗口類型來顯示提醒窗口。

如需瞭解詳細信息,請參閱針對 Android 8.0 的應用的行爲變更內的提醒窗口的常用窗口類型部分。

輸入和導航

隨着 Android 應用出現在 Chrome 操作系統和平板電腦等其他大尺寸設備上,我們看到,用戶在 Android 應用中又重新開始使用鍵盤導航。在 Android 8.0 中,我們又再次使用鍵盤作爲導航輸入設備,從而爲基於箭頭鍵和 Tab 鍵的導航構建了一種更可靠並且可預測的模型。

尤其要指出的是,我們對元素焦點行爲做出以下變更:

  • 現在,如果您沒有爲 View 對象(前景或背景圖片)定義任何焦點狀態顏色,框架會爲 View 設置默認的焦點突出顯示顏色。此焦點突出顯示標誌是基於操作組件主題背景的漣漪圖片。

    如果您不希望 View 對象在接收焦點時使用此默認突出顯示標誌,請在包含 View 的佈局 XML 文件中將 android:defaultFocusHighlightEnabled 屬性設置爲 false,或者將 false 傳遞至應用界面邏輯中的 setDefaultFocusHighlightEnabled()

  • 要測試鍵盤輸入對界面元素焦點有何影響,您可以啓用 Drawing > Show layout bounds 開發者選項。在 Android 8.0 中,此選項在當前具有焦點的元素上顯示一個“X”圖標。

另外,Android 8.0 中的所有工具欄元素自動組成鍵盤導航鍵區,用戶可以更加輕鬆地導航進入和離開每個作爲一個整體的工具欄。

如需詳細瞭解如何在您的應用中改善對鍵盤導航的支持,請閱讀支持鍵盤導航指南。

網頁表單自動填充

現在,Android 自動填充框架提供對自動填充功能的內置支持,對於安裝到運行 Android 8.0 的設備上的應用,與 WebView 對象相關的下列函數已經發生變化:

WebSettings
WebViewDatabase
  • 調用 clearFormData() 不再有任何效果。
  • hasFormData() 函數現在返回 false。之前,當表單包含數據時,此函數返回 true

無障礙功能

現在,無障礙服務可識別應用的 TextView 對象內部的所有 ClickableSpan 實例。

如需瞭解有關如何讓您的應用更便於訪問的更多信息,請參閱無障礙功能


網絡連接和 HTTP(S) 連接

Android 8.0 對網絡連接和 HTTP(S) 連接行爲做出了以下變更:

  • 無正文的 OPTIONS 請求具有 Content-Length: 0 標頭。之前,這些請求沒有 Content-Length 標頭。
  • HttpURLConnection 在包含斜線的主機或頒發機構名稱後面附加一條斜線,使包含空路徑的網址規範化。例如,它將 http://example.com 轉化爲 http://example.com/
  • 通過 ProxySelector.setDefault() 設置的自定義代理選擇器僅針對所請求的網址(架構、主機和端口)。因此,僅可根據這些值選擇代理。傳遞至自定義代理選擇器的網址不包含所請求的網址的路徑、查詢參數或片段。
  • URI 不能包含空白標籤。

    之前,平臺支持一種權宜方法,即允許主機名稱中包含空白標籤,但這是對 URI 的非法使用。此權宜方法只是爲了確保與舊版 libcore 兼容。開發者如果對 API 使用不當,將會看到一條 ADB 消息:“URI example..com 的主機名包含空白標籤。此格式不正確,將不被未來的 Android 版本所接受。”Android 8.0 廢除了此權宜方法;系統對格式錯誤的 URI 會返回 null。

  • Android 8.0 在實現 HttpsURLConnection 時不會執行不安全的 TLS/SSL 協議版本回退。
  • 對隧道 HTTP(S) 連接處理進行了如下變更:
    • 在通過連接建立隧道 HTTP(S) 連接時,系統會在 Host 行中正確放置端口號 (:443) 並將此信息發送至中間服務器。之前,端口號僅出現在 CONNECT 行中。
    • 系統不再將隧道連接請求中的 user-agent 和 proxy-authorization 標頭髮送至代理服務器。

      在建立隧道時,系統不再將隧道 Http(s)URLConnection 中的 proxy-authorization 標頭髮送至代理。相反,由系統生成 proxy-authorization 標頭,在代理響應初始請求發送 HTTP 407 後將其發送至此代理。

      同樣地,系統不再將 user-agent 標頭由隧道連接請求複製到建立隧道的代理請求。相反,庫爲此請求生成 user-agent 標頭。

  • 如果之前執行的 connect() 函數失敗,send(java.net.DatagramPacket) 函數將會引發 SocketException。
    • 如果存在內部錯誤,DatagramSocket.connect() 會引發 pendingSocketException。對於 Android 8.0 之前的版本,即使 send() 調用成功,後續的 recv() 調用也會引發 SocketException。爲確保一致性,現在這兩個調用均會引發 SocketException。
  • 在回退到 TCP Echo 協議之前,InetAddress.isReachable() 會嘗試執行 ICMP。
    • 對於某些屏蔽端口 7 (TCP Echo) 的主機(例如 google.com),如果它們接受 ICMP Echo 協議,現在也許能夠訪問它們。
    • 對於確實無法訪問的主機,此項變更意味着調用需要兩倍的時間才能返回結果。

藍牙

Android 8.0 對 ScanRecord.getBytes() 函數檢索的數據長度做出以下變更:

  • getBytes() 函數對於所接收的字節數不作任何假定。因此,應用不應受所返回的任何最小或最大字節數的影響。相反,應用應當計算所返回數組的長度。
  • 兼容藍牙 5 的設備返回的數據長度可能會超出之前最大約 60 個字節的限制。
  • 如果遠程設備未提供掃描響應,則也可能返回少於 60 個字節的數據。

無縫連接

Android 8.0 對 WLAN 設置進行了多項改進,這樣可以更輕鬆地選擇能夠提供最佳用戶體驗的 WLAN 網絡。具體變更包括:

  • 穩定性和可靠性改進。
  • 更加直觀的界面。
  • 一個合併的 WLAN 首選項菜單。
  • 當附近存在優質的已保存網絡時在兼容設備上自動激活 WLAN。

安全性

Android 8.0 包含以下與安全性有關的變更:

  • 此平臺不再支持 SSLv3。
  • 在與未正確實現 TLS 協議版本協商的服務器建立 HTTPS 連接時,HttpsURLConnection 不再嘗試回退到之前的 TLS 協議版本並重試的權宜方法。
  • Android 8.0 將使用安全計算 (SECCOMP) 過濾器來過濾所有應用。允許的系統調用列表僅限於通過 bionic 公開的系統調用。此外,還提供了其他幾個後向兼容的系統調用,但我們不建議使用這些系統調用。
  • 現在,您的應用的 WebView 對象將在多進程模式下運行。網頁內容在獨立的進程中處理,此進程與包含應用的進程相隔離,以提高安全性。
  • 您無法再假定 APK 駐留在名稱以 -1 或 -2 結尾的目錄中。應用應使用 sourceDir 獲取此目錄,而不能直接使用目錄格式。
  • 如需瞭解與使用原生庫有關的安全性增強的信息,請參閱原生庫

有關提升應用安全性的其他準則,請參閱面向 Android 開發者的安全性

隱私性

Android 8.0 對平臺做出了以下與隱私性有關的變更。

  • 現在,平臺改變了標識符的處理方式。
    • 對於在 OTA 之前安裝到某個版本 Android 8.0(API 級別 26)的應用,除非在 OTA 後卸載並重新安裝,否則 ANDROID_ID 的值將保持不變。要在 OTA 後在卸載期間保留值,開發者可以使用密鑰/值備份關聯舊值和新值。
    • 對於安裝在運行 Android 8.0 的設備上的應用,ANDROID_ID 的值現在將根據應用簽署密鑰和用戶確定作用域。應用簽署密鑰、用戶和設備的每個組合都具有唯一的 ANDROID_ID 值。因此,在相同設備上運行但具有不同簽署密鑰的應用將不會再看到相同的 Android ID(即使對於同一用戶來說,也是如此)。
    • 只要簽署密鑰相同(並且應用未在 OTA 之前安裝到某個版本的 O),ANDROID_ID 的值在軟件包卸載或重新安裝時就不會發生變化。
    • 即使系統更新導致軟件包簽署密鑰發生變化,ANDROID_ID 的值也不會變化。

    要藉助一個簡單的標準系統實現應用獲利,請使用廣告 ID。廣告 ID 是 Google Play 服務針對廣告服務提供的唯一 ID,此 ID 可由用戶重置。

查詢 net.hostname 系統屬性返回的結果爲空。

記錄未捕獲的異常

如果某個應用安裝的 Thread.UncaughtExceptionHandler 未移交給默認的 Thread.UncaughtExceptionHandler,則當出現未捕獲的異常時,系統不會終止應用。從 Android 8.0 開始,在此情況下系統將記錄異常堆棧跟蹤情況;在之前的平臺版本中,系統不會記錄異常堆棧跟蹤情況。

我們建議,自定義 Thread.UncaughtExceptionHandler 實現始終移交給默認處理程序處理;遵循此建議的應用不受 Android 8.0 此項變更的影響。

聯繫人提供程序使用情況統計方法的變更

在之前版本的 Android 中,聯繫人提供程序組件允許開發者獲取每個聯繫人的使用情況數據。此使用情況數據揭示了與某個聯繫人相關聯的每個電子郵件地址和每個電話號碼的信息,包括與該聯繫人聯繫的次數以及上次聯繫該聯繫人的時間。請求 READ_CONTACTS 權限的應用可以讀取此數據。

如果應用請求 READ_CONTACTS 權限,它們仍可以讀取此數據。從 Android 8.0 開始,使用情況數據查詢會返回近似值,而不是精確值。不過,Android 系統內部仍然會保留精確值,因此,此變更不會影響 auto-complete API。

此行爲變更會影響以下查詢參數:

集合的處理

現在,AbstractCollection.removeAll()  AbstractCollection.retainAll() 始終引發 NullPointerException;之前,當集合爲空時不會引發 NullPointerException。此項變更使行爲符合文檔要求。

Android 企業版

Android 8.0 更改了企業應用(包括設備規範控制器 (DPC))的某些 API 和功能的行爲。這些變更包括:

  • 新增多種行爲,幫助應用支持完全託管設備中的工作資料。
  • 變更系統更新處理、應用驗證和身份驗證方式,以提高設備和系統的完整性。
  • 改進用戶在配置、通知、“最近使用的應用”屏幕和 Always on VPN 方面的體驗。

如需查看 Android 8.0 中的所有企業版變更和了解它們可能給您的應用帶來的影響,請閱讀企業中的 Android

針對 Android 8.0 的應用


這些行爲變更專門應用於針對 O 平臺或更高平臺版本的應用。針對 Android 8.0 或更高平臺版本進行編譯,或將 targetSdkVersion 設爲 Android 8.0 或更高版本的應用開發者必須修改其應用以正確支持這些行爲(如果適用)。

提醒窗口

使用 SYSTEM_ALERT_WINDOW 權限的應用無法再使用以下窗口類型來在其他應用和系統窗口上方顯示提醒窗口:

相反,應用必須使用名爲 TYPE_APPLICATION_OVERLAY 的新窗口類型。

使用 TYPE_APPLICATION_OVERLAY 窗口類型顯示應用的提醒窗口時,請記住新窗口類型的以下特性:

  • 應用的提醒窗口始終顯示在狀態欄和輸入法等關鍵系統窗口的下面。
  • 系統可以移動使用 TYPE_APPLICATION_OVERLAY 窗口類型的窗口或調整其大小,以改善屏幕顯示效果。
  • 通過打開通知欄,用戶可以訪問設置來阻止應用顯示使用 TYPE_APPLICATION_OVERLAY 窗口類型顯示的提醒窗口。

內容變更通知

Android 8.0 更改了 ContentResolver.notifyChange()  registerContentObserver(Uri, boolean, ContentObserver) 在針對 Android 8.0 的應用中的行爲方式。

現在,這些 API 需要在所有 URI 中爲頒發機構定義一個有效的 ContentProvider。使用相關權限定義一個有效的 ContentProvider 可幫助您的應用防範來自惡意應用的內容變更,並防止將可能的私密數據泄露給惡意應用。

視圖焦點

可點擊的 View 對象現在默認也可以成爲焦點。如果您希望 View 對象可點擊但不可成爲焦點,請在包含 View 的佈局 XML 文件中將 android:focusable 屬性設置爲 false,或者將 false 傳遞至應用界面邏輯中的 setFocusable()

安全性

如果您的應用的網絡安全性配置選擇退出對明文流量的支持,那麼您的應用的 WebView 對象無法通過 HTTP 訪問網站。每個 WebView 對象必須轉而使用 HTTPS。

有關提升應用安全性的其他準則,請參閱面向 Android 開發者的安全性

帳號訪問和可檢測性

除非身份驗證器擁有用戶帳號或用戶授予訪問權限,否則,應用將無法再訪問用戶帳號。僅擁有 GET_ACCOUNTS 權限尚不足以訪問用戶帳號。要獲得帳號訪問權限,應用應使用 AccountManager.newChooseAccountIntent() 或特定於身份驗證器的函數。獲得帳號訪問權限後,應用可以調用 AccountManager.getAccounts() 來訪問帳號。

Android 8.0 已棄用 LOGIN_ACCOUNTS_CHANGED_ACTION。相反,應用在運行時應使用 addOnAccountsUpdatedListener() 獲取帳號更新信息。

有關新增 API 和增加的帳號訪問和可檢測性函數的信息,請參閱此文檔的“新增 API”部分中的帳號訪問和可檢測性

隱私性

以下變更影響 Android 8.0 的隱私性。

  • 系統屬性 net.dns1net.dns2net.dns3  net.dns4 不再可用,此項變更可加強平臺的隱私性。
  • 要獲取 DNS 服務器之類的網絡連接信息,具有 ACCESS_NETWORK_STATE 權限的應用可以註冊 NetworkRequest  NetworkCallback 對象。這些類在 Android 5.0(API 級別 21)及更高版本中提供。
  • Build.SERIAL 已棄用。需要知道硬件序列號的應用應改爲使用新的 Build.getSerial() 函數,該函數要求具有 READ_PHONE_STATE 權限。
  • LauncherApps API 不再允許工作資料應用獲取有關主個人資料的信息。當某個用戶在託管配置文件中時,LauncherApps API 的行爲就像同一配置文件組的其他配置文件中未安裝任何應用一樣。和之前一樣,嘗試訪問無關聯的個人資料會引發 SecurityExceptions。

權限

在 Android 8.0 之前,如果應用在運行時請求權限並且被授予該權限,系統會錯誤地將屬於同一權限組並且在清單中註冊的其他權限也一起授予應用。

對於針對 Android 8.0 的應用,此行爲已被糾正。系統只會授予應用明確請求的權限。然而,一旦用戶爲應用授予某個權限,則所有後續對該權限組中權限的請求都將被自動批准。

例如,假設某個應用在其清單中列出 READ_EXTERNAL_STORAGE  WRITE_EXTERNAL_STORAGE。應用請求 READ_EXTERNAL_STORAGE,並且用戶授予了該權限。如果該應用針對的是 API 級別 24 或更低級別,系統還會同時授予 WRITE_EXTERNAL_STORAGE,因爲該權限也屬於同一 STORAGE 權限組並且也在清單中註冊過。如果該應用針對的是 Android 8.0,則系統此時僅會授予 READ_EXTERNAL_STORAGE;不過,如果該應用後來又請求 WRITE_EXTERNAL_STORAGE,則系統會立即授予該權限,而不會提示用戶。

媒體

  • 框架會執行音頻閃避。進行 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 時,應用不會失去焦點。新的 API 適用於需要暫停而不是閃避的應用。請注意,此行爲無法在 Android 8.0 1 版本中實現。
  • 當用戶打電話時,活動的媒體流將在通話期間靜音。
  • 所有與音頻相關的 API 都應使用 AudioAttributes 而不是音頻流類型來說明音頻播放用例。僅爲音量控制繼續使用音頻流類型。流類型(例如,已棄用的 AudioTrack constructor)的其他用途仍然有效,但是系統會將其記錄爲錯誤。
  • 使用 AudioTrack 時,如果應用請求了足夠大的音頻緩衝區,則框架將嘗試使用深度緩衝區輸出(如果可用)。
  • 在 Android 8.0 中,媒體按鈕事件的處理有所不同:
    1. 在界面操作組件中處理媒體按鈕未發生變化:前臺操作組件在處理媒體按鈕時仍然優先。
    2. 如果前臺操作組件不處理媒體按鈕,系統會將媒體按鈕路由到最近在本地播放音頻的應用。在確定哪些應用接收媒體按鈕事件時,不再考慮活動狀態、標誌和媒體會話的播放狀態。即使在應用調用 setActive(false) 後,媒體會話仍然可以接收媒體按鈕事件。
    3. 如果應用的媒體會話已經釋放,系統會將媒體按鈕事件發送到應用的 MediaButtonReceiver(如果有)。
    4. 對於任何其他情況,系統都會捨棄媒體按鈕事件。與其開始播放錯誤的應用,不如不播放任何東西。

    下圖彙總了新的媒體按鈕路由邏輯。

    (我也沒有圖啊。。請到官網查看 https://developer.android.com/about/versions/oreo/android-8.0-changes.html

原生庫

在針對 Android 8.0 的應用中,如果原生庫包含任何可寫且可執行的加載代碼段,則不會再加載原生庫。倘若某些應用的原生庫包含不正確的加載代碼段,則此變更可能會導致這些應用停止工作。這是一種安全加強措施。

如需瞭解詳細信息,請參閱可寫且可執行的代碼段

與早期的開發者預覽版相同,Android 8.0 還有助於更輕鬆地發現所有與鏈接器有關的問題。鏈接器的變更綁定到應用的目標 API 級別。如果應用的目標 API 級別發生鏈接器變更,則該應用無法加載該庫。如果您的目標 API 級別低於發生鏈接器變更的 API 級別,則 logcat 會顯示一條警告消息。在預覽版期間,與鏈接器有關的問題不僅會顯示在 logcat 中,也會以 toast 的形式顯示。對於特定的 API 級別,警告可能會變成錯誤,此變更有助於提前發現此類問題。

集合的處理

在 Android 8.0 中,Collections.sort() 是在 List.sort() 的基礎上實現的。在 Android 7.x(API 級別 24 和 25)中,則恰恰相反。在過去,List.sort() 的默認實現會調用 Collections.sort()

此項變更使 Collections.sort() 可以利用優化的 List.sort() 實現,但具有以下限制:

  • List.sort() 的實現不能調用 Collections.sort(),因爲這會導致堆棧因無限遞歸而溢出。相反,如果您需要 List 實現的默認行爲,應避免重寫 sort()

    如果父類以不適當的方法實現 sort() ,通常最好使用在 List.toArray()Arrays.sort()  ListIterator.set() 的基礎上構建的實現重寫 List.sort()。例如:

    @Override
    public void sort(Comparator<? super E> c) {
      Object[] elements = toArray();
      Arrays.sort(elements, c);
      ListIterator<E> iterator = (ListIterator<Object>) listIterator();
      for (Object element : elements) {
        iterator.next();
        iterator.set((E) element);
      }
    }

    在大多數情況下,您也可以使用根據 API 級別委託給其他默認實現的實現重寫 List.sort()。例如:

    @Override
    public void sort(Comparator<? super E> comparator) {
      if (Build.VERSION.SDK_INT <= 25) {
        Collections.sort(this);
      } else {
        super.sort(comparator);
      }
    }

    如果您選擇後者只是因爲您希望開發一種適用於所有 API 級別的 sort() 函數,可以考慮賦予其一個唯一的名稱,例如 sortCompat(),而不是重寫 sort()

  • 現在,Collections.sort() 只是對調用 sort() 的 List 實現進行的一項結構性修改。例如,在 Android 8.0 之前的平臺版本中,如果通過調用 List.sort() 進行排序,則當迭代處理 ArrayList 以及在迭代過程中調用 sort() 時,會引發 ConcurrentModificationException。而 Collections.sort() 則不會引發異常。

    此項變更使平臺行爲更加一致:現在,兩種方法都會引發 ConcurrentModificationException

類加載行爲

Android 8.0 檢查確保類加載器在加載新類時不會違反運行時假設條件。不論類引用自 Java(來自 forName())、Dalvik 字節碼還是 JNI,都會執行這些檢查。平臺不會攔截 Java 對 loadClass() 函數的直接調用,也不會檢查此類調用的結果。此行爲不應影響運行良好的類加載器的正常運行。

平臺將檢查類加載器返回的類描述符是否與預期的描述符一致。如果返回的描述符與預期不符,平臺會引發 NoClassDefFoundError 錯誤,並在異常日誌中存儲一條註明不一致之處的詳細錯誤消息。

平臺還檢查請求的類描述符是否有效。此檢查捕獲間接加載諸如 GetFieldID() 等類的 JNI 調用,向這些類傳遞無效的描述符。例如,找不到包含 java/lang/String 簽名的字段,是因爲此簽名無效;它應爲 Ljava/lang/String;

這與 JNI 對 FindClass() 的調用不同,其中 java/lang/String 是一個有效的完全限定名稱。

Android 8.0 不支持多個類加載器同時嘗試使用相同的 DexFile 對象來定義類。嘗試進行此操作,會導致 Android 運行時引發 InternalError 錯誤,同時顯示消息“Attempt to register dex file <filename> with multiple class loaders”。

DexFile API 現已棄用,強烈建議您改爲使用此平臺的類加載器之一,包括 PathClassLoader  BaseDexClassLoader

注: 您可以創建多個引用文件系統中同一個 APK 或 JAR 文件容器的類加載器。這樣做通常不會佔用大量內存:如果存儲而不壓縮容器中的 DEX 文件,平臺可以對此類文件執行 mmap 操作,而不直接提取它們。但是,如果平臺必須從容器中提取 DEX 文件,以這種方式引用 DEX 文件可能佔用大量內存。

在 Android 中,所有類加載器都被視爲支持並行運行。當多個線程爭用同一個類加載器加載相同的類時,第一個完成此操作的線程勝出,而操作結果將用於其他線程。無論類加載器是返回同一個類、返回不同的類還是引發異常,都將發生此行爲。該平臺靜默忽略此類異常。

注意: 在低於 Android 8.0 的平臺版本中,違反這些假設條件可能導致多次定義同一個類、由於類混淆造成堆損壞和其他不良影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章