注意:本文的範例於 Android 8 (Oreo) 上已無法執行,而 Google 也表明為了改善應用程式的安全與穩定,未來將逐步限制這些非正規的存取方式。
如果您有研究過 Android Source Code,應該會發現其中有許多函式都被標註了 @hide。也就是這些函式在 SDK 中是被隱藏的,一般情形下無法被呼叫使用。但有時我們又想使用這些功能該怎麼辦呢? 在不更動 Android System 的前提下,我們可以透過 Java 的反射機制 (Java Reflection) 來達成。
範例:
我們在 Android Source Code 中的 PackageManager 類別裡發現了一個函式 getPackageSizeInfo,可以用來取得應用程式的磁碟空間使用量,但在 SDK 內卻找不到此函式。
我們先試著用 getMethods 列出 PackageManager 中所有的函式
看一下結果,沒錯,裡面確實有 getPackageSizeInfo 這個函式
調用方式:
首先用 getMethod 透過名稱 "getPackageSizeInfo" 來取得函式,後面的 String.class 與 IPackageStatsObserver.class 是 getPackageSizeInfo 的參數型態(由 Android Source Code 得知)。
接著用 invoke 來實際調用,"xxx.xxx.xxx" 是你想取得磁碟空間資料的應用程式 package name,IPackageStatsObserver 裡面則有一個 onGetStatsCompleted 的 Callback 會返回 PackageStats 資料。
過程中你會發現編譯器並不認識 IPackageStatsObserver 與 PackageStats,
此時我們需要在專案中建立 android.content.pm 這個 package,
並將 Android Source Code\frameworks\base\core\java\android\content\pm 目錄中的 IPackageDataObserver.aidl 與 PackageStats.aidl 複製到其中,剩下的 ADT 會自動處理。
另外,要使用 getPackageSizeInfo 必須於 AndroidManifest.xml 設定下面這個權限:
執行,再看一次結果,確實取得了磁碟使用空間的資料。
總結:
使用 Java 的反射機制確實可以讓我們做到更多事情,但同時也要自負風險,畢竟官方將某些函式隱藏也許有他的理由。
另外,有些函式雖然可以被反射到,但裡面可能做了其他的防護(如限制 Permission 或檢查 PID 之類的),所以也不見得 100% 能被使用。
補充:
若是 Class 本身被隱藏的話,也可以用下面的方式透過名稱取出。
參考資料:
http://www-jo.se/f.pfleger/android-package-size
http://www.blogjava.net/zh-weir/archive/2011/03/26/347063.html
https://android-developers.googleblog.com/2018/02/improving-stability-by-reducing-usage.html
如果您有研究過 Android Source Code,應該會發現其中有許多函式都被標註了 @hide。也就是這些函式在 SDK 中是被隱藏的,一般情形下無法被呼叫使用。但有時我們又想使用這些功能該怎麼辦呢? 在不更動 Android System 的前提下,我們可以透過 Java 的反射機制 (Java Reflection) 來達成。
範例:
我們在 Android Source Code 中的 PackageManager 類別裡發現了一個函式 getPackageSizeInfo,可以用來取得應用程式的磁碟空間使用量,但在 SDK 內卻找不到此函式。
我們先試著用 getMethods 列出 PackageManager 中所有的函式
PackageManager pm = getPackageManager(); Method[] methods = pm.getClass().getMethods(); for (int i = 0; i < methods.length; i++) { Log.d(TAG, methods[i].getName()); }
看一下結果,沒錯,裡面確實有 getPackageSizeInfo 這個函式
調用方式:
Method getPackageSizeInfo = pm.getClass().getMethod( "getPackageSizeInfo", String.class, IPackageStatsObserver.class); getPackageSizeInfo.invoke(pm, "xxx.xxx.xxx", new IPackageStatsObserver.Stub() { @Override public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) throws RemoteException { Log.d(TAG, "codeSize: " + pStats.codeSize + " dataSize: " + pStats.dataSize + " cacheSize: " + pStats.cacheSize); } });
首先用 getMethod 透過名稱 "getPackageSizeInfo" 來取得函式,後面的 String.class 與 IPackageStatsObserver.class 是 getPackageSizeInfo 的參數型態(由 Android Source Code 得知)。
接著用 invoke 來實際調用,"xxx.xxx.xxx" 是你想取得磁碟空間資料的應用程式 package name,IPackageStatsObserver 裡面則有一個 onGetStatsCompleted 的 Callback 會返回 PackageStats 資料。
過程中你會發現編譯器並不認識 IPackageStatsObserver 與 PackageStats,
此時我們需要在專案中建立 android.content.pm 這個 package,
並將 Android Source Code\frameworks\base\core\java\android\content\pm 目錄中的 IPackageDataObserver.aidl 與 PackageStats.aidl 複製到其中,剩下的 ADT 會自動處理。
另外,要使用 getPackageSizeInfo 必須於 AndroidManifest.xml 設定下面這個權限:
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
執行,再看一次結果,確實取得了磁碟使用空間的資料。
總結:
使用 Java 的反射機制確實可以讓我們做到更多事情,但同時也要自負風險,畢竟官方將某些函式隱藏也許有他的理由。
另外,有些函式雖然可以被反射到,但裡面可能做了其他的防護(如限制 Permission 或檢查 PID 之類的),所以也不見得 100% 能被使用。
補充:
若是 Class 本身被隱藏的話,也可以用下面的方式透過名稱取出。
Class hideClass = Class.forName("android.xxx.xxx");
參考資料:
http://www-jo.se/f.pfleger/android-package-size
http://www.blogjava.net/zh-weir/archive/2011/03/26/347063.html
https://android-developers.googleblog.com/2018/02/improving-stability-by-reducing-usage.html
留言
張貼留言