前言
Android 12 是 2021 年 10 月發布的最新正式版本,然而很多同學表示還沒有適配。針對開發者在進行版本適配過程中遇到的問題,我們建立了 GitHub · AndroidPlatformWiki。我們希望站在開發者的視角,全面且深刻地解讀每個 Android 版本更新,以此建立起一個體系化的 Android 系統適配手冊。具體包括:
兩個維度
根據內容相關度,我們將從 2 個維度解讀:
- 基于時間線: 現階段官方每年會發布一個新的版本,因此有必要以一個 Android 版本為單位,解讀該版本涉及的新功能與行為變更。這樣可以幫助開發同學了解新版本的更新內容,例如我們會通過一個文檔解讀 Android 13 版本的更新內容與適配自查表;
- 基于內容線: 通常一個系統功能模塊會歷經多個系統版本更新才會趨于穩定,因此有必要以一個功能為單位,解讀該功能的主要能力以及不同版本的變更和差異。這樣可以幫助開發同學了解該功能在不同版本上的差異,例如我們會通過一個文檔單獨解讀系統通知。
三個等級
根據故障敏感性分級,我們將系統變更的兼容性劃分為 3 個等級:
- 強制適配 ?: 所有應用必須適配,否則會出現編譯不通過、功能不可用或者用戶體驗受損等問題;
- 推薦適配 ?: 不強制要求適配,但適配的應用將獲得更出色的用戶體驗或更安全的隱私保護等收益;
- 已適配: 應用不需要任何改動就已經兼容。
兩類行為變更
系統行為變更通常屬于以下兩種類別之一:
- 面對所有應用的行為變更: 運行在該系統版本上的所有應用都會影響,而無論應用的 targetSDKVersion 為何。通常應該先針對這些變更進行適配和測試,這有助于用戶在新版本系統上運行你的應用時,用戶體驗不會受損;
- 以特定 targetSDKVersion 為目標版本的行為變更: 只有 targetSDKVersion 高于或等于系統版本的應用會影響,通常是影響較大或適配工作量較大的變更,我們可以理解為一個 Google 留給開發者的適配緩沖。
Android 12 適配自查表
根據故障敏感性分級,我們將 Android 12 系統變更的兼容性劃分為 3 個等級:
- 強制適配 ?:涉及該功能的所有應用必須適配的變更,不適配的應用會出現編譯不通過、功能不可用,或者用戶體驗出現一定受損等問題;
- 推薦適配 ?:不強制要求適配的變更,適配的應用具有更出色的用戶體驗或更安全的隱私保護等;
- 已適配:應用不需要任何改動就可以兼容的變更。
以 Android 12 為目標版本的應用
類別 | 變更 | 兼容性 | 摘要 |
---|---|---|---|
1. 用戶體驗 | 自定義通知外觀模板統一 | 強制 ? | 自定義通知的內容區域縮小為自定義通知模板內的一塊區域,不再完整覆蓋通知區域 |
畫中畫 (PiP) 交互改進 | 推薦 ? | 優化畫中畫 (PiP) 模式的用戶交互 | |
Toast 視圖改進 | 已適配 | 系統 Toast 視圖文本最多可以顯示兩行,并且始終在文本旁邊顯示應用圖標 | |
2. 安全和隱私設置 | 新藍牙運行時權限(新) | 推薦 ? | 引入一些新運行時權限,用于更好地管理應用于附近藍牙設備的連接,而無需請求位置信息權限 |
傳感器采樣率限制 | 已適配 | 系統會限制某些移動傳感器和位置傳感器的數據的刷新率 | |
應用休眠改進 | 已適配 | 擴展應用休眠機制 | |
數據訪問審核中的歸因標記改進 | 強制 ? | 歸因標記必須在 Manifest 文件中聲明 | |
ADB 備份限制 | 已適配 | adb backup 導出的數據不再默認包含應用數據 | |
顯式指定組件 exported 屬性 | 強制 ? | 聲明了 <intent-filter> 過濾器的組件必須顯式設置 android:exported 屬性 | |
顯式指定 PendingIntent 可變性 | 強制 ? | PendingIntent 必須顯式聲明一個可變性標志 | |
檢測不安全的嵌套 Intent 啟動 | 已適配 | StrictMode 會檢測不安全的嵌套 Intent 啟動 | |
3. 性能和電池 | 精確的鬧鐘權限(新) | 強制 ? | 設置 AlarmManager 精準鬧鐘的應用必須在 Manifest 中聲明權限 |
前臺服務啟動限制 | 強制 ? | 除了少數情況外,禁止應用從后臺啟動前臺服務 | |
通知 trampoline 限制 | 強制 ? | 禁止從通知 trampoline 間接啟動目標 Activity |
所有應用
類別 | 變更 | 兼容性 | 摘要 |
---|---|---|---|
4. 用戶體驗 | Material You 設計語言(新) | 已適配 | 新的設計語言 |
富媒體內容插入(新) | 推薦 ? | 應用可以從統一的位置接受任何來源(剪貼板粘貼、鍵盤輸入或拖放操作)的內容 | |
支持 AVIF 圖片(新) | 推薦 ? | 支持 AVIF 格式圖片 | |
應用啟動動畫 API SplashScreen(新) | 強制 ? | 支持定制應用啟動轉場動畫 | |
Widget 桌面小部件改進 | 推薦 ? | 改進 Widgets 外觀和行為 | |
圖形 API 改進 | 推薦 ? | 新增圖形效果 | |
OverScroll 過度滑動動畫改進 | 已適配 | 過度滑動動畫改為拉伸和反彈效果 | |
通知改進 | 推薦 ? | 增加新的通知樣式和安全保障 | |
HTTP 深度鏈接解析改進 | 已適配 | 調整了 HTTP Intent 的默認解析行為 | |
全屏模式的手勢導航改進 | 推薦 ? | 增加了一次交互即可執行手勢導航的模式 | |
屏幕尺寸 API 變更 | 強制 ? | 針對適配每種設配上獲取屏幕尺寸的需求,系統引入了新 API | |
多窗口模式標準化 | 強制 ? | 在大屏設備中,系統會為所有 Activity 啟用多窗口模式 | |
延遲展示前臺服務通知 | 已適配 | 除了特殊情況外,前臺服務通知會延遲 10 s 顯示 | |
activity 生命周期改進 | 已適配 | 修改根 Activity 的返回行為 | |
Surface 幀率切換改進 | 推薦 ? | 引入強制切換幀率的 API | |
5. 安全和隱私設置 | 隱私信息中心(新功能) | 推薦 ? | 隱私信息中心以一個時間軸的方式顯示過去時間內所有應用對于敏感信息的訪問情況 |
支持只授予粗略位置權限(新) | 強制 ? | 用戶可以只授予應用模糊位置權限 | |
麥克風和攝像頭切換開關(新) | 已適配 | 用戶可以通過全局切換開關停用整臺設備上的攝像頭或麥克風權限 | |
麥克風和攝像頭指示標示(新) | 已適配 | 應用使用麥克風或相機時,狀態欄會有圖標標記。 | |
剪貼板訪問提示(新) | 已適配 | 應用首次從另一個應用訪問剪輯數據時,會彈出一個消息框消息 | |
隱藏應用疊加窗口(新) | 推薦 ? | 應用的窗口可見時可以隱藏所有可見的系統級懸浮窗口 | |
應用無法關閉系統對話框 | 強制 ? | 除了特殊情況外,禁止應用嘗試關閉系統對話框 | |
屏蔽不信任的觸摸事件 | 強制 ? | 屏蔽從不同應用的窗口傳遞的事件 | |
6. 性能和電池 | 應用待機分區改進 | 已適配 | 引入了一個新的受限待機分區 |
第 1~3 節介紹的是以 Android 12 為目標版本的應用行為變更和新功能更新,我將這部分更新總結為 3 部分:
1、用戶體驗(以 Android 12 為目標版本)
2、安全和隱私設置(以 Android 12 為目標版本)
3、性能和電池(以 Android 12 為目標版本)
1. 用戶體驗(以 Android 12 為目標版本)
1.1 自定義通知外觀模板統一
Android 系統通知可以分為兩類樣式:標準通知 + 自定義通知
-
標準通知:標準通知是指基于
NotificationCompat.Builder#setContentTitle()
等模板 API 構建通知,最終會按照系統預置的視圖模板展示。例如:
-
自定義通知:自定義通知是指基于
NotificationCompat.Builder#setCustomContentView()
等 API 構建的通知,最終會按照開發者自定義的的布局展示,而不會按照標準通知模板展示。
從 Android 12 系統開始,系統規范了自定義通知的外觀和行為,自定義通知的內容區域縮小為自定義通知模板內的一塊區域,不再完整覆蓋通知區域。因此,如果你的應用使用了自定義通知,則需要進行必要的測試和調整:
- 布局調整:由于內容區域縮小了,需要調整并測試通知布局;
-
設置展開式通知:由于所有通知都是可展開的,所以需要調用
NotificationCompat.Builder#setCustomBigContentView()
設置展開后布局,確保展開和收起狀態一致。
下圖是統一的自定義通知模板:
可以看出,這次改動是 Google 希望自定義通知能夠呈現相對一致的感觀體驗,以及減少不同設備上產生的兼容性問題。
1.2 畫中畫 (PiP) 交互改進
畫中畫模式是 Android 8.0 中引入的一種多窗口模式,最常用于視頻播放 Activity,能夠實現在視頻播放過程中打開其他應用,而不退出中斷當前視頻。目前主流的音視頻 App 都支持畫中畫模式,你可以在系統設置中搜索 “畫中畫” 查看。這次改動是 Google 對畫中畫模式的用戶交互進行優化,具體參考資料:
- 對畫中畫的支持 —— 官方文檔
- Android 12 畫中畫改進 —— 官方文檔
1.3 Toast 視圖改進
在 Android 12 中,系統 Toast 視圖文本最多可以顯示兩行,并且始終在文本旁邊顯示應用圖標。相關資料:消息框概覽
2. 安全和隱私設置(以 Android 12 為目標版本)
2.1 新藍牙運行時權限(新功能)
Android 12 系統引入了新的運行時權限 BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE 和 BLUETOOTH_CONNECT 權限,用于更好地管理應用于附近藍牙設備的連接。
在低版本中,應用與附近藍牙設備連接需要用戶授予 ACCESS_FINE_LOCATION
精確位置權限,這其實是不合理的設計,因為用戶很難理解為什么藍牙連接會跟位置信息有關。從 Android 12 系統開始,ACCESS_FINE_LOCATION 精確位置權限是可選項,只要應用不會通過藍牙推導物理位置信息,就不再需要請求。如果不會,你需要在 Manifest 中顯式做出 usesPermissionFlags
聲明:
<manifest>
<!-- Include "neverForLocation" only if you can strongly assert that
your app never derives physical location from Bluetooth scan results. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
...
</manifest>
-
新藍牙權限體系(以 Android 12 為目標版本):
- BLUETOOTH_SCAN:允許搜索附近藍牙設備;
- BLUETOOTH_ADVERTISE:允許當前設備暴露給其他藍牙設備;
- BLUETOOTH_CONNECT:允許當前設備連接其他藍牙設備;
- ACCESS_FINE_LOCATION(可選):允許由藍牙信息推導設備位置信息。
-
舊藍牙權限體系:
- BLUETOOTH:允許與藍牙相關的交互;
- ACCESS_FINE_LOCATION(必選):允許由藍牙信息推導設備位置信息,在 Android 9 或以下版本,可以用 ACCESS_COARSE_LOCATION 替代。
另外,BLUETOOTH_SCAN 等權限是 NEARBY_DEVICES 附近設備權限組的一部分。請求該權限組的權限,權限授予對話框會提示用戶批準訪問附近的設備。
可以看出,這次的改動 Google 是希望連接藍牙設備的權限授予能夠給用戶更精準的權限功能描述。
相關資料:
2.2 傳感器采樣率限制
大多數 Android 設備都有內置傳感器,用來測量運動、屏幕方向和各種環境條件,這些傳感器能夠提供高度精確的原始數據。為了保護有關用戶的潛在敏感信息,Android 12 系統會限制某些移動傳感器和位置傳感器的數據的刷新率。
相關資料:傳感器概覽 —— 官方文檔
2.3 應用休眠改進
Android 11 引入了應用休眠機制,如果用戶有幾個月沒有與應用交互,那么系統會將應用置于休眠 / 冬眠狀態,Android 12 擴展了應用休眠機制:
- Android 11:重置已授予的運行時敏感權限;
- Android 12:重置已授予的運行時敏感權限;無法從后臺運行任務;無法接收推送通知;應用緩存文件會被刪除。
相關資料:應用休眠 —— 官方文檔
2.4 數據訪問審核中的歸因標記改進
Android 11 引入了數據訪問審核 API,開發者可以在應用訪問用戶隱私數據的代碼位置增加歸因標記,并通過注冊 AppOpsManager.OnOpNotedCallback
監聽。這個功能提供了對調用隱私數據的監聽,無論是應用層還是依賴庫中的代碼,只要訪問到私密數據(危險權限)都會回調。從 Android 12 系統開始,歸因標記必須在 Manifest 文件中聲明,例如:
<manifest ...>
<!-- The value of "android:tag" must be a literal string, and the
value of "android:label" must be a resource. The value of
"android:label" should be user-readable. -->
<attribution android:tag="sharePhotos"
android:label="@string/share_photos_attribution_label" />
...
</manifest>
相關資料:數據訪問審核 —— 官方文檔
2.5 ADB 備份限制
為了保護私有應用數據,Android 12 變更了 adb backup
命令的默認行為,adb backup 導出的數據不再默認包含應用數據。如果開發階段需要依賴于 adb backup 導出的應用數據,可以將 Manifest 文件中將 android:debuggable 設置為 true 來導出應用數據。
2.6 顯式指定組件 exported 屬性
組件屬性 android:exported
用于設置該組件是否支持其他應用交互,exported 為 false 表示不允許該組件被其他應用啟動。一般 exported 屬性默認為 false,除非組件聲明了 <intent-filter>
過濾器(即支持隱式啟動),則 exported 屬性默認為 true。從 Android 12 系統開始,聲明了 <intent-filter> 過濾器的組件必須顯式設置 android:exported 屬性。例如:
<service android:name="com.example.app.backgroundService"
android:exported="false">
<intent-filter>
<action android:name="com.example.app.START_BACKGROUND" />
</intent-filter>
</service>
否則,在編譯應用時就會有報錯:
Manifest merger failed : Apps targeting Android 12 and higher are required \
to specify an explicit value for android:exported when the corresponding \
component has an intent filter defined.
如果使用低版本的 Android Gradle 插件雖然可以編譯成功,但安裝時會報錯:
Installation did not succeed.
The application could not be installed: INSTALL_FAILED_VERIFICATION_FAILURE
可以看出,這次改動背后的理念是 “不要相信默認值”,因為不符合預期的默認值會產生更嚴重的風險。舉個例子,由于開發者的疏忽,一個原本不允許外部應用啟動的組件未顯式聲明 android:exported=“false”,而正好該組件聲明了 <intent-filter> 過濾器,那么就因為默認值的影響的產生了一個安全風險。而強制開發者對聲明 <intent-filter> 過濾器的組件顯式聲明 android:exported 的值,就可以避免了默認值的安全風險。同樣的道理在對接外部系統時,也不要相信默認值,例如網絡請求參數的默認值,能傳的就傳。
2.7 顯式指定 PendingIntent 可變性
為了使 PendingIntent 的處理更加安全,Android 12 要求 PendingIntent 必須顯式聲明一個可變性標志即 FLAG_MUTABLE 或 FLAG_IMMUTABLE。在此之前,PendingIntent 默認是可變的。
2.8 檢測不安全的嵌套 Intent 啟動
Android 12 引入了一項 StrictMode
檢查規則,用于檢測不安全的嵌套 Intent 啟動。StrictMode 模式大家很熟悉了,這里解釋下為什么嵌套 Intent 啟動是不安全的。
舉個例子,開發者的預期效果是 Client App 請求 Provider App 的一個服務,并且希望在請求結束后回調到 Client App 的 ClientCallbackActivity。那么,最直接的方法是將啟動 ClientCallbackActivity 的 Intent 當作參數嵌套到啟動 ApiService 的 Intent 里。例如:
乍看起來沒有問題,但其實這種實現方式存在兩個隱蔽的安全風險:
- Client App:由于 ClientCallbackActivity 是從另一個應用 Provider App 啟動的,因此它必須暴露為 exported。這意味著除了 Provider App 外,設備上其他惡意的應用也可以啟動 ClientCallbackActivity;
- Provider App:由于嵌套的 Intent 是在 Provider App 的上下文中啟動的,因此惡意應用 Attacker App 可以將 Provider App 的任何一個 Activity 嵌套其中,即使啟動的是私有的非 exported 的 Activity,這讓 Provider App 防不勝防。
解決方法是使用 PendingIntent 替代嵌套 Intent,PendingIntent 是 Intent 的包裝容器,也類似于一個嵌套 Intent。但是,很多小伙伴簡單地認為 PendingIntent 只是延遲待處理的 Intent,兩者只有時間維度的區別,這是片面的。
PendingIntent 的最主要的作用是授權外部應用以本應用的身份執行使用嵌套的 Intent。有點拗口哈,在我們這個例子里,就是 Client App 將啟動 ClientCallbackActivity 的 Intent 暴露給 Provider App 后,但 Provider App 在使用 PendingIntent 時,系統會以 Client App 的上下文身份來使用嵌套的 Intent。
PendingIntent pendingIntent = PendingIntent.getActivity(application, 0, resultIntent, 0);
現在,我們再回顧下還有沒有風險:
- Client App:由于 PendingIntent 使用 Client App 的身份使用嵌套的 Intent,那么 ClientCallbackActivity 不再需要暴露為 exported;
- Provider App:由于 PendingIntent 使用 Client App / Attacker App 的身份使用嵌套的 Intent,而它們是沒有權限訪問 Provider App 非 exported 的 ApiSensitiveActivity 的。
相關資料:Android 嵌套 Intent —— 官方博客文章
3. 性能和電池(以 Android 12 為目標版本)
3.1 精確的鬧鐘權限(新功能)
Android 12 系統引入了新的權限 android.permission.SCHEDULE_EXACT_ALARM
,設置 AlarmManager 精準鬧鐘的應用必須在 Manifest 中請求 SCHEDULE_EXACT_ALARM 權限。此外,還新增了一個新的 API —— canScheduleExactAlarms()
,用于檢查應用的精準鬧鐘權限狀態。
相關資料:設置重復鬧鐘時間
3.2 前臺服務啟動限制
Android 12 對應用從后臺啟動前臺服務的行為做出限制,除了 后臺啟動限制的豁免 等少數情況外,如果應用嘗試在后臺運行時啟動前臺服務,系統會拋出 ForegroundServiceStartNotAllowedException
異常。應用可以使用 JobScheduler 中新引入的 加急作業 (expedited job) 來代替之前的做法。
提示:如果一個應用調用
Context.startForegroundService()
以啟動另一個應用擁有的前臺服務,則這些限制僅適用于兩個應用都針對 Android 12 或更高版本的情況。
3.3 通知 trampoline 限制
通知 trampoline (蹦床) 是指利用廣播接收器或服務間接啟動目標 Activity(用戶與通知交互后,應用先啟動服務或廣播接收器作為中介,再去啟動 目標 Activity)。Android 12 系統對通知 trampoline 做出限制,當應用嘗試從通知 trampoline 啟動 Activity,系統會攔截該啟動行為:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \
this should be avoided for performance reasons.
如果你的應用使用了通知 trampoline,那么你需要切換為常規的 PendingIntent 方式。
第 4~6 節介紹的是針對所有應用的應用行為變更和新功能更新,我將這部分更新總結為 3 部分:
- 4、用戶體驗(所有應用)
- 5、安全和隱私設置(所有應用)
- 6、性能和電池(所有應用)
4. 用戶體驗(所有應用)
4.1 Material You 設計語言(新功能)
Android 12 引入了一種名為 Material You 的新設計語言(對 Material Design 的再發展),可幫助構建更具個性化、更精美的應用。
4.2 富媒體內容插入(新功能)
Android 12 系統引入了一個統一 API,使得應用可以從統一的位置接受任何來源(剪貼板粘貼、鍵盤輸入或拖放操作)的內容。例如:
相關資料:接收富媒體內容 —— 官方文檔
4.3 支持 AVIF 圖片(新功能)
Android 12 引入了對使用 AV1 圖片文件格式 (AVIF) 的圖片的支持。AVIF 是一種使用 AV1 編碼的圖片和圖片序列的容器格式。AVIF 利用了視頻壓縮的幀內編碼內容。與以前的圖片格式(例如 JPEG)相比,這種格式可顯著提升相同文件大小下的圖片質量。
相關資料:AVIF has landed —— Jake Archibald 著
4.4 應用啟動動畫 API SplashScreen(新功能)
從 Android 12 系統開始,所有應用的冷啟動和溫啟動期間,系統會使用新的 SplashScreen API 來啟動應用啟動動畫。除了平臺 API 外,Google 還提供了兼容庫 API:androidx.core.splashscreen。
在 SplashScreen API 之前,我們通常是利用 SplashActivity 的背景圖 android:windowBackground
來實現應用啟動轉場效果,這個大家都很熟悉了。如果你不做任何適配,那么根據你配置的 windowBackground 資源值,在 Android 12 上會有不同的效果:
- windowBackground 采用
@color/單色
,則系統會使用該單色和應用的啟動圖標來構成啟動效果,這可能與預期效果不符; - windowBackground 采用
@drawable/圖片
,則系統會繼續使用該圖片來構成啟動效果,這個體驗與低版本系統一致。
因此,如果你的應用采用的是 windowBackground 為圖片資源的方式,那么你不適配也沒有問題。需要升級啟動效果的話,推薦參考以下資料:
- 啟動畫面 —— Android 官方文檔
- Jetpack 新成員 SplashScreen:打造全新的 App 啟動畫面 —— TechMerger 著
可以看出,這次改動 Google 是希望提升下應用啟動時的轉場體驗,同時也給予開發者更多自定義的想象空間。
4.5 Widget 桌面小部件改進
Android 12 改進了現有的 Widgets API,讓它們更實用、更美觀,且更易于發現。相關改動詳見以下資料:
相關資料:
- 應用 Widget 概覽 —— 官方文檔
- Android 12 Widget 改進 —— 官方文檔
- 創造無限可能 | 在 Android 12 中使用 widget —— 官方博客文章
- 更新您的 widget 以適配 Android 12 —— 官方博客文章
4.6 圖形 API 改進
圓角:Android 12 引入了新的圓角 API RoundedCorner
和 WindowInsets.getRoundedCorner(int position),可以提供給 View 實現圓角效果;-
濾鏡:Android 12 添加了新的濾鏡 API RenderEffect 可以給 View 實現常見的圖片效果(如毛玻璃、顏色濾鏡、Android 著色器效果及更多效果)。
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
4.7 OverScroll 過度滑動動畫改進
Android 12 修改了可滾動控件在邊緣過度滑動(OverScroll)的動畫效果,從低版本的邊緣發光效果修改為拉伸和反彈效果。這個邊緣過度滑動效果是可以關閉的,有兩種方法:
- 設置
android:overScrollMode=”never”
- 設置
View#setOverScrollMode(View.OVER_SCROLL_NEVER)
4.8 通知改進
- 電話通知:從 Android 12 系統開始,新增了新的電話通知樣式 Notification.CallStyle;
- 豐富圖片支持:從 Android 12 系統開始,應用可以在 MessagingStyle() 和 BigPictureStyle() 通知中提供動畫圖片,來豐富應用的通知體驗。此外,應用現在還可以讓用戶在從通知欄回復消息時發送圖片消息;
- 設備解鎖保障:從 Android 12 系統開始,應用可以通過 setAuthenticationRequired(true),要求系統在執行通知的 PendingIntent 前先要求用戶解鎖設備,這對于敏感操作增加了一層安全保障。
4.9 HTTP 深度鏈接解析改進
Android 系統支持通過 Deep Link 或 Android App Link 將深度鏈接與應用行為關聯,實踐中采用的鏈接基于 URI 格式,例如:
從 Android 12 系統開始,系統調整了 HTTP Intent 的默認解析行為。在低版本中,如果 HTTP 鏈接未命中任何 Deep Link / App Link 的匹配規則,那么系統會打開應用選擇對話框;而現在系統會直接通過默認瀏覽器打開鏈接(因為該鏈接本身是一個可訪問的網址)
相關資料:
4.10 全屏模式下的手勢導航改進
全屏模式是指應用最大限度地利用屏幕空間來展示內容,讓用戶獲得最佳體驗,常見場景例如視頻、游戲、演示文稿等。
全屏模式會隱藏狀態欄、導航欄等系統欄,意味著用戶無法輕松與系統欄交互,因此系統定義了以下全屏模式下的系統欄行為,使用 WindowInsetsControllerCompat.setSystemBarsBehavior 設置:
- BEHAVIOR_SHOW_BARS_BY_TOUCH 模式,當用戶點擊屏幕任何位置后,會重新顯示系統欄。這種模式適合于用戶不會與屏幕進行大量互動的場景;
- BEHAVIOR_SHOW_BARS_BY_SWIPE 模式,當用戶從隱藏系統欄的邊緣滑動時,會顯示系統欄。例如從屏幕底部邊緣向上滑動,會重新顯示系統導航欄。這種模式適合于用戶需要與屏幕進行大量交互的場景,例如游戲、閱讀等,使用這種意圖更強的手勢能夠避免系統欄交互與應用交互沖突;
- BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 模式,當用戶從隱藏系統欄的邊緣滑動時,會暫時性地顯示系統欄,并等待一小段時間后自動重新隱藏。系統欄會并不會擠壓應用內容,而是以半透明的方式覆蓋在應用上層。
Android 12 系統整合了現有模式,BEHAVIOR_SHOW_BARS_BY_TOUCH 和 BEHAVIOR_SHOW_BARS_BY_SWIPE 這兩種行為現已棄用,新增了 BEHAVIOR_DEFAULT 行為。
- BEHAVIOR_DEFAULT 模式:當用戶從隱藏系統欄的邊緣滑動時,會顯示系統欄,這一點與 BEHAVIOR_SHOW_BARS_BY_SWIPE 類似。最主要的是,全面屏導航手勢可以直接生效,不管系統導航欄是否可見。換句話說,BEHAVIOR_DEFAULT 行為讓用戶只需滑動一次即可執行手勢導航,而在 Android 11 上則需要滑動兩次。
相關資料:啟用全屏模式
4.11 屏幕尺寸 API 變更
Android 11 系統廢棄了 Display.getSize & Display.getMetrics,并在 Android 12 中繼續廢棄了 Display.getRealSize & Display.getRealMetrics。Android 設備有許多不同的外形(例如大屏設備、平板電腦和可折疊手機),為了針對適配每種設備上獲取屏幕尺寸的需求,系統引入了 WindowMetrics API。
- 平臺 API: WindowMetrics
- 兼容庫 API: WindowManager
4.12 多窗口模式標準化
Android 7 系統引入了多窗口模式,允許同時在屏幕上顯示多個應用,目前一共有 3 種多窗口模式:
- 分屏模式:以左右并排或上下并排顯示兩個應用;
- 畫中畫模式:以疊加的小窗口顯示應用;
- 自由窗口模式:以可移動且可調整顯示尺寸的窗口顯示應用;
從 Android 12 系統開始,多窗口模式將成為大屏設備上的標準行為,大屏設備下 Activity 的 resizeableActivity
配置將被忽略。具體如下:
- Android 7:手機設備支持分屏模式,電視設備支持畫中畫模式,更大尺寸的設備制造商可以選擇啟用自由窗口模式。開發者可以設置 android:resizeableActivity=”false” 禁用多窗口模式,確保 Activity 始終以獨占屏幕的方式顯示;
- Android 8:手機設備也支持畫中畫模式;
- Android 12:在小屏設備(sw < 600dp)設備中,系統根據 resizeableActivity 配置確定該 Activity 是否啟用多窗口模式,在大屏設備中,系統會忽略 resizeableActivity 配置,為所有 Activity 啟用多窗口模式。在此之前,用戶要操作的 Activity 不支持多窗口的話,用戶只能先退出窗口,再回來,體驗就中斷了。
可以看出,這次改動 Google 是希望大屏設備下的多窗口模式成為標準行為,實現多窗口模式下的體驗閉環。
4.13 延遲展示前臺服務通知
前臺服務(startForegroundService 啟動的服務)會顯示一個系統通知,以便讓用戶應用正在執行任務并且消耗系統資源,即使該應用已經退出到后臺。從 Android 12 系統開始,前臺服務通知會延遲 10 s 顯示,除非一些需要立即顯示通知的服務。
以下是立即顯示通知的情況:
- 1、前臺服務通知調用
NotificationCompat.Builder#setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE)
修改了顯示行為; - 2、前臺服務通知調用
NotificationCompat.Builder#addAction()
配置了可操作按鈕; - 3、前臺服務通知調用
NotificationCompat.Builder#setCategory()
配置為 CATEGORY_CALL、CATEGORY_NAVIGATION 或 CATEGORY_TRANSPORT; - 4、前臺服務的 foregroundServiceType 取值 mediaPlayback、mediaProjection 或 phoneCall。
可以看出,這次改動 Google 是希望簡化短期運行的前臺服務的用戶感知,既然服務很快就停止了,就沒有必要用通知讓用戶注意到。
相關資料:
- 前臺服務 —— 官方文檔
4.14 activity 生命周期改進
從 Android 12 開始,系統修改了 Activity Task 根 Activity 在處理 ”返回鍵“ 時的默認行為。在舊版本中,返回鍵會執行 finish Activity,而從 Android 12 開始會將 Task 任務棧切換到后臺。此后,用戶返回應用將執行熱啟動,應用的熱啟動簡單得多,系統的工作只是將 Activity 恢復到前臺。
Activity.java
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
FragmentManager fragmentManager = mFragments.getFragmentManager();
if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
return;
}
if (!isTaskRoot()) {
// If the activity is not the root of the task, allow finish to proceed normally.
finishAfterTransition();
return;
}
// 以上 Android 12 與舊版本相同,差別在下面這句:
try {
// Android 11
// Inform activity task manager that the activity received a back press
// while at the root of the task. This call allows ActivityTaskManager
// to intercept or defer finishing.
ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
new IRequestFinishCallback.Stub() {
public void requestFinish() {
mHandler.post(() -> finishAfterTransition());
}
});
// Android 12
// Inform activity task manager that the activity received a back press while at the
// root of the task. This call allows ActivityTaskManager to intercept or move the task
// to the back.
ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken,
new RequestFinishCallback(new WeakReference<>(this)));
} catch (RemoteException e) {
finishAfterTransition();
}
}
可以看出,這次的改動 Google 希望通過 ”返回鍵“ 退出的應用,能夠更快地從熱啟動恢復應用,而不是從冷啟動或溫啟動重啟應用。
4.15 Surface 幀率切換改進
Android 11 系統引入了一個 Surface 幀率切換 API setFrameRate()
,但這個 API 并不總是會生效。由于不支持無縫切換幀率的屏幕在切換幀率時會有一兩秒的黑屏,所以系統會對這一行為做攔截。如果屏幕不支持無縫切換,即使應用調用 setFrameRate() 依然會繼續使用原來的幀率。
Android 12 系統引入了強制切換幀率的 API,這對于長視頻內容的幀率切換更有優勢,因為合適幀率帶來的體驗提升已經超過了不支持無縫切換帶來的體驗損失。
// 檢查是否支持無縫切換幀率
val refreshRates = this.display?.mode?.alternativeRefreshRates
val willBeSeamless = Arrays.asList<FloatArray>(refreshRates).contains(newRefreshRate)
// 切換幀率,即使屏幕不支持無縫過渡
surface.setFrameRate(newRefreshRate, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS)
- CHANGE_FRAME_RATE_ALWAYS:總是切換幀率,即使屏幕不支持無縫過渡;
- CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:僅在屏幕支持無縫過渡時切換幀率(舊版本行為)。
5. 安全和隱私設置(所有應用)
5.1 隱私信息中心(新功能)
Android 12 系統在系統設置中引入了隱私信息中心功能,可以讓用戶更好地了解應用正在訪問數據的行為。隱私信息中心以一個時間軸的方式顯示過去時間內所有應用對于麥克風、攝像頭或位置等敏感信息的訪問情況。
另外,系統定義了一個新的 Intent 操作 ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD,可以從隱私信息中心中向用戶解釋為什么應用需要訪問這些隱私信息。要支持此功能,應用需要定義一個 Activity 并聲明 IntentFilter 關聯到此 Action 上,例如:
<!-- android:exported required if you target Android 12. -->
<activity android:name=".DataAccessRationaleActivity"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE"
android:exported="true">
<!-- VIEW_PERMISSION_USAGE shows a selectable information icon on
your app permission's page in system settings.
VIEW_PERMISSION_USAGE_FOR_PERIOD shows a selectable information
icon on the Privacy Dashboard screen. -->
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD" />
<category android:name="android.intent.category.DEFAULT" />
...
</intent-filter>
</activity>
相關資料:解釋對比較敏感信息的訪問權限
5.2 支持只授予粗略位置權限(新功能)
Android 系統支持兩個精度級別的位置信息,并且分別對應一個權限。雖然有兩個精度級別的權限,但是因為它們處于同一個權限組中,所以應用只要請求授予其中一個權限,另一個權限就自動授予了。
- 粗略位置:精確到 3 平方公里的位置值,請求 ACCESS_COARSE_LOCATION 權限可以獲得;
- 精確位置:精確到 50 米以內的位置值,請求 ACCESS_FINE_LOCATION 權限可以獲得。
然而,從 Android 12 系統開始,這一規則不再成立了。從 Android 12 系統開始,用戶可以只授予應用模糊位置 ACCESS_COARSE_LOCATION 權限,即使應用請求的是精確位置 ACCESS_FINE_LOCATION 權限。
舉個例子,我們通過以下代碼請求 ACCESS_FINE_LOCATION 權限,在 Android 12 系統上的權限請求彈窗會給用戶兩個選項:Precise 精確位置
和 Approximate 粗略位置
。如果用戶選擇授予粗略位置,那么最終應用獲得的權限反而是 ACCESS_COARSE_LOCATION 權限,而不是一開始請求的 ACCESS_FINE_LOCATION 權限,并且應用也只能獲取粗略位置信息。
val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGrant ->
Log.i("權限","isGrant: $isGrant")
if (PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)) {
Log.i("權限", "ACCESS_COARSE_LOCATION is Grant")
}
if (PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
Log.i("權限", "ACCESS_FINE_LOCATION is Grant")
}
}
findViewById<View>(R.id.tv).setOnClickListener {
locationPermissionRequest.launch( Manifest.permission.ACCESS_FINE_LOCATION)
}
輸出日志:
I/權限: isGrant: false
I/權限: ACCESS_COARSE_LOCATION is Grant
那么從 Android 12 開始,你在請求位置權限時就要注意以下問題,稍不注意就出現兼容問題了:
-
請求位置權限時要同時請求 ACCESS_FINE_LOCATION 權限和 ACCESS_COARSE_LOCATION 權限,如果應用只請求 ACCESS_FINE_LOCATION 權限,系統會直接忽略該請求。如果應用以 Android 12 或更高版本為目標版本,系統會在 logcat 中提示錯誤:
ACCESS_FINE_LOCATION must be requested with ACCESS_COARSE_LOCATION.
提示:我在 Pixel 模擬器上實測并沒有出現文檔描述的 ”忽略請求“ 和 ”報錯提示“,不過最好還是按照官方文檔處理吧。
即使用戶已經授予了精確位置權限,用戶依然可以進入系統設置中直接修改到粗略位置權限,修改后系統會自動殺死進程。
為了更好地尊重用戶隱私,盡量只請求 ACCESS_COARSE_LOCATION 權限,因為粗略位置信息已經能滿足大多數應用場景。僅請求 ACCESS_COARSE_LOCATION 權限時,授權彈窗只有一個選項:
- 如果你的應用場景確實需要請求 ACCESS_FINE_LOCATION 權限,那么你可以再次同時請求 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 權限。由于之前用戶已經授予過粗略位置權限,這次的系統彈窗會變成詢問是否升級到精確位置權限:
- 最后一個問題,怎么確定應用場景時需要精確位置還是粗略位置呢?其實并不是依靠純主觀判斷,這塊是有行業標準的。在我們之前討論的 還在見招拆招?先看懂 APP 個人信息保護治理機制 中,提到過一個標準 團體標準《APP 收集使用個人信息最小必要評估規范》共 17 項。其中對于位置信息的獲取有明確的標準和案例,例如:
有小伙伴發現部分 Android 12 系統的手機上,請求位置權限的行為跟低版本沒有區別,這是因為有些廠商系統還沒有完全實現這個功能。以小米 MIUI 13.0.5.0 為例,模糊定位功能需要打開
系統設置 - 隱私保護實驗室 - 模糊定位
選項才能啟用。而且我在該系統上實測后,發現即使用戶只授予 ACCESS_COARSE_LOCATION 權限,另一個 ACCESS_FINE_LOCATION 權限也會同時授予,這個就離譜了,怪不得還在實驗室。
可以看出,這次的改動是 Google 希望引導開發者盡量使用低精度的位置信息,提高對用戶隱私的保護度。
相關資料:
- 請求位置權限 —— 官方文檔
- 請求應用權限 —— 官方文檔
- 解釋對比較敏感信息的訪問權限 —— 官方文檔
5.3 麥克風和攝像頭切換開關(新功能)
從 Android 12 系統開始,用戶可以通過一個全局切換開關,停用整臺設備上的攝像頭或麥克風權限。當應用請求攝像頭或麥克風權限時,系統有彈窗提示。這個切換開關在不同廠商系統上的體現不一樣,比如小米系統是放在 手機關機-隱身模式
。
5.4 麥克風和攝像頭指示標示(新功能)
從 Android 12 開始,當應用使用麥克風或相機時,在狀態欄會有圖標標記。
5.5 剪貼板訪問提示(新功能)
在 Android 12 及更高版本中,當某個應用首次調用 getPrimaryClip 以 [從另一個應用訪問剪輯數據](https://developer.android.google.cn/guide/topics/text/copy-paste#Pasting "getPrimaryClip( "從另一個應用訪問剪輯數據")") 時,會彈出一個消息框消息,提示用戶應用存在訪問剪貼板的行為。
5.6 隱藏應用疊加窗口(新功能)
Android 12 系統引入了隱藏 TYPE_APPLICATION_OVERLAY 窗口的功能。聲明 HIDE_OVERLAY_WINDOWS 權限后,應用可以調用 setHideOverlayWindows 指明當應用的窗口可見時隱藏所有可見的 TYPE_APPLICATION_OVERLAY 窗口。例如在顯示敏感頁面(如交易)時,應用可以選擇隱藏其他懸浮窗。
5.7 應用無法關閉系統對話框
為了加強用戶與應用和系統互動時的控制,從 Android 12 系統開始,棄用了 ACTION_CLOSE_SYSTEM_DIALOGS Intent 操作,當應用使用嘗試關閉系統對話框時,除了 一些特殊情況 之外,系統會進行攔截:
以 Android 12 或更高版本為目標版本:系統會拋出 SecurityException;
-
以 Android 11 或更低版本為目標版本:系統不會執行 Intent,并在 logcat 提示:
E ActivityTaskManager Permission Denial: \ android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from \ com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, \ dropping broadcast.
5.8 屏蔽不信任的觸摸事件
屏幕應用是用戶與應用交互的主要方式,為了提高觸摸交互的直觀和安全性,Android 12 系統會屏蔽從不同應用的窗口傳遞的事件。因此,這次的改動主要影響 聲明選擇讓觸摸事件穿透窗口的應用,例如聲明了 TYPE_APPLICATION_OVERLAY 和 FLAG_NOT_TOUCHABLE 的懸浮窗口。
詳細分析見相關資料:行為變更 | Android 12 中不受信任的觸摸事件 —— 官方博客文章
6. 性能和電池(所有應用)
6.1 應用待機分區改進
App Standby Buckets 應用待機分區是 Android 9 引入的電池管理功能,系統會對應用的使用新近度和使用頻率對應用進行排序,分別放置在不同的分區中。更活躍的應用會被分配到更高優先級的分區中,而低優先級的分區中應用的作業、鬧鐘或 FCM 會有一定限制。Android 12 系統引入了一個新的分區 —— “受限” 待機分區:
- 活躍:目前正在使用,或者最近剛剛使用;
- 工作集:定期使用;
- 常用:經常使用,但不會每天使用;
- 極少使用:不經常使用;
- 受限:應用消耗大量資源,或表現出不良行為
- 從未使用:已安裝但從未運行過。
舊版本的待機分區只是根據應用的活躍度排序,而現在還有引入資源占用率的維度。可以看出,這次改動是 Google 希望提高對消耗大量系統資源的應用的限制。
提示: 這些限制僅適用于設備使用電池供電的情況;在設備充電期間,系統不會對應用施加這些限制。
相關資料:應用待機分區 —— 官方文檔
7. 總結
關注我,帶你了解更多,我們下次見。
以下變更相對冷門,實用價值較低,暫且按住不表:
- 行為變更 - Target 12 - 用戶體驗 - 大屏設備上的相機預覽改進 & 富感反饋體驗 & AppSearch & 游戲模式 & 近期網址共享(僅限 Pixel)
- 行為變更:Target 12 - WebView 中的現代 SameSite Cookie & 備份和恢復 & 連接性 & 供應商 & 更新后的非 SDK 接口限制
- 行為變更:所有應用 - 安全和隱私設置 - 權限軟件包可見性 & 移除了 BouncyCastle 實現
- 行為變更:所有應用 - 安全和隱私設置 -
- 行為變更:所有應用 - 媒體 & 相機 & 圖形和圖片 & 連接性 & 更新后的非 SDK 接口限制
參考資料
- 新版本系統適配: Android 12 中的兼容性變更 —— 官方文檔
- Android 12 正式發布 | 開發者們的全新舞臺 —— 官方文檔
- 行為變更:所有應用 —— 官方文檔
- 行為變更:以 Android 12 為目標平臺的應用 —— 官方文檔
- 功能和 API 概覽 —— 官方文檔
你的點贊對我意義重大!希望大家可以一起討論技術,找到志同道合的朋友,我們下次見!