Android O 行為變更指南

Android O 除了提供諸多新特性和功能外,還對系統(tǒng)和 API 行為做出了各種變更。本文重點(diǎn)介紹您應(yīng)該了解并在開發(fā)應(yīng)用時(shí)加以考慮的一些主要變更。

其中大部分變更會影響所有應(yīng)用,而不論應(yīng)用針對的是何種版本的 Android。不過,有幾項(xiàng)變更僅影響針對 Android O 的應(yīng)用。為清楚起見,本頁面分為兩個(gè)部分:針對所有 API 級別的應(yīng)用和針對 Android O 的應(yīng)用。

針對所有 API 級別的應(yīng)用

這些行為變更適用于在 Android O 平臺上運(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://example.com轉(zhuǎn)化為http://example.com/。

通過 ProxySelector.setDefault ( ) 設(shè)置的自定義代理選擇器僅針對所請求的網(wǎng)址(架構(gòu)、主機(jī)和端口)。因此,僅可根據(jù)這些值選擇代理。傳遞至自定義代理選擇器的網(wǎng)址不包含所請求的網(wǎng)址的路徑、查詢參數(shù)或片段。

URI 不能包含空白標(biāo)簽。

之前,平臺支持一種權(quán)宜方法,即允許主機(jī)名稱中包含空白標(biāo)簽,但這是對 URI 的非法使用。此權(quán)宜方法只是為了確保與舊版 libcore 兼容。開發(fā)者如果對 API 使用不當(dāng),將會看到一條 ADB 消息:“URI example..com 的主機(jī)名包含空白標(biāo)簽。此格式不正確,將不被未來的 Android 版本所接受。”Android O 廢除了此權(quán)宜方法;系統(tǒng)對格式錯(cuò)誤的 URI 會返回 null。

Android O 在實(shí)現(xiàn) HttpsURLConnection 時(shí)不會執(zhí)行不安全的 TLS/SSL 協(xié)議版本回退。

對隧道 HTTP(S) 連接處理進(jìn)行了如下變更:

在通過連接建立隧道 HTTP(S) 連接時(shí),系統(tǒng)會在 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ù)將會引發(fā) SocketException:

如果存在內(nèi)部錯(cuò)誤,DatagramSocket.connect ( ) 會引發(fā) pendingSocketException。對于 Android O 之前的版本,即使 send ( ) 調(diào)用成功,后續(xù)的 recv ( ) 調(diào)用也會引發(fā) SocketException。為確保一致性,現(xiàn)在這兩個(gè)調(diào)用均會引發(fā) SocketException。

在回退到 TCP Echo 協(xié)議之前,InetAddress.isReachable ( ) 會嘗試執(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í)不會引發(fā)NullPointerException。此項(xiàng)變更使行為符合文檔要求。

記錄未捕獲的異常

如果某個(gè)應(yīng)用安裝的Thread.UncaughtExceptionHandler未移交給默認(rèn)的Thread.UncaughtExceptionHandler,則當(dāng)出現(xiàn)未捕獲的異常時(shí),系統(tǒng)不會終止應(yīng)用。從 Android O 開始,在此情況下系統(tǒng)將記錄異常堆棧跟蹤情況;在之前的平臺版本中,系統(tǒng)不會記錄異常堆棧跟蹤情況。

我們建議,自定義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)顏色,框架會為View設(shè)置默認(rèn)的焦點(diǎn)突出顯示顏色。此焦點(diǎn)突出顯示標(biāo)志是基于操作組件主題背景的漣漪圖片。

如果您不希望View對象在接收焦點(diǎn)時(shí)使用此默認(rèn)突出顯示標(biāo)志,請?jiān)诎琕iew的布局 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ǎ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)的變更:

此平臺不再支持 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)

后臺執(zhí)行限制

Android O 為提高電池續(xù)航時(shí)間而引入的變更之一是,當(dāng)您的應(yīng)用進(jìn)入已緩存狀態(tài)時(shí),如果沒有活動的組件,系統(tǒng)將解除應(yīng)用具有的所有喚醒鎖。

此外,為提高設(shè)備性能,系統(tǒng)會限制未在前臺運(yùn)行的應(yīng)用的某些行為。具體而言:

現(xiàn)在,在后臺運(yùn)行的應(yīng)用對后臺服務(wù)的訪問受到限制。

應(yīng)用無法使用其清單注冊大部分隱式廣播(即,并非專門針對此應(yīng)用的廣播)。

Android O 還對特定函數(shù)做出了以下變更:

如果針對 Android O 的應(yīng)用嘗試在不允許其創(chuàng)建后臺服務(wù)的情況下使用startService ( )函數(shù),則該函數(shù)將引發(fā)一個(gè)IllegalStateException。

新的Context.startForegroundService ( )函數(shù)將啟動一個(gè)前臺服務(wù)?,F(xiàn)在,即使應(yīng)用在后臺運(yùn)行,系統(tǒng)也允許其調(diào)用Context.startForegroundService ( )。不過,應(yīng)用必須在創(chuàng)建服務(wù)后的五秒內(nèi)調(diào)用該服務(wù)的startForeground ( )函數(shù)。

如需了解詳細(xì)信息,請參閱以下鏈接中的后臺執(zhí)行限制。

(https://developer.android.google.cn/preview/features/background.html)

隱私性

Android O 對平臺做出了以下與隱私性有關(guān)的變更:

現(xiàn)在,平臺改變了標(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)用將不會再看到相同的 Android ID(即使對于同一用戶來說,也是如此)。

只要簽署密鑰相同(并且應(yīng)用未在 OTA 之前安裝到某個(gè)版本的 O),ANDROID_ID的值在軟件包卸載或重新安裝時(shí)就不會發(fā)生變化。

即使系統(tǒng)更新導(dǎo)致軟件包簽署密鑰發(fā)生變化,ANDROID_ID的值也不會變化。

要借助一個(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é)果為空。

針對 Android O 的應(yīng)用

這些行為變更專門應(yīng)用于針對 O 平臺或更高平臺版本的應(yīng)用。針對 Android O 或更高平臺版本進(jìn)行編譯,或?qū)argetSdkVersion設(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)诎琕iew的布局 XML 文件中將android:focusable屬性設(shè)置為false,或者將false傳遞至應(yīng)用界面邏輯中的setFocusable ( )。

權(quán)限

在 Android O 之前,如果應(yīng)用在運(yùn)行時(shí)請求權(quán)限并且被授予該權(quán)限,系統(tǒng)會錯(cuò)誤地將屬于同一權(quán)限組并且在清單中注冊的其他權(quán)限也一起授予應(yīng)用。

對于針對 Android O 的應(yīng)用,此行為已被糾正。系統(tǒng)只會授予應(yīng)用明確請求的權(quán)限。然而,一旦用戶為應(yīng)用授予某個(gè)權(quán)限,則所有后續(xù)對該權(quán)限組中權(quán)限的請求都將被自動批準(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)還會同時(shí)授予WRITE_EXTERNAL_STORAGE,因?yàn)樵摍?quán)限也屬于同一STORAGE權(quán)限組并且也在清單中注冊過。

如果該應(yīng)用針對的是 Android O,則系統(tǒng)此時(shí)僅會授予READ_EXTERNAL_STORAGE;不過,如果該應(yīng)用后來又請求WRITE_EXTERNAL_STORAGE,則系統(tǒng)會立即授予該權(quán)限,而不會提示用戶。

集合的處理

在 Android O 中,Collections.sort ( )是在List.sort ( )的基礎(chǔ)上實(shí)現(xiàn)的。在 Android 7.x(API 級別 24 和 25)中,則恰恰相反。在過去,List.sort ( )的默認(rèn)實(shí)現(xiàn)會調(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)檫@會導(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

publicvoidsort(Comparatorc){

Object[]elements=toArray();

Arrays.sort(elements,c);

ListIteratoriterator=(ListIterator)listIterator();

for(Objectelement:elements){

iterator.next();

iterator.set((E)element);

}

}

在大多數(shù)情況下,您也可以使用根據(jù) API 級別委托給其他默認(rèn)實(shí)現(xiàn)的實(shí)現(xiàn)重寫List.sort ( )

例如:

@Override

publicvoidsort(Comparatorcomparator){

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 之前的平臺版本中,如果通過調(diào)用List.sort ( )進(jìn)行排序,則當(dāng)?shù)幚鞟rrayList以及在迭代過程中調(diào)用sort ( )時(shí),會引發(fā)ConcurrentModificationException。而Collections.sort ( )則不會引發(fā)異常。

此項(xiàng)變更使平臺行為更加一致:現(xiàn)在,兩種方法都會引發(fā)ConcurrentModificationException。

媒體

框架會執(zhí)行音頻閃避。進(jìn)行AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK時(shí),應(yīng)用不會失去焦點(diǎn)。新的 API 適用于需要暫停而不是閃避的應(yīng)用。請注意,此行為無法在 Android O Developer Preview 1 版本中實(shí)現(xiàn)。

當(dāng)用戶打電話時(shí),活動的媒體流將在通話期間靜音。

所有與音頻相關(guān)的 API 都應(yīng)使用AudioAttributes而不是音頻流類型來說明音頻播放用例。僅為音量控制繼續(xù)使用音頻流類型。流類型(例如,已棄用的AudioTrack constructor)的其他用途仍然有效,但是系統(tǒng)會將其記錄為錯(cuò)誤。

使用AudioTrack時(shí),如果應(yīng)用請求了足夠大的音頻緩沖區(qū),則框架將嘗試使用深度緩沖區(qū)輸出(如果可用)。

在 Android O 中,媒體按鈕事件的處理有所不同:

在界面操作組件中處理媒體按鈕未發(fā)生變化:前臺操作組件在處理媒體按鈕時(shí)仍然優(yōu)先。

如果前臺操作組件不處理媒體按鈕,系統(tǒng)會將媒體按鈕路由到最近在本地播放音頻的應(yīng)用。在確定哪些應(yīng)用接收媒體按鈕事件時(shí),不再考慮活動狀態(tài)、標(biāo)志和媒體會話的播放狀態(tài)。即使在應(yīng)用調(diào)用setActive( false )后,媒體會話仍然可以接收媒體按鈕事件。

如果應(yīng)用的媒體會話已經(jīng)釋放,系統(tǒng)會將媒體按鈕事件發(fā)送到應(yīng)用的MediaButtonReceiver(如果有)。

對于任何其他情況,系統(tǒng)都會舍棄媒體按鈕事件。與其開始播放錯(cuò)誤的應(yīng)用,不如不播放任何東西。

下圖匯總了新的媒體按鈕路由邏輯:

類加載行為

Android O 檢查確保類加載器在加載新類時(shí)不會違反運(yùn)行時(shí)假設(shè)條件。不論類引用自 Java(來自forName ( ))、Dalvik 字節(jié)碼還是 JNI,都會執(zhí)行這些檢查。平臺不會攔截 Java 對loadClass ( )函數(shù)的直接調(diào)用,也不會檢查此類調(diào)用的結(jié)果。此行為不應(yīng)影響運(yùn)行良好的類加載器的正常運(yùn)行。

平臺將檢查類加載器返回的類描述符是否與預(yù)期的描述符一致。如果返回的描述符與預(yù)期不符,平臺會引發(fā)NoClassDefFoundError錯(cuò)誤,并在異常日志中存儲一條注明不一致之處的詳細(xì)錯(cuò)誤消息。

平臺還檢查請求的類描述符是否有效。此檢查捕獲間接加載諸如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)行此操作,會導(dǎo)致 Android 運(yùn)行時(shí)引發(fā)InternalError錯(cuò)誤,同時(shí)顯示消息 “Attempt to register dex filewith multiple class loaders” 。

DexFile API 現(xiàn)已棄用,強(qiáng)烈建議您改為使用此平臺的類加載器之一,包括PathClassLoader或BaseDexClassLoader。

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

在 Android 中,所有類加載器都被視為支持并行運(yùn)行。當(dāng)多個(gè)線程爭用同一個(gè)類加載器加載相同的類時(shí),第一個(gè)完成此操作的線程勝出,而操作結(jié)果將用于其他線程。無論類加載器是返回同一個(gè)類、返回不同的類還是引發(fā)異常,都將發(fā)生此行為。該平臺靜默忽略此類異常。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,045評論 25 709
  • 文章摘要1、提醒窗口2、內(nèi)容變更通知3、視圖焦點(diǎn)4、安全性5、帳號訪問和可檢測性6、隱私性7、權(quán)限8、媒體9、原生...
    Android那些事兒閱讀 977評論 0 50
  • 文章摘要1、后臺執(zhí)行限制2、Android 后臺位置限制3、應(yīng)用快捷鍵4、語言區(qū)域和國際化5、提醒窗口6、輸入和導(dǎo)...
    Android那些事兒閱讀 648評論 1 6
  • 溫暖男人,影響男人,調(diào)教男人 課程筆記 第1-2課 婚姻及男人的真相 婚姻里矛盾與沖突的來源:文化/心智/習(xí)慣差異...
    輕言細(xì)語0001閱讀 938評論 0 6
  • 手機(jī)里的設(shè)置是隨機(jī)播放 沒有單曲循環(huán)來得浪漫 歡快的歌聲回響 時(shí)光依舊慢放 把臉埋進(jìn)《放學(xué)后》 東野圭吾會指示著神...
    安度無恙年華閱讀 241評論 0 2

友情鏈接更多精彩內(nèi)容