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
-
getSaveFormData()
函數現在返回false
。之前,此函數返回true
。- 調用
setSaveFormData()
不再有任何效果。
-
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 可由用戶重置。
- 對於在 OTA 之前安裝到某個版本 Android 8.0(API 級別 26)的應用,除非在 OTA 后卸載並重新安裝,否則
- 查詢
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.dns1
、net.dns2
、net.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 中,媒體按鈕事件的處理有所不同:
- 在界面操作組件中處理媒體按鈕未發生變化:前台操作組件在處理媒體按鈕時仍然優先。
- 如果前台操作組件不處理媒體按鈕,系統會將媒體按鈕路由到最近在本地播放音頻的應用。在確定哪些應用接收媒體按鈕事件時,不再考慮活動狀態、標志和媒體會話的播放狀態。即使在應用調用 setActive(false) 后,媒體會話仍然可以接收媒體按鈕事件。
- 如果應用的媒體會話已經釋放,系統會將媒體按鈕事件發送到應用的 MediaButtonReceiver(如果有)。
- 對於任何其他情況,系統都會舍棄媒體按鈕事件。與其開始播放錯誤的應用,不如不播放任何東西。
下圖匯總了新的媒體按鈕路由邏輯。
原生庫
在針對 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 中,所有類加載器都被視為支持並行運行。當多個線程爭用同一個類加載器加載相同的類時,第一個完成此操作的線程勝出,而操作結果將用於其他線程。無論類加載器是返回同一個類、返回不同的類還是引發異常,都將發生此行為。該平台靜默忽略此類異常。