Android Q 適配指南
官方文檔:
https://developer.android.com/about/versions/10
在Android 10開始版本中,官方的改動較大,相應的開發者適配成本還是很高的。
這里按照2019.11.11 google android q workshop流程,大概說明一下Android Q適配需要注意的內容。雖然是大概介紹,但應該是目前最全的適配攻略了...
- 非SDK 接口
- 設備ID
- 外部存儲
- 權限
- 新功能——夜間模式
一、非SDK接口
官方文檔:針對非 SDK 接口的限制
官方從 Android 9(API 級別 28)開始,對應用使用的非 SDK 接口實施了限制。
如果你的APP通過引用非 SDK 接口或嘗試使用反射或 JNI 來獲取句柄,這些限制就會起作用。官方給出的解釋是為了提升用戶體驗、降低應用崩潰風險。
1.1、非SDK接口檢測工具
官方給出了一個檢測工具,下載地址:veridex
veridex使用方法:
appcompat.sh --dex-file=apk.apk

1.2、blacklist、greylist、greylist-max-o、greylist-max-p含義
以上截圖中,blacklist、greylist、greylist-max-o、greylist-max-p含義如下:
- blacklist 黑名單:禁止使用的非SDK接口,運行時直接Crash(因此必須解決)
- greylist 灰名單:即當前版本仍能使用的非SDK接口,但在下一版本中可能變成被限制的非SDK接口
- greylist-max-o: 在targetSDK<=O中能使用,但是在targetSDK>=P中被禁止使用的非SDK接口
- greylist-max-p: 在targetSDK<=P中能使用,但是在targetSDK>=Q中被禁止使用的非SDK接口
如果覺得我沒有說清楚,可以看以下 2019.11.11 google android q workshop PPT 截圖



1.3、Android Q 加固 與 熱修復
關於加固與熱修復,官方也提供了相應的API
-
加固

-
熱修復

注:
未適配Android Q的應用,若使用了blacklist 相關接口,在Android Q系統上打開時,會直接Crash!
未適配Android Q的應用,若使用了blacklist 相關接口,在Android Q系統上打開時,會直接Crash!
未適配Android Q的應用,若使用了blacklist 相關接口,在Android Q系統上打開時,會直接Crash!
二、設備ID
從Android 10開始已經無法完全標識一個設備,曾經用mac地址、IMEI等設備信息標識設備的方法,從Android 10開始統統失效。而且無論你的APP是否是配過Android 10。
2.1、IMEI等設備信息
從Android10開始普通應用不再允許請求權限android.permission.READ_PHONE_STATE。
而且,無論你的App是否適配過Android Q(既targetSdkVersion是否大於等於29),均無法再獲取到設備IMEI等設備信息。
受影響的API如下:
Build.getSerial();
TelephonyManager.getImei();
TelephonyManager.getMeid()
TelephonyManager.getDeviceId();
TelephonyManager.getSubscriberId();
TelephonyManager.getSimSerialNumber();

targetSdkVersion<29 的應用,其在獲取設備ID時,會直接返回null
targetSdkVersion>=29 的應用,其在獲取設備ID時,會直接跑出異常SecurityException

如果您的App希望在Android 10以下的設備中仍然獲取設備IMEI等信息,可按以下方式進行適配:

2.2、Mac地址隨機分配
從Android10開始,默認情況下,在搭載 Android 10 或更高版本的設備上,系統會傳輸隨機分配的 MAC 地址。(既從Android 10開始,普通應用已經無法獲取設備的真正mac地址,標識設備已經無法使用mac地址)
2.3、如何標識設備唯一性?


Google給出的解決方案是:如果您的應用有 追蹤非登錄用戶重裝 的需求,可用ANDROID_ID來標識設備。
- ANDROID_ID的生成規則為:
簽名+設備信息+設備用戶 - ANDROID_ID重置規則:
設備恢復出廠設置時,ANDROID_ID將被重置
String androidId = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
也就是從Android 10開始已經無法完全標識一個設備,曾經用mac地址、IMEI等設備信息標識設備的方法,從Android 10開始統統失效。而且無論你的APP是否是配過Android 10。
求高手留言解答:
在Android 10系統上,目前本人尚未找到標識設備唯一性的辦法,如果大家有辦法希望留言告知!!!
三、外部存儲
官方文檔:
外部存儲訪問權限范圍限定為應用文件和媒體
Manage scoped external storage access

為解決截圖中的問題,從Android Q開始,官方對外部存儲進行了一定的限制。
To give users more control over their files and to limit file clutter, apps targeting Android 10 (API level 29) and higher are given scoped access into an external storage device, or scoped storage, by default. Such apps can see only their app-specific directory—accessed using Context.getExternalFilesDir()—and specific types of media. It's a best practice to use scoped storage unless your app needs access to a file that doesn't reside in either the app-specific directory or the MediaStore.
為了使用戶更改的管理Sdcard中的文件,解決文件混亂的問題。從Android 10開始(API level 29),Android將對外部存儲進行一定的限制。
默認情況下,對於外部存儲,App只能通過Context.getExternalFilesDir()訪問自己的特定文件目錄;
以及系統特定的文件類型目錄(例:照片、屏幕快照、視頻 等)。

3.1、APP專屬路徑
對於App專屬 內部存儲路徑與外部存儲路徑的訪問,將不再需要 READ_EXTERNAL_STORAGE 與 WRITE_EXTERNAL_STORAGE 權限:
- 內部存儲路徑
/data/data/<包名>/ - 外部存儲路徑
/storage/Android/data/<包名>/
Android Q 存儲目錄的獲取,可參考以下文檔:Android Sdcard存儲目錄
Android Q 存儲目錄的獲取,可參考以下文檔:Android Sdcard存儲目錄
Android Q 存儲目錄的獲取,可參考以下文檔:Android Sdcard存儲目錄
3.2、手機共享路徑
讀取其他APP創建的共享文件,例:相冊、屏幕快照 等,則需要申請READ_EXTERNAL_STORAGE權限:
- 圖片:Photos、Screenshots 使用MediaStore.Images API訪問
- 視頻 使用MediaStore.Video API訪問
- 音頻 使用 MediaStore.Audio API訪問
3.3、Downloads文件夾
讀取手機的Downloads文件夾,不需要任何權限,需要使用API Storage Access Framework
四、權限相關
主要包括:
- 在后台運行時訪問設備位置信息
- 從后台啟動 Activity 的限制
- 屏幕錄制
- 攝像頭和麥克風
- 剪切板隱私限制
4.1、在后台運行時訪問設備位置信息
Android 10 引入了 ACCESS_BACKGROUND_LOCATION 權限。
若應用在后台運行時,訪問手機位置,需要動態申請該權限,用戶則可以選擇拒絕。
官方給出的數據,大部分用戶對位置信息是比較敏感的。而且大部分用戶是不允許應用在后台使用位置信息的。



4.2、從后台啟動 Activity 的限制
4.3、屏幕錄制
不需要手動申請權限,但官方 API內部會向用戶彈窗申請權限

4.4、攝像頭和麥克風
Android 9 攝像頭和麥克風 后台權限已經移除了
4.5、活動探知——新增權限

4.6、剪切板隱私限制
從Android P開始,除非你的應用是默認輸入法,否則它無法訪問用戶的剪貼板數據;但向剪切板寫入數據不影響。
五、新功能——夜間模式
關於夜間模式,感興趣的同學,可以查看我的另一篇文檔:
六、參考文章
========== THE END ==========

