Android O 除了提供諸多新特性和功能外,還對系統和 API 行爲做出了各種變更。本文重點介紹您應該瞭解並在開發應用時加以考慮的一些主要變更。
其中大部分變更會影響所有應用,而不論應用針對的是何種版本的 Android。不過,有幾項變更僅影響針對 Android O 的應用。爲清楚起見,本頁面分爲兩個部分:針對所有 API 級別的應用和針對 Android O 的應用。
針對所有 API 級別的應用
這些行爲變更適用於 在 Android O 平臺上運行的 所有應用,無論這些應用是針對哪個 API 級別構建。所有開發者都應查看這些變更,並修改其應用以正確支持這些變更(如果適用)。
後臺執行限制
Android O 爲提高電池壽命而引入的變更之一是,當您的應用進入已緩存狀態時,如果沒有活動的組件,系統將解除應用具有的所有喚醒鎖。
Android 後臺位置限制
爲節約電池電量,保持良好的用戶體驗和確保系統健康運行,在運行 Android O 的設備上使用後臺應用時,降低了後臺應用接收位置更新的頻率。此行爲變更會影響包括 Google Play 服務在內的所有接收位置更新的應用。
此類變更會影響以下 API:
- Fused Location Provider (FLP)
- Geofencing
- GNSS Measurements
- Location Manager
Android O 還對特定方法做出了以下變更:
NotificationManager.startServiceInForeground()
方法將啓動一個前臺服務。啓動前臺服務的老辦法將不再奏效。- 現在,如果針對 Android O 的應用嘗試在限制服務的情況下使用
startService()
方法,則該方法將引發一個IllegalStateException
。
爲確保您的應用按預期方式運行,請完成以下步驟:
- 查看您的應用的邏輯,並確保您使用的是最新的位置 API。
- 測試您的應用是否在每個用例中都表現出預期行爲。
- 考慮使用 Fused Location Provider (FLP) 或地理圍欄來處理依賴於用戶當前位置的用例。
如需瞭解此類變更的詳細信息,請參閱後臺位置限制。
藍牙
Android O 對 ScanRecord.getBytes()
方法檢索的數據長度做出了以下變更:
getBytes()
方法對於所接收的字節數不作任何假定。因此,應用不應受所返回的任何最小或最大字節數的影響。相反,應用應當計算所返回數組的長度。- 兼容藍牙 5 的設備返回的數據長度可能會超出之前最大 60 個字節的限制。
- 如果遠程設備未提供掃描響應,則也可能返回少於 60 個字節的數據。
輸入和導航
隨着 Android 應用出現在 Chrome 操作系統和平板電腦等其他大尺寸設備上,我們看到,用戶在 Android 應用中又重新開始使用鍵盤導航。在 Android O 中,我們又再次使用鍵盤作爲導航輸入設備,從而爲基於箭頭和標籤的導航構建了一種更可靠並且可預測的模型。
如需瞭解如何在您的應用中改善對鍵盤導航的支持,請閱讀支持鍵盤導航指南。
無障礙功能
現在,無障礙服務可識別應用的 TextView
對象內部的所有 ClickableSpan
實例。
如需瞭解有關如何讓您的應用更便於訪問的更多信息,請參閱無障礙功能。
安全性
Android O 包含以下與安全性有關的變更:
- 此平臺不再支持 SSLv3。
- 在與未正確實現 TLS 協議版本協商的服務器建立 HTTPS 連接時,
HttpsURLConnection
不再嘗試回退到之前的 TLS 協議版本並重試的權宜方法。 - Android O 將使用安全計算 (SECCOMP) 過濾器來過濾所有應用。允許的系統調用列表僅限於通過 bionic 公開的系統調用。此外,還提供了其他幾個後向兼容的系統調用,但我們不建議使用這些系統調用。
- 現在,您的應用的
WebView
對象將在多進程模式下運行。網頁內容在獨立的進程中處理,此進程與包含應用的進程相隔離,以提高安全性。 - 如需瞭解與使用原生庫有關的安全性增強的信息,請參閱原生庫。
有關提升應用安全性的其他準則,請參閱面向 Android 開發者的安全性。
隱私性
Android O 對平臺做出了以下與隱私性有關的變更。
- 現在,平臺改變了標識符的處理方式。
- 現在,根據應用(而不是根據用戶)來確定
ANDROID_ID
的值範圍。應用軟件包名稱、簽名、用戶和設備的每個組合都具有唯一的ANDROID_ID
值。同一個設備上運行的兩個應用不再出現 Android ID 相同的情況,因此不可能建立關聯。 - 只要軟件包名稱和簽名密鑰相同,在軟件包卸載或重新安裝時
ANDROID_ID
的值不會改變。 - 如果軟件包簽名密鑰是因爲更新而發生改變,那麼,
ANDROID_ID
的值不會改變。 - Widevine ID 的範圍根據應用來確定。
對於在 OTA 之前安裝的應用,除非卸載並重新安裝,否則,
ANDROID_ID
的值將保持不變。如果您要繼續將 Android ID 用於與設備綁定的免費試用保護,您可以這麼做。請確保軟件包名稱和簽名相一致。
要藉助一個簡單的標準系統實現應用獲利,請使用廣告 ID。廣告 ID 是 Google Play 服務針對廣告服務提供的唯一 ID,此 ID 可由用戶重置。
- 現在,根據應用(而不是根據用戶)來確定
- 查詢
net.hostname
系統屬性返回的結果爲空。 - 您無法再假定 APK 駐留在名稱以 -1 或 -2 結尾的目錄中。應用應使用
sourceDir
獲取此目錄,而不能直接使用目錄格式。
網絡連接和 HTTP(S) 連接
Android O 對網絡連接和 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 O 廢除了此權宜方法;系統對格式錯誤的 URI 會返回 null。
- Android O 在實現 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 O 之前的版本,即使 send() 調用成功,後續的 recv() 調用也會引發 SocketException。爲確保一致性,現在這兩個調用均會引發 SocketException。
- 在回退到 TCP Echo 協議之前,InetAddress.isReachable() 會嘗試執行 ICMP。
- 對於某些屏蔽端口 7 (TCP Echo) 的主機(例如 google.com),如果它們接受 ICMP Echo 協議,現在也許能夠訪問它們。
- 對於確實無法訪問的主機,此項變更意味着調用需要兩倍的時間才能返回結果。
記錄未捕獲的異常
如果某個應用安裝的 Thread.UncaughtExceptionHandler
未移交給默認的 Thread.UncaughtExceptionHandler
,則當出現未捕獲的異常時,系統不會終止應用。從
Android O 開始,在此情況下系統將記錄異常堆棧跟蹤情況;在之前的平臺版本中,系統不會記錄異常堆棧跟蹤情況。
我們建議,自定義 Thread.UncaughtExceptionHandler
實現始終移交給默認處理程序處理;遵循此建議的應用不受
Android O 此項變更的影響。
集合的處理
現在,AbstractCollection.removeAll()
和 AbstractCollection.retainAll()
始終引發 NullPointerException
;之前,當集合爲空時不會引發 NullPointerException
。此項變更使行爲符合文檔要求。
語言區域和國際化
Android 7.0(API 級別 24)引入能指定默認類別語言區域的概念,但是某些 API 在本應使用默認 DISPLAY
類別語言區域時,仍然使用不帶參數的通用 Locale.getDefault()
方法。現在,在
Android O 中,以下方法使用 Locale.getDefault(Category.DISPLAY)
來代替 Locale.getDefault()
:
當爲 Locale
參數指定的 displayScript 值不可用時,Locale.getDisplayScript(Locale)
同樣回退到 Locale.getDefault()
。
與語言區域和國際化有關的其他變更如下:
- 調用
Currency.getDisplayName(null)
會引發NullPointerException
,以與文檔規定的行爲保持一致。 - 改變了時區名稱的分析方法。之前,Android 設備使用在啓動時取樣的系統時鐘值,緩存用於分析日期時間的時區名稱。因此,如果在啓動時或其他較爲罕見的情況下系統時鐘出錯,可能對分析產生負面影響。
現在,一般情況下,在分析時區名稱時分析邏輯將使用 ICU 和當前系統時鐘值。此項變更可提供更加準確的結果,如果您的應用使用
SimpleDateFormat
等類,此結果可能與之前的 Android 版本不同。 - Android O 將 ICU 的版本更新至版本 58。
聯繫人提供程序使用情況統計方法的變更
在之前版本的 Android 中,聯繫人提供程序組件允許開發者獲取每個聯繫人的使用情況數據。此使用情況數據揭示了與某個聯繫人相關聯的每個電子郵件地址和每個電話號碼的信息,包括與該聯繫人聯繫的次數以及上次聯繫該聯繫人的時間。請求 READ_CONTACTS
權限的應用可以讀取此數據。
如果應用請求 READ_CONTACTS
權限,它們仍可以讀取此數據。從
Android O 開始,使用情況數據查詢會返回近似值,而不是精確值。不過,Android 系統內部仍然會保留精確值,因此,此變更不會影響 auto-complete API。
此行爲變更會影響以下查詢參數:
應用快捷鍵
Android O 對應用快捷鍵做出了以下變更:
com.android.launcher.action.INSTALL_SHORTCUT
廣播不再會對您的應用有任何影響,因爲它現在是私有的隱式廣播。相反,您應使用ShortcutManager
類中的requestPinShortcut()
方法創建應用快捷鍵。- 現在,
ACTION_CREATE_SHORTCUT
Intent 可以創建可使用ShortcutManager
類進行管理的應用快捷鍵。此 Intent 還可以創建不與ShortcutManager
交互的舊版啓動器快捷鍵。在以前,此 Intent 只能創建舊版啓動器快捷鍵。 - 現在,使用
requestPinShortcut()
創建的快捷鍵和在處理ACTION_CREATE_SHORTCUT
Intent 的 Activity 中創建的快捷鍵均已轉換爲功能齊全的應用快捷鍵。因此,應用現在可以使用ShortcutManager
中的方法來更新這些快捷鍵。 - 舊版快捷鍵仍然保留了它們在舊版 Android 中的功能,但您必須在應用中手動將它們轉換成應用快捷鍵。
如需瞭解有關應用快捷鍵變更的更多信息,請參閱固定快捷鍵和小部件預覽功能指南。
提醒窗口
如果應用使用 SYSTEM_ALERT_WINDOW
權限並且嘗試使用以下窗口類型之一來在其他應用和系統窗口上方顯示提醒窗口:
...那麼,這些窗口將始終顯示在使用 TYPE_APPLICATION_OVERLAY
窗口類型的窗口下方。如果應用針對的是
Android O,則應用會使用 TYPE_APPLICATION_OVERLAY
窗口類型來顯示提醒窗口。
如需瞭解詳細信息,請參閱針對 Android O 的應用的行爲變更內的提醒窗口的常用窗口類型部分。
企業中的 Android
Android O 包含會影響企業應用的變更。如果您正在爲企業構建應用,包括 DPC(設備規範控制器),您應查閱企業中的 Android 頁面中介紹的變更,並相應修改您的應用。
針對 Android O 的應用
這些行爲變更專門應用於針對 O 平臺或更高平臺版本的應用。針對 Android O 或更高平臺版本進行編譯,或將 targetSdkVersion
設爲 Android O 或更高版本的應用開發者必須修改其應用以正確支持這些行爲(如果適用)。
後臺執行限制
爲提高設備性能,系統會限制未在前臺運行的應用的某些行爲。具體而言:
- 現在,在後臺運行的應用對後臺服務的訪問受到限制。
- 應用無法使用其清單註冊大部分隱式廣播(即,並非專門針對此應用的廣播)。
如需瞭解詳細信息,請參閱後臺執行限制。
安全性
如果您的應用的網絡安全性配置選擇退出對明文流量的支持,那麼,您的應用的 WebView
對象無法通過
HTTP 訪問網站。每個 WebView
對象必須轉而使用 HTTPS。
有關提升應用安全性的其他準則,請參閱面向 Android 開發者的安全性。
隱私性
以下變更影響 Android O 的隱私性。
- 系統屬性
net.dns1
、net.dns2
、net.dns3
和net.dns4
不再可用,此項變更可加強平臺的隱私性。 - 要獲取 DNS 服務器之類的網絡連接信息,具有
ACCESS_NETWORK_STATE
權限的應用可以註冊NetworkRequest
或NetworkCallback
對象。這些類在 Android 5.0(API 級別 21)及更高版本中提供。 - 從 Android O 開始,不再支持 Build.SERIAL,此字段將返回一個未定義的值。需要知道硬件序列號的應用應改爲使用新的
Build.getSerial()
方法,該方法要求具有READ_PHONE_STATE
權限。 LauncherApps
API 不再允許託管配置文件應用獲取有關主配置文件的信息。當某個用戶在託管配置文件中時,LauncherApps
API 的行爲就像同一配置文件組的其他配置文件中未安裝任何應用一樣。和之前一樣,嘗試訪問無關聯的配置文件會引發 SecurityExceptions。
權限
在 Android O 之前,如果應用在運行時請求權限並且被授予該權限,系統會錯誤地將屬於同一權限組並且在清單中註冊的其他權限也一起授予應用。
對於針對 Android O 的應用,此行爲已被糾正。系統只會授予應用明確請求的權限。然而,一旦用戶爲應用授予某個權限,則所有後續對該權限組中權限的請求都將被自動批准。
例如,假設某個應用在其清單中列出 READ_EXTERNAL_STORAGE
和 WRITE_EXTERNAL_STORAGE
。應用請求 READ_EXTERNAL_STORAGE
,並且用戶授予了該權限。如果該應用針對的是
API 級別 24 或更低級別,系統還會同時授予 WRITE_EXTERNAL_STORAGE
,因爲該權限也屬於同一 STORAGE
權限組並且也在清單中註冊過。如果該應用針對的是
Android O,則系統此時僅會授予 READ_EXTERNAL_STORAGE
;不過,如果該應用後來又請求 WRITE_EXTERNAL_STORAGE
,則系統會立即授予該權限,而不會提示用戶。
媒體
- 框架會執行音頻閃避。進行
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
時,應用不會失去焦點。新的 API 適用於需要暫停而不是閃避的應用。不過,Android O 中未提供此行爲。 - 當用戶打電話時,活動的媒體流將在通話期間靜音。
- 音頻流類型應僅用於音量控制;所有其他流類型的使用(例如
AudioTrack
構造函數)仍有效,但系統會將其作爲錯誤記錄下來。 - 所有與音頻相關的 API 均使用
AudioAttributes
來描述音頻播放用例。 - 使用
AudioTrack
時,如果應用請求了足夠大的音頻緩衝區,則框架將嘗試使用深度緩衝區輸出(如果可用)。
原生庫
在針對 Android O 的應用中,如果原生庫包含任何可寫且可執行的加載代碼段,則不會再加載原生庫。倘若某些應用的原生庫包含不正確的加載代碼段,則此變更可能會導致這些應用停止工作。這是一種安全加強措施。
如需瞭解詳細信息,請參閱可寫且可執行的代碼段。
與早期的開發者預覽版相同,Android O 還有助於更輕鬆地發現所有與鏈接器有關的問題。鏈接器的變更綁定到應用的目標 API 級別。如果應用的目標 API 級別發生鏈接器變更,則該應用無法加載該庫。如果您的目標 API 級別低於發生鏈接器變更的 API 級別,則 logcat 會顯示一條警告消息。在預覽版期間,與鏈接器有關的問題不僅會顯示在 logcat 中,也會以 toast 的形式顯示。對於特定的 API 級別,警告可能會變成錯誤,此變更有助於提前發現此類問題。
集合的處理
在 Android O 中,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 O 之前的平臺版本中,如果通過調用List.sort()
進行排序,則當迭代處理ArrayList
以及在迭代過程中調用sort()
時,會引發ConcurrentModificationException
。而Collections.sort()
則不會引發異常。此項變更使平臺行爲更加一致:現在,兩種方法都會引發
ConcurrentModificationException
。
帳號訪問和可檢測性
除非身份驗證器擁有用戶帳號或用戶授予訪問權限,否則,應用將無法再訪問用戶帳號。僅擁有 GET_ACCOUNTS
權限尚不足以訪問用戶帳號。要獲得帳號訪問權限,應用應使用 AccountManager.newChooseAccountIntent()
或特定於身份驗證器的方法。獲得帳號訪問權限後,應用可以調用 AccountManager.getAccounts()
來訪問帳號。
Android O 已棄用 LOGIN_ACCOUNTS_CHANGED_ACTION
。相反,應用在運行時應使用 addOnAccountsUpdatedListener()
獲取帳號更新信息。
有關新增 API 和增加的帳號訪問和可檢測性方法的信息,請參閱此文檔的“新增 API”部分中的帳號訪問和可檢測性。
提醒窗口
使用 SYSTEM_ALERT_WINDOW
權限的應用無法再使用以下窗口類型來在其他應用和系統窗口上方顯示提醒窗口:
相反,應用必須使用名爲 TYPE_APPLICATION_OVERLAY
的新窗口類型。
使用 TYPE_APPLICATION_OVERLAY
窗口類型顯示應用的提醒窗口時,請記住新窗口類型的以下特性:
- 應用的提醒窗口始終顯示在狀態欄和輸入法等關鍵系統窗口的下面。
- 系統可以移動使用
TYPE_APPLICATION_OVERLAY
窗口類型的窗口或調整其大小,以改善屏幕顯示效果。 - 通過打開通知欄,用戶可以訪問設置來阻止應用顯示使用
TYPE_APPLICATION_OVERLAY
窗口類型顯示的提醒窗口。
內容變更通知
Android O 更改了 ContentResolver.notifyChange()
和 registerContentObserver(Uri,
boolean, ContentObserver)
在面向針對 Android O 的應用中的行爲方式。
現在,這些 API 需要在所有 URI 中爲頒發機構定義一個有效的 ContentProvider
。使用相關權限定義一個有效的 ContentProvider
可幫助您的應用防範來自惡意應用的內容變更,並防止將可能的私密數據泄露給惡意應用。