前言
上篇文章Android修煉之Pie 適配的搬運工中介紹了Android P的一些行為變更並提供了一些對齊劉海和非SDK介面的適配建議,大部分人還是更加關心非SDK介面的問題,所以本文來說一下如何檢測非SDK介面。
區分SDK介面和非SDK介面
一般來說,SDK介面是指在Android框架軟體包索引中記錄的介面,其對立面自然就叫非SDK介面了。Android 9引入了針對非SDK介面的使用限制,無論是直接使用還是通過反射或JNI間接使用。這意味著,通過反射之類的語義來操作某個類時,不應訪問SDK中未列出的函式或欄位(通常都有@hide
標誌)。
Android 9將非SDK介面都放在了各名單中:
- 白名單:自然就是可以直接使用的SDK介面;
- 淺灰名單:仍可以訪問的非 SDK 函式/欄位;
- 深灰名單:包含我們在開發期間未看到使用,但是我們可能已經忽視的函式/欄位。列入深灰名單的函式/欄位是那些與SDK和淺灰名單中的公開介面緊密相關的函式/欄位。
- 對於目標SDK低於API級別28的應用,允許使用深灰名單介面;
- 對於目標SDK為API 28或更高階別的應用:行為與黑名單相同;
- 對於目標SDK低於API級別28的應用,允許使用深灰名單介面;
- 黑名單:受限,無論目標SDK如何。平臺將表現為似乎介面並不存在。
這些名單都可以在:非SDK介面名單中找到。如果不想編譯AOSP,可以直接下載該頁的tgz
檔案,解壓即可看到對應的txt
檔案。
如何檢測非SDK介面?
檢視logcat日誌
在Android 9的系統中,如你的應用中使用了非SDK介面,系統將會在APP執行時列印日誌;如果訪問了某些“列入灰名單的”非SDK介面,系統還可能顯示Toast;如果訪問了“列入黑名單的”非SDK介面,系統將引發異常。
日誌訊息
在開發過程中,我們可以通過AS的logcat來檢視日誌,否則就是通過adb logcat
了。非SDK介面的日誌會顯示在正在執行的應用的PID下,大概格式是:
Accessing hidden field Landroid/net/wifi/WifiManager;->WIFI_SCAN_AVAILABLE:Ljava/lang/String; (dark greylist, reflection)
複製程式碼
- Accessing hidden:固定格式
- field:可能是method,表示呼叫的是屬性還是方法
- Landroid/net/wifi/WifiManager:表示呼叫的類名
- ->WIFI_SCAN_AVAILABLE:Ljava/lang/String;:表示呼叫的屬性和方法名
- (dark greylist, reflection):前半部分表示該非SDK介面在哪個名單中,後半部分表示被呼叫的方式,比如:reflection(反射)和JNI
這裡給出幾個示例:
@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();
}
}
複製程式碼
他們的日誌如下:
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檢測工具
谷歌提供了一個靜態檢測工具:veridex,可以幫助我們檢測apk中是否使用了非SDK介面。這裡使用veridex-linux.zip
進行測試,解壓後目錄結構如下:
appcompat.sh
就是執行指令碼。
將待檢測的apk檔案拷貝到veridex目錄下,執行以下命令即可:
./appcompat.sh --dex-file=檔名.apk --imprecise
複製程式碼
結果:
這裡顯示的數量明顯比我們示例中的多,這是為什麼?觀察詳細內容,發現veridex把Android自己呼叫的也檢測出來了。如果去掉--imprecise
會檢測不出來黑名單的介面。
我們主要關注黑名單和深灰名單。可以看到,日誌中給出了呼叫非SDK介面的位置,很容易定位。
總結
- 在開發過程中可以通過日誌檢視是否呼叫了非SDK介面;
- 主要注意深灰名單和黑名單的介面呼叫,雖然程式中經常寫有try catch,但還是竟可能找到繞過或者替代的方案;
- 使用veridex分析apk時,最好帶上
--imprecise
引數,可以看到更多詳細的資訊; - 示例程式碼:github.com/zjxstar/And…