上篇文章Android修煉之Pie 適配的搬運工中介紹了Android P的一些行爲變動並提供了一些對齊劉海和非SDK接口的適配建議,大部分人仍是更加關心非SDK接口的問題,因此本文來講一下如何檢測非SDK接口。java
通常來講,SDK接口是指在Android框架軟件包索引中記錄的接口,其對立面天然就叫非SDK接口了。Android 9引入了針對非SDK接口的使用限制,不管是直接使用仍是經過反射或JNI間接使用。這意味着,經過反射之類的語義來操做某個類時,不該訪問SDK中未列出的函數或字段(一般都有@hide
標誌)。linux
Android 9將非SDK接口都放在了各名單中:android
這些名單均可以在:非SDK接口名單中找到。若是不想編譯AOSP,能夠直接下載該頁的tgz
文件,解壓便可看到對應的txt
文件。git
在Android 9的系統中,如你的應用中使用了非SDK接口,系統將會在APP運行時打印日誌;若是訪問了某些「列入灰名單的」非SDK接口,系統還可能顯示Toast;若是訪問了「列入黑名單的」非SDK接口,系統將引起異常。github
日誌消息微信
在開發過程當中,咱們能夠經過AS的logcat來查看日誌,不然就是經過adb logcat
了。非SDK接口的日誌會顯示在正在運行的應用的PID下,大概格式是:app
Accessing hidden field Landroid/net/wifi/WifiManager;->WIFI_SCAN_AVAILABLE:Ljava/lang/String; (dark greylist, reflection)
複製代碼
這裏給出幾個示例:框架
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
Field field = null;
try {
field = wifiManager.getClass().getDeclaredField("WIFI_SCAN_AVAILABLE");
Log.d("ThirdActivity", (String) field.get(wifiManager));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 直接在淺灰名單中找的接口
public void testLightGreyList(View view) {
ReflectUtils.getMethod(TelephonyManager.class, "isMultiSimEnabled");
}
// 直接在深灰名單中找的接口
@TargetApi(Build.VERSION_CODES.M)
public void testDarkGreyList(View view) {
ReflectUtils.getField(CarrierConfigManager.class, "KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL");
}
// 直接在黑名單中找的接口
public void testBlackList(View view) {
try {
ReflectUtils.getMethod(ReflectUtils.getClass("android.net.util.IpUtils"), "ipChecksum", ByteBuffer.class, int.class);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
複製代碼
他們的日誌以下:ide
Accessing hidden field Landroid/net/wifi/WifiManager;->WIFI_SCAN_AVAILABLE:Ljava/lang/String; (dark greylist, reflection)
Accessing hidden method Landroid/telephony/TelephonyManager;->isMultiSimEnabled()Z (light greylist, reflection)
Accessing hidden field Landroid/telephony/CarrierConfigManager;->KEY_ALWAYS_PLAY_REMOTE_HOLD_TONE_BOOL:Ljava/lang/String; (dark greylist, reflection)
Accessing hidden method Landroid/net/util/IpUtils;->ipChecksum(Ljava/nio/ByteBuffer;I)S (blacklist, reflection)
複製代碼
注意:在debug的時候,只有觸發了對應的調用邏輯,纔會打印日誌,而不是靜態檢測。因此新項目中,咱們在開發過程當中就要注意非SDK接口的調用。函數
Toast和Dialog提示
可能由於個人模擬器用的是Android P的正式版,去除了Toast提示。因此,在測試的時候並無看到相關的Toast提示。在預覽版中應該有Toast提示功能。固然,也多是我沒有觸發「某些灰名單中的非SDK接口」(官方文檔原話)的緣由。
不過,當我把TargetSDK改成27時,出現過這種對話框提示:
引起異常
官方說明:
經測試,只有深灰名單和黑名單中的非SDK接口會引起異常和錯誤。
注意:當TargetSDK>=28時,深灰名單和黑名單都會引起異常;而當TargetSDK<28時,只有黑名單會引起異常。示例結果以下:
谷歌提供了一個靜態檢測工具:veridex,能夠幫助咱們檢測apk中是否使用了非SDK接口。這裏使用veridex-linux.zip
進行測試,解壓後目錄結構以下:
appcompat.sh
就是運行腳本。
將待檢測的apk文件拷貝到veridex目錄下,運行如下命令便可:
./appcompat.sh --dex-file=文件名.apk --imprecise
複製代碼
結果:
這裏顯示的數量明顯比咱們示例中的多,這是爲何?觀察詳細內容,發現veridex把Android本身調用的也檢測出來了。若是去掉--imprecise
會檢測不出來黑名單的接口。
咱們主要關注黑名單和深灰名單。能夠看到,日誌中給出了調用非SDK接口的位置,很容易定位。
--imprecise
參數,能夠看到更多詳細的信息;