Android 中未公开的类(用@hide隐藏的类)

来源:互联网 发布:mysql命令行界面建表 编辑:程序博客网 时间:2024/06/07 09:47

今天在公司一个同事突然问我一个关于android提供的SDK源码中public对应的field无法访问的问题,我仔细的检查了一下相关的代码,没有发现代码有任何的问题,但是在调用该class对应的filed的时候却始终都报错,源码如下:

[java] view plaincopy
  1. public static final class Email implements DataColumnsWithJoins, CommonColumns  

[java] view plaincopy
  1. /** 
  2.  * The email address. 
  3.  * <P>Type: TEXT</P> 
  4.  * @hide TODO: Unhide in a separate CL 
  5.  */  
  6. public static final String ADDRESS = DATA1;  

从以上代码可以看到Email这个类中有一个public的static field叫做ADDRESS,但是根据Email.ADDRESS访问一直提示找不到field。这个现象让我觉得非常的好奇,仔细的google了一下,发现原因就在于@hide这个annotation上面。

在android的SDK中有两类的API是无法直接访问的,一类是com.android.internal包下面的类,可以称之为internal API。另外一类就是通过hide这个annotation所标注的class,method或者field,可以称作hide api。(可惜没有在SDK中找到关于hide这个annotation的源码)

根据网上的信息,把internal封装的源码和通过hide annotation标注的代码进行一个小小的对比,原文来自http://devmaze.wordpress.com/2011/01/18/using-com-android-internal-part-1-introduction/

Hidden API之所以被隐藏,是想阻止开发者使用SDK中那些未完成或不稳定的部分(接口或架构)。举个例子,Bluetooth API在API 5(Android 2.0)上才开放;在API 3 和4上都是用@hide属性隐藏了。当这些API被验证和清理后,Google的开发者会移除@hide属性,并让其在API 5官方化。很多地方在API 4 和5之间发生了变化。如果你的程序依赖某些隐藏的API,当其部署到新的平台上时,就有可能陷入困境。

对于internal API来说,从来都没有计划将其开放出来。它就是Android的“内部厨房”,对开发者来说,应该将其视作黑盒。凡事都会有变化的。如果你依赖某些internal API,也有可能在新的Android release上,这些internal API发生变化,从而令你失望。

总结一下区别:

Hidden API = 进行中的工作;

Internal API = 黑盒;

那么在开发应用程序时如何才能用到这些hide的类呢?
如果用到这种类,有两种办法,

0.通过java的反射机制绕过这层限制
1.把你的应用放在android源码树下编译。可以直接import进来的。
2.把源码树中用到的这种类的@hide去掉,重新编译sdk,在得到的sdk环境下不需要源码树也可以编译你的应用。

注意:用到这种类的应用不保证向后兼容,因为这些类随着android版本更新,可能会发生变化或者消失。



接下来介绍下方法二如何实现

我们使用sdk在开发一个android项目时,往往不能使用被隐藏的api,比如说:
1.我们想实现应用的静默安装,我们不能使用

1pm.installPackage(mPackageURI, observer, installFlags,installerPackageName);

2.使用大容量存储的相关功能时,不能使用

1StorageManager s = (StorageManager) getSystemService(Context.STORAGE_SERVICE);

等等。。
以自带android自带的AlarmClock 为例,导入eclipse后,往往出现很多错误,如下图所示:

例如,上图中的Intent.ACTION_ALARM_CHANGED 无法访问,下面我们就找到源代码看看原因何在?

我所介绍的方法必须要在linux下编译,但仅仅只是编译sdk,开发还是在windows下,假如你现在还不会android源码编译,请先搞定这个,再往下看。下面的方法二真的是非常的方便,解决了我好多问题,强烈建议你耐心看看。

根据java 编程规范,我们知道这个api (静态变量)被隐藏掉了,所以在sdk中无法使用。在知道了原因之后,我们有几种解决方案(强烈建议使用方法二):

方法一:自己将源代码中的@hide去掉,然后编译一个sdk来替换默认的sdk。 
在linux上使用  make PRODUCT-sdk-sdk 命令,编译一个新的sdk出来,注意编译后其实我们不需要整个sdk,只需要android.jar这个文件替换掉sdk里的android.jar,例如:笔者的sdk里的jar对应的目录为:

F:/Program Files/Android/android-sdk-windows/platforms/android-8/android.jar

具体编译sdk的方法是,在linux编译环境下用命令make PRODUCT-sdk-sdk ,成功后,会有如下提示:

Package SDK: out/host/linux-x86/sdk/android-sdk_eng.stevewang_linux-x86.zip

我们进入到linux编译环境的 out/host/linux-x86/sdk/android-sdk_eng.stevewang_linux-x86/platforms/android-2.2/目录下可以看到android.jar 文件。使用此文件替换   F:/Program Files/Android/android-sdk-windows/platforms/android-8/android.jar 即可。替换前记得备份

此方法较为麻烦,建议使用方法二

方法二:添加framework 编译出来的classes.jar文件到eclipse的build path 
其实在编译android的时候,我们将framework 编译到一个临时的jar包中了,这个jar包的路径一般为:
out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
我们只需要在linux上android源代码目录下使用make 命令即可生成此文件。

由于这个jar文件中的api 还没有重新打包,里面被@hide掉的api并没有被去掉。所以我们依然能够引用里面被@hide的api。而sdk 中的android.jar文件时重新打包生成的,其里标记有@hide的api已经被去掉了。所以我们把 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar 拷贝到本地pc上。然后在工程中添加此jar包。
具体方法:
1 . 拷贝linux编译生成的  out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar到本机PC。
2,在eclipse的Android项目中,选择项目属性->Java Build Path->Libraries->Add Library->User Library->Next-> UserLibraries进入到User Libraries管理界面,点击New新建一个User Library,比如android_framework,点击Add Jars把Jar包加入到建立的User Library中,最后点击OK。
3.选择项目属性->Java Build Path->Order and Export,使用右面的“UP”键,把所建立的User Libraries移到Android SDK的上面。这样做的意思就是优先使用classes.jar中的API,其次使用android.jar中的API。为什么不直接使用classes.Jar?因为我在使用时发现,android.jar中有的东西classes.jar中没有。

如下图:

之后我们的工程错误消失了:

到此问题基本解决。