引言
距離 Android 14 發布已經有一段時間了,趁著這次機會,了解和熟悉了 Android 14 更新的內容,現在來和大家分享一下,大家喜歡的話可以點個贊多多支持一下,文章的內容按照適配內容的重要程度進行排序。
targetSdk 版本要求
在 Android 14 上面,新增了一個要求,要求新安裝的應用的 targetSdkVersion
需要大于等于 23(即 Android 6.0 及以上),如果小于這個值將無法在 Android 14 的設備上面安裝,此時大家心里可能有疑惑了,谷歌為什么要求那么做呢?我們來看看谷歌的原話是什么
惡意軟件通常會以較舊的 API 級別為目標平臺, 以繞過在較新版本 Android 中引入的安全和隱私保護機制。 例如,有些惡意軟件應用使用 targetSdkVersion 22, 以避免受到 Android 6.0 Marshmallow(API 級別 23)在 2015 年引入的運行時權限模型的約束。 這項 Android 14 變更使惡意軟件更難以規避安全和隱私權方面的改進限制。
-
從上面這段話不難看出來谷歌的用意,其實為了保障用戶的手機安全,如果用戶安裝應用的
targetSdkVersion
版本過低,有一些惡意軟件會利用高系統會兼容舊軟件這一特性(漏洞),故意繞過系統的安全檢查,從而會導致 Android 高版本上面一些安全特性無法生效,沒有了系統的管束,這些惡意軟件可能就會肆意亂來。 -
另外你如果想在 Android 14 系統上面,仍然要安裝
targetSdkVersion
小于 23 的應用,可以通過以下 adb 命令來安裝 apk,這樣就能繞過系統的安裝限制。
adb install --bypass-low-target-sdk-block xxx.apk
?
前臺服務類型要求
- 如果你的應用
targetSdkVersion
升級到了 34(即 Android 14),并且在 Service 中調用了startForeground
方法,那么就需要進行適配了,否則系統會拋出MissingForegroundServiceTypeException
異常,這是因為在 Android 14 上面,要求應用在開啟前臺服務的時候,需要注明這個前臺服務的用途,谷歌給我們列舉了以下幾種用途:
用途 | 說明 | 清單文件權限要求 | 運行時要求 |
---|---|---|---|
攝像頭 | 繼續在后臺訪問相機,例如支持多任務的視頻聊天應用 | FOREGROUND_SERVICE_CAMERA | 請求 CAMERA 運行時權限 |
連接的設備 | 與需要藍牙、NFC、IR、USB 或網絡連接的外部設備進行互動 | FOREGROUND_SERVICE_CONNECTED_DEVICE | 必須至少滿足以下其中一個條件: 在清單中至少聲明以下其中一項權限: CHANGE_NETWORK_STATE CHANGE_WIFI_STATE CHANGE_WIFI_MULTICAST_STATE NFC TRANSMIT_IR 至少請求以下其中一項運行時權限: BLUETOOTH_CONNECT BLUETOOTH_ADVERTISE BLUETOOTH_SCAN UWB_RANGING 調用 UsbManager.requestPermission() ? |
數據同步 | 數據傳輸操作,例如: 數據上傳或下載 備份和恢復操作 導入或導出操作 獲取數據 本地文件處理 通過網絡在設備和云端之間傳輸數據 | FOREGROUND_SERVICE_DATA_SYNC | 無 |
健康 | 為健身類別的應用(例如鍛煉追蹤器)提供支持的所有長時間運行的用例 | FOREGROUND_SERVICE_HEALTH | 必須至少滿足以下其中一個條件: 在清單中聲明 HIGH_SAMPLING_RATE_SENSORS 權限。至少請求以下其中一項運行時權限: BODY_SENSORS ACTIVITY_RECOGNITION |
位置 | 需要位置信息使用權的長時間運行的用例, 例如導航和位置信息分享 | FOREGROUND_SERVICE_LOCATION | 至少請求以下其中一項運行時權限:ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION |
媒體 | 在后臺繼續播放音頻或視頻。 在 Android TV 上支持數字視頻錄制 (DVR) 功能。 | FOREGROUND_SERVICE_MEDIA_PLAYBACK | |
媒體投影 | 使用 MediaProjection API 將內容投影到非主要顯示屏或外部設備。這些內容不必全都為媒體內容。不包括 Cast SDK | FOREGROUND_SERVICE_MEDIA_PROJECTION | 調用 createScreenCaptureIntent() 方法。 無 |
麥克風 | 在后臺繼續捕獲麥克風內容,例如錄音器或通信應用 | FOREGROUND_SERVICE_MICROPHONE | 請求 RECORD_AUDIO 運行時權限 |
打電話 | 使用 ConnectionService API 繼續當前通話 | FOREGROUND_SERVICE_PHONE_CALL | 在清單文件中聲明 MANAGE_OWN_CALLS 權限。 |
消息服務 | 將短信從一臺設備轉移到另一臺設備。在用戶切換設備時,幫助確保用戶消息任務的連續性 | FOREGROUND_SERVICE_REMOTE_MESSAGING | 無 |
短期服務 | 快速完成不可中斷或推遲的關鍵工作。 這種類型有一些獨特的特征: 只能持續運行一小段時間(大約 3 分鐘)。 不支持粘性前臺服務。 無法啟動其他前臺服務。 不需要類型專用權限,不過它仍需要 FOREGROUND_SERVICE 權限。正在運行的前臺服務不能更改為 shortService 類型或從該類型更改為其他類型。 | 無 | 無 |
特殊用途 | 涵蓋其他前臺服務類型未涵蓋的所有有效前臺服務用例。 除了聲明 FOREGROUND_SERVICE_TYPE_SPECIAL_USE 前臺服務類型之外,開發者還應在清單中聲明用例。為此,他們會在 `` 元素內指定 <property> 元素。當您在 Google Play 管理中心內提交應用時,我們會審核這些值和相應的用例。 | FOREGROUND_SERVICE_SPECIAL_USE | 無 |
系統豁免 | 為系統應用和特定系統集成預留, 使其能繼續使用前臺服務。 如需使用此類型,應用必須至少滿足以下條件之一: 設備處于演示模式狀態 應用是設備所有者 應用是性能分析器所有者 屬于具有 ROLE_EMERGENCY 角色的安全應用屬于設備管理應用 否則,聲明此類型會導致系統拋出 ForegroundServiceTypeNotAllowedException 。 | FOREGROUND_SERVICE_SYSTEM_EXEMPTED | 無 |
- 介紹完這幾種前臺服務類型,接下來介紹如何適配它,適配前臺服務類型的特性方式具體有兩種方式,一種是注冊清單屬性,另外一種是代碼動態注冊
<serviceandroid:name=".XxxService"android:foregroundServiceType="dataSync"android:exported="false">
</service>
startForeground(xxx, xxx, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
- 另外附上前臺服務類型對應的適配屬性
用途 | 清單文件屬性值 | Java 常量值 |
---|---|---|
攝像頭 | camera | ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA |
連接的設備 | connectedDevice | ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE |
數據同步 | dataSync | ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC |
健康 | health | ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH |
位置 | location | ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION |
媒體 | mediaPlayback | ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK |
媒體投影 | mediaProjection | ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION |
麥克風 | microphone | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE |
打電話 | phoneCall | ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL |
消息服務 | remoteMessaging | ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING |
短期服務 | shortService | ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE |
特殊用途 | specialUse | ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE |
系統豁免 | systemExempted | ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED |
圖片和視頻的部分訪問權限
-
谷歌在 API 33(Android 13)上面引入了
READ_MEDIA_IMAGES
和READ_MEDIA_VIDEO
這兩個權限,目前針對這兩個權限在 Android 14 上面有新的變動,具體的變動點就是新增了READ_MEDIA_VISUAL_USER_SELECTED
權限,那么這個權限的作用是什么呢?我們都知道READ_MEDIA_IMAGES
和READ_MEDIA_VIDEO
是申請圖片和視頻權限的,但是這樣會有一個問題,當第三方應用申請到權限后,就擁有了手機相冊中所有照片和視頻的訪問權限,這是十分危險的,也是非常不可控的,因為用戶也無法知道第三方應用會干什么,所以谷歌在 API 34(Android 14)引入了這個權限,這樣用戶擁有了更多的選擇,可以將相冊中所有的圖片和視頻授予給第三方應用,也可以將部分的圖片和視頻給第三方應用。 -
講完了這個特性的來龍去脈,那么接下來講講這個權限如何適配,如果你的應用申請了
READ_MEDIA_IMAGES
或者READ_MEDIA_VIDEO
權限,并且 targetSdkVersion 大于等于 33(Android 13),那么需要在申請權限時攜帶上READ_MEDIA_VISUAL_USER_SELECTED
權限方能正常申請,如果不攜帶上READ_MEDIA_VISUAL_USER_SELECTED
權限就申請READ_MEDIA_IMAGES
或者READ_MEDIA_VIDEO
權限,會彈出權限詢問對話框,但是如果用戶是選擇全部授予,那么READ_MEDIA_IMAGES
或者READ_MEDIA_VIDEO
權限狀態是已授予的狀態,如果用戶是選擇部分授予,那么READ_MEDIA_IMAGES
或者READ_MEDIA_VIDEO
權限狀態是已拒絕的狀態,假設此時有攜帶了READ_MEDIA_VISUAL_USER_SELECTED
權限的情況下,那么READ_MEDIA_VISUAL_USER_SELECTED
權限是已授予的狀態。 -
看到這里,腦洞大的同學可能有想法了,那我不申請
READ_MEDIA_IMAGES
或者READ_MEDIA_VIDEO
權限,我就只申請READ_MEDIA_VISUAL_USER_SELECTED
權限行不行啊?答案也是不行的,我替大家試驗過了,這個權限申請會在不會詢問用戶的情況下,被系統直接拒絕掉。 -
另外需要的一點是
READ_MEDIA_VISUAL_USER_SELECTED
屬于危險權限,除了在運行時動態申請外,還需要在清單文件中進行注冊。
registerReceiver 需要指定導出行為
-
谷歌在 Android 12 (API 31)新增了四大組件需要指定
android:exported
屬性的特性,這次在 Android 13 上面做了一些變動,因為谷歌之前只考慮到靜態注冊四大組件的情況,但是遺漏了一種情況,BroadcastReceiver 不僅可以靜態注冊,還可以動態注冊,動態注冊的廣播不需要額外在AndroidManifest.xml
中再進行靜態注冊,所以這次谷歌將這個規則漏洞補上了,并且要求開發者在動態注冊廣播的時候,能夠指定 BroadcastReceiver 是否能支持導出,由此來保護應用免受安全漏洞的影響。 -
到此,大家心中可能有一個疑惑,這里的支持導出是什么意思?有產生什么作用?可以先看一下谷歌官方的原話
為了幫助提高運行時接收器的安全性,Android 13 允許您指定您應用中的特定廣播接收器是否應被導出以及是否對設備上的其他應用可見。 如果導出廣播接收器,其他應用將可以向您的應用發送不受保護的廣播。 此導出配置在以 Android 13 或更高版本為目標平臺的應用中可用,有助于防止一個主要的應用漏洞來源。 在以前的 Android 版本中,設備上的任何應用都可以向動態注冊的接收器發送不受保護的廣播,除非該接收器受簽名權限的保護。
?
-
谷歌的解釋很明了,如果廣播支持導出,那么其他應用可以通過發送這個廣播觸發我們應用的邏輯,這可能會發生程序安全漏洞的問題。
-
那么該如何適配這一特性呢?谷歌官方提供了一個
registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags)
API,flags
參數傳入Context.RECEIVER_EXPORTED
(支持導出) 或Context.RECEIVER_NOT_EXPORTED
(不支持導出),具體的代碼適配代碼如下:
String action = "xxxxxx";
IntentFilter filter = new IntentFilter(action);
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {context.registerReceiver(new LocaleChangeReceiver(), filter, Context.RECEIVER_EXPORTED);
} else {context.registerReceiver(new LocaleChangeReceiver(), filter);
}
- 還有一種情況,不需要指定
flag
參數,就是當要注冊的廣播 action 隸屬系統的 action 時候,這個時候可以不需要指定導出行為。
更安全的動態代碼加載
- 如果我們應用有動態加載代碼的需求,并且此時
targetSdk
升級到了 API 34(即 Android 14),那么需要注意一個點,動態加載的文件(Jar、Dex、Apk 格式)需要設置成可讀的,具體案例的代碼如下:
File jar = new File("xxxx.jar");
try (FileOutputStream os = new FileOutputStream(jar)) {jar.setReadOnly();
} catch (IOException e) { ... }
PathClassLoader cl = new PathClassLoader(jar, parentClassLoader);
- 至于谷歌這樣做的原因,我覺得十分簡單,是為了程序的安全,防止有人搶先在動態加載之前先把動態文件替換了,那么會導致執行到一些惡意的代碼,間接導致應用被入侵或者篡改。
-
另外需要注意的一個點的是,如果你的應用
targetSdk
大于等于 API 34(即 Android 14),如果不去適配這一特性,那么運行在 Android 14 的手機上面系統會拋出異常。
屏幕截圖檢測
Android 14 新增引入了屏幕截圖檢測的 API,方便開發者更好地檢測到用戶的操作,具體的使用案例如下:
在清單文件中靜態注冊權限
<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
創建監聽器對象
final Activity.ScreenCaptureCallback screenCaptureCallback = new Activity.ScreenCaptureCallback() {@Overridepublic void onScreenCaptured() {// 監聽到截圖了}
};
在合適的時機注冊監聽
public final class XxxActivity extends Activity {@Overrideprotected void onStart() {super.onStart();registerScreenCaptureCallback(executor, screenCaptureCallback);}
}
在合適的時機取消注冊監聽
public final class XxxActivity extends Activity {@Overrideprotected void onStop() {super.onStop();unregisterScreenCaptureCallback(screenCaptureCallback);}
}
-
需要注意的是,如果使用的是 adb 進行的截圖,并不會觸發
onScreenCaptured
監聽方法。 -
如果不想你的應用能被系統截圖,可以考慮給當前的 Window 窗口加上
WindowManager.LayoutParams.FLAG_SECURE
標記位。 -
最后表達一下我對這個 API 看法,這個 API 設計得不是很好,比如應用想知道用戶是否截圖了,應用可能需要知道的是,截圖文件的存放路徑,但是
onScreenCaptured
是一個空參函數,也就意味著沒有攜帶任何參數,如果要實現獲取截圖文件存放路徑的需求,可能還需要沿用之前的老方式,即使用ContentObserver
監聽媒體數據庫的變化,然后從幾個維度(文件時間維度、文件路徑維度、圖片尺寸維度)判斷新增的圖片是否為用戶的截圖,這種實現的方式相對是比較麻煩的,但是也無發現更好的實現方式。