Android 有兩種類型的 API 不能通過 SDK 訪問。一種是在 com.android.internal 包中的 API,稱之為 internal API。另一種是被標(biāo)記為 @hide 屬性的類和方法,這是一組小級別的被隱藏的 API,稱之為 hidden API。
當(dāng)使用 Android SDK 進(jìn)行開發(fā)的時候,應(yīng)用默認(rèn)引用了 android.jar,它位于 SDKDir\platforms\android-X 目錄下(X 代表 API 級別),默認(rèn)移除了所有的被@hide標(biāo)識的方法或者類以及 internal 包下的類。。當(dāng)應(yīng)用在設(shè)備上運(yùn)行時,它會加載 framework.jar。簡單來說,framework.jar 和 android.jar 等同,但是沒有移除 internal API 和 hidden API。Hidden API 之所以被隱藏,是想阻止開發(fā)者使用 SDK 中未完成或不穩(wěn)定的部分。
舉個栗子:這是沒有移除 internal API 的 android.jar,可以看到包里的類是完整的。

比如 AssetManager 的 addAssetPath 方法被 @hide 標(biāo)記,它屬于 hidden API,我們無法直接調(diào)用該方法。
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
synchronized (this) {
int res = addAssetPathNative(path);
makeStringBlocks(mStringBlocks);
return res;
}
}
但是,人的需求是無限的。如果我們非要使用不可用的 API 怎么辦呢?最簡單的就是 Java 的反射,反射 @hiden 的方法或類,修改訪問修飾符,然后就可以搞事情了~~還有一種方法是從設(shè)備中提取,簡單說就是把設(shè)備上的 /system/framework/framework.jar 提取出來,經(jīng)過一系列轉(zhuǎn)換,最終得到完整的 android.jar,具體的步驟可以參考這篇文章:android怎樣調(diào)用@hide和internal API。另外一種方式非常簡單,GitHub 上有一個項目:android-hidden-api,里面提供了眾多版本完整的 android.jar 包,下圖所示。

我們把工程 clone 下來,找到對應(yīng)平臺的 android.jar 包,替換掉 Android SDK 下面的 jar,最好先備份一下原始的 jar,重新編譯工程或者重啟 Studio 就行了。
比如 AssetManager 的 addAssetPath 方法,沒有替換之前是這樣,Studio 提示錯誤,編譯失敗~o(>_<)o ~

但是在使用了完整的 android.jar 后,發(fā)現(xiàn)竟然不會報錯了,代碼可以通過編譯,終于可以愉快地使用想要的方法了 O(∩_∩)O~

這種方式對于個人開發(fā)來說沒有問題,你把 android.jar 替換掉就好了,但是如果面對團(tuán)隊開發(fā),就非常痛苦了 %>_<%,每個人都要替換 SDK 的 android.jar,代價和風(fēng)險可想而知。那么有沒有好的解決辦法,既可以讓工程編譯通過,又能夠免去多人替換 jar 的成本呢?答案是有的。
Studio 默認(rèn)引用的是 SDK 下面的 android.jar,那我們把它的引用改成完整的 jar 的路徑不就行了么?
我們把完整的 android.jar 放在工程 libs 目錄下,也就是平時依賴 jar 的地方,然后在工程 build.gradle 配置的 dependencies 里,以 provided 的方式引用 android.jar。因為每個工程模塊依賴 android.jar 的類型就是 provided,這樣不會把 android.jar 打包到應(yīng)用中,運(yùn)行環(huán)境中存在 framework.jar,應(yīng)用直接就可以使用。
dependencies {
// compile fileTree(include: ['*.jar'], dir: 'libs') 這行一定要去掉,當(dāng)然為 android.jar 換個目錄也行
testCompile 'junit:junit:4.12'
provided files('libs/hidden_api_23.jar')
}
最后還要在工程根目錄的 build.gradle 里面配置當(dāng)前 project,加上下面的代碼就行了。
project('app') { // app是你工程的名字,配置只對當(dāng)前工程有效
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
// 注意修改 jar 包的路徑,替換 app/libs/hidden_api_23.jar,其他部分不要改
// Xbootclasspath/p:是 Java 編譯的尋址優(yōu)先設(shè)置,先找缺省路徑還是全路徑
options.compilerArgs.add('-Xbootclasspath/p:app/libs/hidden_api_23.jar')
}
}
}
現(xiàn)在重新編譯工程,雖然會在代碼中出現(xiàn)錯誤提示,但是編譯打包運(yùn)行都是正常的。_
在開發(fā)中使用隱藏 API 和內(nèi)部 API 是不推薦的做法,但是為了實現(xiàn)一些「黑科技」,這些又是必須的~