Android O 除了提供諸多新特性和功能外,,還對系統(tǒng)和 API 行為做出了各種變更,。本文重點(diǎn)介紹您應(yīng)該了解并在開發(fā)應(yīng)用時(shí)加以考慮的一些主要變更。
其中大部分變更會(huì)影響所有應(yīng)用,,而不論應(yīng)用針對的是何種版本的 Android,。不過,有幾項(xiàng)變更僅影響針對 Android O 的應(yīng)用,。為清楚起見,,本頁面分為兩個(gè)部分:針對所有 API 級別的應(yīng)用和針對 Android O 的應(yīng)用。
這些行為變更適用于在 Android O 平臺(tái)上運(yùn)行的所有應(yīng)用,,無論這些應(yīng)用是針對哪個(gè) API 級別構(gòu)建,。所有開發(fā)者都應(yīng)查看這些變更,并修改其應(yīng)用以正確支持這些變更(如果適用)。
網(wǎng)絡(luò)連接和 HTTP(S) 連接 Android O 對網(wǎng)絡(luò)連接和 HTTP(S) 連接行為做出了以下變更:
無正文的 OPTIONS 請求具有 Content-Length: 0 標(biāo)頭,。之前,,這些請求沒有 Content-Length 標(biāo)頭。 HttpURLConnection 在包含斜線的主機(jī)或頒發(fā)機(jī)構(gòu)名稱后面附加一條斜線,,使包含空路徑的網(wǎng)址規(guī)范化,。例如,它將 http:// 轉(zhuǎn)化為 http:/// ,。 通過 ProxySelector.setDefault ( ) 設(shè)置的自定義代理選擇器僅針對所請求的網(wǎng)址(架構(gòu),、主機(jī)和端口)。因此,,僅可根據(jù)這些值選擇代理,。傳遞至自定義代理選擇器的網(wǎng)址不包含所請求的網(wǎng)址的路徑、查詢參數(shù)或片段,。 URI 不能包含空白標(biāo)簽,。 之前,平臺(tái)支持一種權(quán)宜方法,,即允許主機(jī)名稱中包含空白標(biāo)簽,,但這是對 URI 的非法使用。此權(quán)宜方法只是為了確保與舊版 libcore 兼容,。開發(fā)者如果對 API 使用不當(dāng),,將會(huì)看到一條 ADB 消息:“URI example..com 的主機(jī)名包含空白標(biāo)簽。此格式不正確,,將不被未來的 Android 版本所接受,。”Android O 廢除了此權(quán)宜方法,;系統(tǒng)對格式錯(cuò)誤的 URI 會(huì)返回 null,。 Android O 在實(shí)現(xiàn) HttpsURLConnection 時(shí)不會(huì)執(zhí)行不安全的 TLS/SSL 協(xié)議版本回退。 對隧道 HTTP(S) 連接處理進(jìn)行了如下變更: 在通過連接建立隧道 HTTP(S) 連接時(shí),,系統(tǒng)會(huì)在 Host 行中正確放置端口號 (:443) 并將此信息發(fā)送至中間服務(wù)器,。之前,端口號僅出現(xiàn)在 CONNECT 行中 系統(tǒng)不再將隧道連接請求中的 user-agent 和 proxy-authorization 標(biāo)頭發(fā)送至代理服務(wù)器,。 在建立隧道時(shí),,系統(tǒng)不再將隧道 Http(s)URLConnection 中的 proxy-authorization 標(biāo)頭發(fā)送至代理,。相反,,由系統(tǒng)生成 proxy-authorization 標(biāo)頭,在代理響應(yīng)初始請求發(fā)送 HTTP 407 后將其發(fā)送至此代理,。 同樣地,,系統(tǒng)不再將 user-agent 標(biāo)頭由隧道連接請求復(fù)制到建立隧道的代理請求。相反,庫為此請求生成 user-agent 標(biāo)頭,。
如果之前執(zhí)行的 connect ( ) 函數(shù)失敗,,send( java.net.DatagramPacket ) 函數(shù)將會(huì)引發(fā) SocketException: 如果存在內(nèi)部錯(cuò)誤,DatagramSocket.connect ( ) 會(huì)引發(fā) pendingSocketException,。對于 Android O 之前的版本,,即使 send ( ) 調(diào)用成功,后續(xù)的 recv ( ) 調(diào)用也會(huì)引發(fā) SocketException,。為確保一致性,,現(xiàn)在這兩個(gè)調(diào)用均會(huì)引發(fā) SocketException。
在回退到 TCP Echo 協(xié)議之前,,InetAddress.isReachable ( ) 會(huì)嘗試執(zhí)行 ICMP: 對于某些屏蔽端口 7 (TCP Echo) 的主機(jī)(例如 google.com),,如果它們接受 ICMP Echo 協(xié)議,現(xiàn)在也許能夠訪問它們,。 對于確實(shí)無法訪問的主機(jī),,此項(xiàng)變更意味著調(diào)用需要兩倍的時(shí)間才能返回結(jié)果。
集合的處理 現(xiàn)在,,AbstractCollection.removeAll ( ) 和 AbstractCollection.retainAll ( ) 始終引發(fā) NullPointerException,;之前,當(dāng)集合為空時(shí)不會(huì)引發(fā) NullPointerException,。此項(xiàng)變更使行為符合文檔要求,。
記錄未捕獲的異常 如果某個(gè)應(yīng)用安裝的 Thread.UncaughtExceptionHandler 未移交給默認(rèn)的 Thread.UncaughtExceptionHandler,則當(dāng)出現(xiàn)未捕獲的異常時(shí),,系統(tǒng)不會(huì)終止應(yīng)用,。從 Android O 開始,在此情況下系統(tǒng)將記錄異常堆棧跟蹤情況,;在之前的平臺(tái)版本中,,系統(tǒng)不會(huì)記錄異常堆棧跟蹤情況。
我們建議,,自定義 Thread.UncaughtExceptionHandler 實(shí)現(xiàn)始終移交給默認(rèn)處理程序處理,;遵循此建議的應(yīng)用不受 Android O 此項(xiàng)變更的影響。
輸入和導(dǎo)航 隨著 Android 應(yīng)用出現(xiàn)在 Chrome 操作系統(tǒng)和平板電腦等其他大尺寸設(shè)備上,,我們看到,,用戶在 Android 應(yīng)用中又重新開始使用鍵盤導(dǎo)航。在 Android O 中,,我們又再次使用鍵盤作為導(dǎo)航輸入設(shè)備,,從而為基于箭頭鍵和 Tab 鍵的導(dǎo)航構(gòu)建了一種更可靠并且可預(yù)測的模型。
尤其要指出的是,,我們對元素焦點(diǎn)行為做出以下變更: 現(xiàn)在,,如果您沒有為 View 對象(前景或背景圖片)定義任何焦點(diǎn)狀態(tài)顏色,,框架會(huì)為 View 設(shè)置默認(rèn)的焦點(diǎn)突出顯示顏色。此焦點(diǎn)突出顯示標(biāo)志是基于操作組件主題背景的漣漪圖片,。 如果您不希望 View 對象在接收焦點(diǎn)時(shí)使用此默認(rèn)突出顯示標(biāo)志,,請?jiān)诎?View 的布局 XML 文件中將 android:defaultFocusHighlightEnabled 屬性設(shè)置為 false ,或者將 false 傳遞至應(yīng)用界面邏輯中的 setDefaultFocusHighlightEnabled ( ) ,。 要測試鍵盤輸入對界面元素焦點(diǎn)有何影響,,您可以啟用 Drawing > Show layout bounds 開發(fā)者選項(xiàng)。在 Android O 中,,此選項(xiàng)在當(dāng)前具有焦點(diǎn)的元素上顯示一個(gè) “X” 圖標(biāo),。
另外,Android O 中的所有工具欄元素自動(dòng)組成鍵盤導(dǎo)航鍵區(qū),,用戶可以更加輕松地導(dǎo)航進(jìn)入和離開每個(gè)作為一個(gè)整體的工具欄,。
如需詳細(xì)了解如何在您的應(yīng)用中改善對鍵盤導(dǎo)航的支持,請閱讀以下鏈接中的支持鍵盤導(dǎo)航指南,。 (https://developer.android.google.cn/training/keyboard-input/navigation.html)
安全性 Android O 包含以下與安全性有關(guān)的變更:
此平臺(tái)不再支持 SSLv3 Android O 將使用安全計(jì)算 (SECCOMP) 過濾器來過濾所有應(yīng)用,。允許的系統(tǒng)調(diào)用列表僅限于通過 bionic 公開的系統(tǒng)調(diào)用。此外,,還提供了其他幾個(gè)后向兼容的系統(tǒng)調(diào)用,,但我們不建議使用這些系統(tǒng)調(diào)用。
在與未正確實(shí)現(xiàn) TLS 協(xié)議版本協(xié)商的服務(wù)器建立 HTTPS 連接時(shí),,HttpsURLConnection 不再嘗試回退到之前的 TLS 協(xié)議版本并重試的權(quán)宜方法,。 現(xiàn)在,您的應(yīng)用的 WebView 對象將在多進(jìn)程模式下運(yùn)行,。網(wǎng)頁內(nèi)容在獨(dú)立的進(jìn)程中處理,,此進(jìn)程與包含應(yīng)用的進(jìn)程相隔離,以提高安全性,。 您無法再假定 APK 駐留在名稱以 -1 或 -2 結(jié)尾的目錄中,。應(yīng)用應(yīng)使用 sourceDir 獲取此目錄,而不能直接使用目錄格式,。
有關(guān)提升應(yīng)用安全性的其他準(zhǔn)則,,請參閱以下鏈接中的面向 Android 開發(fā)者的安全性。 (https://developer.android.google.cn/topic/security/index.html)
后臺(tái)執(zhí)行限制 Android O 為提高電池續(xù)航時(shí)間而引入的變更之一是,,當(dāng)您的應(yīng)用進(jìn)入已緩存狀態(tài)時(shí),,如果沒有活動(dòng)的組件,系統(tǒng)將解除應(yīng)用具有的所有喚醒鎖,。
此外,,為提高設(shè)備性能,系統(tǒng)會(huì)限制未在前臺(tái)運(yùn)行的應(yīng)用的某些行為,。具體而言: 現(xiàn)在,,在后臺(tái)運(yùn)行的應(yīng)用對后臺(tái)服務(wù)的訪問受到限制。 應(yīng)用無法使用其清單注冊大部分隱式廣播(即,,并非專門針對此應(yīng)用的廣播),。
Android O 還對特定函數(shù)做出了以下變更: 如果針對 Android O 的應(yīng)用嘗試在不允許其創(chuàng)建后臺(tái)服務(wù)的情況下使用 startService ( ) 函數(shù),則該函數(shù)將引發(fā)一個(gè) IllegalStateException,。 新的 Context.startForegroundService ( ) 函數(shù)將啟動(dòng)一個(gè)前臺(tái)服務(wù)?,F(xiàn)在,即使應(yīng)用在后臺(tái)運(yùn)行,,系統(tǒng)也允許其調(diào)用 Context.startForegroundService ( ) ,。不過,應(yīng)用必須在創(chuàng)建服務(wù)后的五秒內(nèi)調(diào)用該服務(wù)的 startForeground ( ) 函數(shù),。
如需了解詳細(xì)信息,,請參閱以下鏈接中的后臺(tái)執(zhí)行限制。 (https://developer.android.google.cn/preview/features/background.html)
隱私性 Android O 對平臺(tái)做出了以下與隱私性有關(guān)的變更:
現(xiàn)在,,平臺(tái)改變了標(biāo)識符的處理方式: 對于在 OTA 之前安裝到某個(gè)版本 Android O(API 級別 26)的應(yīng)用,,除非在 OTA 后卸載并重新安裝,否則 ANDROID_ID 的值將保持不變,。要在 OTA 后在卸載期間保留值,,開發(fā)者可以使用密鑰/值備份關(guān)聯(lián)舊值和新值。 對于安裝在運(yùn)行 Android O 的設(shè)備上的應(yīng)用,,ANDROID_ID 的值現(xiàn)在將根據(jù)應(yīng)用簽署密鑰和用戶確定作用域,。應(yīng)用簽署密鑰、用戶和設(shè)備的每個(gè)組合都具有唯一的 ANDROID_ID 值,。因此,,在相同設(shè)備上運(yùn)行但具有不同簽署密鑰的應(yīng)用將不會(huì)再看到相同的 Android ID(即使對于同一用戶來說,也是如此),。 只要簽署密鑰相同(并且應(yīng)用未在 OTA 之前安裝到某個(gè)版本的 O),,ANDROID_ID 的值在軟件包卸載或重新安裝時(shí)就不會(huì)發(fā)生變化。 即使系統(tǒng)更新導(dǎo)致軟件包簽署密鑰發(fā)生變化,,ANDROID_ID 的值也不會(huì)變化,。 要借助一個(gè)簡單的標(biāo)準(zhǔn)系統(tǒng)實(shí)現(xiàn)應(yīng)用獲利,請使用廣告 ID,。廣告 ID 是 Google Play 服務(wù)針對廣告服務(wù)提供的唯一 ID,,此 ID 可由用戶重置。
查詢 net.hostname 系統(tǒng)屬性返回的結(jié)果為空,。
這些行為變更專門應(yīng)用于針對 O 平臺(tái)或更高平臺(tái)版本的應(yīng)用,。針對 Android O 或更高平臺(tái)版本進(jìn)行編譯,或?qū)?targetSdkVersion 設(shè)為 Android O 或更高版本的應(yīng)用開發(fā)者必須修改其應(yīng)用以正確支持這些行為(如果適用),。
內(nèi)容變更通知 Android O 更改了 ContentResolver.notifyChange ( ) 和 registerContentObserver ( Uri, boolean, ContentObserver ) 在針對 Android O 的應(yīng)用中的行為方式,。 現(xiàn)在,,這些 API 需要在所有 URI 中為頒發(fā)機(jī)構(gòu)定義一個(gè)有效的 ContentProvider。使用相關(guān)權(quán)限定義一個(gè)有效的 ContentProvider 可幫助您的應(yīng)用防范來自惡意應(yīng)用的內(nèi)容變更,,并防止將可能的私密數(shù)據(jù)泄露給惡意應(yīng)用,。
視圖焦點(diǎn) 可點(diǎn)擊的 View 對象現(xiàn)在默認(rèn)也可以成為焦點(diǎn)。如果您希望 View 對象可點(diǎn)擊但不可成為焦點(diǎn),,請?jiān)诎?View 的布局 XML 文件中將 android:focusable 屬性設(shè)置為 false,,或者將 false 傳遞至應(yīng)用界面邏輯中的 setFocusable ( ) 。
權(quán)限 在 Android O 之前,,如果應(yīng)用在運(yùn)行時(shí)請求權(quán)限并且被授予該權(quán)限,,系統(tǒng)會(huì)錯(cuò)誤地將屬于同一權(quán)限組并且在清單中注冊的其他權(quán)限也一起授予應(yīng)用。
對于針對 Android O 的應(yīng)用,,此行為已被糾正,。系統(tǒng)只會(huì)授予應(yīng)用明確請求的權(quán)限。然而,,一旦用戶為應(yīng)用授予某個(gè)權(quán)限,,則所有后續(xù)對該權(quán)限組中權(quán)限的請求都將被自動(dòng)批準(zhǔn)。
例如: 假設(shè)某個(gè)應(yīng)用在其清單中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE ,。應(yīng)用請求 READ_EXTERNAL_STORAGE ,,并且用戶授予了該權(quán)限。
如果該應(yīng)用針對的是 API 級別 24 或更低級別,,系統(tǒng)還會(huì)同時(shí)授予 WRITE_EXTERNAL_STORAGE ,,因?yàn)樵摍?quán)限也屬于同一 STORAGE 權(quán)限組并且也在清單中注冊過。
如果該應(yīng)用針對的是 Android O,,則系統(tǒng)此時(shí)僅會(huì)授予 READ_EXTERNAL_STORAGE ,;不過,如果該應(yīng)用后來又請求 WRITE_EXTERNAL_STORAGE ,,則系統(tǒng)會(huì)立即授予該權(quán)限,,而不會(huì)提示用戶。
集合的處理
在 Android O 中,,Collections.sort ( ) 是在 List.sort ( ) 的基礎(chǔ)上實(shí)現(xiàn)的,。在 Android 7.x(API 級別 24 和 25)中,則恰恰相反,。在過去,,List.sort ( ) 的默認(rèn)實(shí)現(xiàn)會(huì)調(diào)用 Collections.sort ( ) 。
此項(xiàng)變更使 Collections.sort ( ) 可以利用優(yōu)化的 List.sort ( ) 實(shí)現(xiàn),,但具有以下限制: List.sort ( ) 的實(shí)現(xiàn)不能調(diào)用 Collections.sort ( ),,因?yàn)檫@會(huì)導(dǎo)致堆棧因無限遞歸而溢出。相反,,如果您需要 List 實(shí)現(xiàn)的默認(rèn)行為,,應(yīng)避免重寫 sort(),。 如果父類以不適當(dāng)?shù)姆椒▽?shí)現(xiàn) sort ( ) ,通常最好使用在 List.toArray ( ),、Arrays.sort ( ) 和 ListIterator.set ( ) 的基礎(chǔ)上構(gòu)建的實(shí)現(xiàn)重寫 List.sort ( ) ,。
例如: @Override public void sort(Comparator<> super E> c) { Object[] elements = toArray(); Arrays.sort(elements, c); ListIteratorE> iterator = (ListIteratorObject>) listIterator(); for (Object element : elements) { iterator.next(); iterator.set((E) element); } }
在大多數(shù)情況下,您也可以使用根據(jù) API 級別委托給其他默認(rèn)實(shí)現(xiàn)的實(shí)現(xiàn)重寫 List.sort ( ) 例如: @Override public void sort(Comparator<> super E> comparator) { if (Build.VERSION.SDK_INT <> 25) { Collections.sort(this); } else { super.sort(comparator); } }
如果您選擇后者只是因?yàn)槟M_發(fā)一種適用于所有 API 級別的 sort ( ) 函數(shù),,可以考慮賦予其一個(gè)唯一的名稱,例如 sortCompat ( ),,而不是重寫 sort ( ) ,。
現(xiàn)在,Collections.sort ( ) 只是對調(diào)用 sort ( ) 的 List 實(shí)現(xiàn)進(jìn)行的一項(xiàng)結(jié)構(gòu)性修改,。例如,,在 Android O 之前的平臺(tái)版本中,如果通過調(diào)用 List.sort ( ) 進(jìn)行排序,,則當(dāng)?shù)幚?span> ArrayList 以及在迭代過程中調(diào)用 sort ( ) 時(shí),,會(huì)引發(fā) ConcurrentModificationException。而 Collections.sort ( ) 則不會(huì)引發(fā)異常,。
此項(xiàng)變更使平臺(tái)行為更加一致:現(xiàn)在,,兩種方法都會(huì)引發(fā) ConcurrentModificationException 。
媒體 框架會(huì)執(zhí)行音頻閃避,。進(jìn)行 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 時(shí),,應(yīng)用不會(huì)失去焦點(diǎn)。新的 API 適用于需要暫停而不是閃避的應(yīng)用,。請注意,,此行為無法在 Android O Developer Preview 1 版本中實(shí)現(xiàn)。 當(dāng)用戶打電話時(shí),,活動(dòng)的媒體流將在通話期間靜音,。 所有與音頻相關(guān)的 API 都應(yīng)使用 AudioAttributes 而不是音頻流類型來說明音頻播放用例。僅為音量控制繼續(xù)使用音頻流類型,。流類型(例如,,已棄用的 AudioTrack constructor)的其他用途仍然有效,但是系統(tǒng)會(huì)將其記錄為錯(cuò)誤,。 使用 AudioTrack 時(shí),,如果應(yīng)用請求了足夠大的音頻緩沖區(qū),則框架將嘗試使用深度緩沖區(qū)輸出(如果可用),。 在 Android O 中,,媒體按鈕事件的處理有所不同: 在界面操作組件中處理媒體按鈕未發(fā)生變化:前臺(tái)操作組件在處理媒體按鈕時(shí)仍然優(yōu)先。 如果前臺(tái)操作組件不處理媒體按鈕,,系統(tǒng)會(huì)將媒體按鈕路由到最近在本地播放音頻的應(yīng)用,。在確定哪些應(yīng)用接收媒體按鈕事件時(shí),,不再考慮活動(dòng)狀態(tài)、標(biāo)志和媒體會(huì)話的播放狀態(tài),。即使在應(yīng)用調(diào)用 setActive( false ) 后,,媒體會(huì)話仍然可以接收媒體按鈕事件。 如果應(yīng)用的媒體會(huì)話已經(jīng)釋放,,系統(tǒng)會(huì)將媒體按鈕事件發(fā)送到應(yīng)用的 MediaButtonReceiver(如果有),。 對于任何其他情況,系統(tǒng)都會(huì)舍棄媒體按鈕事件,。與其開始播放錯(cuò)誤的應(yīng)用,,不如不播放任何東西。
下圖匯總了新的媒體按鈕路由邏輯:
類加載行為
Android O 檢查確保類加載器在加載新類時(shí)不會(huì)違反運(yùn)行時(shí)假設(shè)條件,。不論類引用自 Java(來自 forName ( ) ),、Dalvik 字節(jié)碼還是 JNI,都會(huì)執(zhí)行這些檢查,。平臺(tái)不會(huì)攔截 Java 對 loadClass ( ) 函數(shù)的直接調(diào)用,,也不會(huì)檢查此類調(diào)用的結(jié)果。此行為不應(yīng)影響運(yùn)行良好的類加載器的正常運(yùn)行,。
平臺(tái)將檢查類加載器返回的類描述符是否與預(yù)期的描述符一致,。如果返回的描述符與預(yù)期不符,平臺(tái)會(huì)引發(fā) NoClassDefFoundError 錯(cuò)誤,,并在異常日志中存儲(chǔ)一條注明不一致之處的詳細(xì)錯(cuò)誤消息,。
平臺(tái)還檢查請求的類描述符是否有效。此檢查捕獲間接加載諸如 GetFieldID ( ) 等類的 JNI 調(diào)用,,向這些類傳遞無效的描述符,。例如,找不到包含 java/lang/String 簽名的字段,,是因?yàn)榇撕灻麩o效,;它應(yīng)為 Ljava/lang/String; 。
這與 JNI 對 FindClass ( ) 的調(diào)用不同,,其中 java/lang/String 是一個(gè)有效的完全限定名稱,。
Android O 不支持多個(gè)類加載器同時(shí)嘗試使用相同的 DexFile 對象來定義類。嘗試進(jìn)行此操作,,會(huì)導(dǎo)致 Android 運(yùn)行時(shí)引發(fā) InternalError 錯(cuò)誤,,同時(shí)顯示消息 “Attempt to register dex file with multiple class loaders” 。
DexFile API 現(xiàn)已棄用,,強(qiáng)烈建議您改為使用此平臺(tái)的類加載器之一,,包括 PathClassLoader 或 BaseDexClassLoader。
注: 您可以創(chuàng)建多個(gè)引用文件系統(tǒng)中同一個(gè) APK 或 JAR 文件容器的類加載器。這樣做通常不會(huì)占用大量內(nèi)存:如果存儲(chǔ)而不壓縮容器中的 DEX 文件,,平臺(tái)可以對此類文件執(zhí)行 mmap 操作,,而不直接提取它們。但是,,如果平臺(tái)必須從容器中提取 DEX 文件,,以這種方式引用 DEX 文件可能占用大量內(nèi)存。
在 Android 中,,所有類加載器都被視為支持并行運(yùn)行,。當(dāng)多個(gè)線程爭用同一個(gè)類加載器加載相同的類時(shí),第一個(gè)完成此操作的線程勝出,,而操作結(jié)果將用于其他線程,。無論類加載器是返回同一個(gè)類、返回不同的類還是引發(fā)異常,,都將發(fā)生此行為,。該平臺(tái)靜默忽略此類異常,。
|