Android 有两种类型的 API 不能通过 SDK 访问。一种是在 com.android.internal 包中的 API,称之为 internal API。另一种是被标记为 @hide
属性的类和方法,这是一组小级别的被隐藏的 API,称之为 hidden API。
当使用 Android SDK 进行开发的时候,应用默认引用了 android.jar,它位于 SDKDir\platforms\android-X 目录下(X 代表 API 级别),默认移除了所有的被@hide
标识的方法或者类以及 internal 包下的类。。当应用在设备上运行时,它会加载 framework.jar。简单来说,framework.jar 和 android.jar 等同,但是没有移除 internal API 和 hidden API。Hidden API 之所以被隐藏,是想阻止开发者使用 SDK 中未完成或不稳定的部分。
举个栗子:这是没有移除 internal API 的 android.jar,可以看到包里的类是完整的。
比如 AssetManager 的 addAssetPath 方法被 @hide
标记,它属于 hidden API,我们无法直接调用该方法。
/**
* 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
的方法或类,修改访问修饰符,然后就可以搞事情了~~还有一种方法是从设备中提取,简单说就是把设备上的 /system/framework/framework.jar 提取出来,经过一系列转换,最终得到完整的 android.jar,具体的步骤可以参考这篇文章:android怎样调用@hide和internal API。另外一种方式非常简单,GitHub 上有一个项目:android-hidden-api,里面提供了众多版本完整的 android.jar 包,下图所示。
我们把工程 clone 下来,找到对应平台的 android.jar 包,替换掉 Android SDK 下面的 jar,最好先备份一下原始的 jar,重新编译工程或者重启 Studio 就行了。
比如 AssetManager 的 addAssetPath 方法,没有替换之前是这样,Studio 提示错误,编译失败~o(>_<)o ~
但是在使用了完整的 android.jar 后,发现竟然不会报错了,代码可以通过编译,终于可以愉快地使用想要的方法了 O(∩_∩)O~
这种方式对于个人开发来说没有问题,你把 android.jar 替换掉就好了,但是如果面对团队开发,就非常痛苦了 %>_<%,每个人都要替换 SDK 的 android.jar,代价和风险可想而知。那么有没有好的解决办法,既可以让工程编译通过,又能够免去多人替换 jar 的成本呢?答案是有的。
Studio 默认引用的是 SDK 下面的 android.jar,那我们把它的引用改成完整的 jar 的路径不就行了么?
我们把完整的 android.jar 放在工程 libs 目录下,也就是平时依赖 jar 的地方,然后在工程 build.gradle 配置的 dependencies 里,以 provided 的方式引用 android.jar。因为每个工程模块依赖 android.jar 的类型就是 provided,这样不会把 android.jar 打包到应用中,运行环境中存在 framework.jar,应用直接就可以使用。
dependencies {
// compile fileTree(include: ['*.jar'], dir: 'libs') 这行一定要去掉,当然为 android.jar 换个目录也行
testCompile 'junit:junit:4.12'
provided files('libs/hidden_api_23.jar')
}
最后还要在工程根目录的 build.gradle 里面配置当前 project,加上下面的代码就行了。
project('app') { // app是你工程的名字,配置只对当前工程有效
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
// 注意修改 jar 包的路径,替换 app/libs/hidden_api_23.jar,其他部分不要改
// Xbootclasspath/p:是 Java 编译的寻址优先设置,先找缺省路径还是全路径
options.compilerArgs.add('-Xbootclasspath/p:app/libs/hidden_api_23.jar')
}
}
}
现在重新编译工程,虽然会在代码中出现错误提示,但是编译打包运行都是正常的。_
在开发中使用隐藏 API 和内部 API 是不推荐的做法,但是为了实现一些「黑科技」,这些又是必须的~