作为一枚coder,做界面,很多时候都是一场梦魇。很多时候,我们会感觉对于底层逻辑实现的很有把握性,哪怕需求一直在变,也可以通过不断的重构一直跟进,一切尽在掌握。但遭遇界面,往往就不再如此,它的好坏总是和审美、体验之类的词汇扯在一起,在凤姐芙蓉出没的年头,谈审美成为一件恐怖的事情。你可能会被要求不停的改代码,就为了移动一个像素,调整一枚按钮,琐碎而无聊。
为了改变这样的状况,挽救coder们于水生活热之中,很多开发平台,都采用了类似于资源文件的解决方案。此类方案的基本思想是,将界面的实现与底层逻辑的实现完全剥离开来,用资源文件这样的东西来描述界面。资源文件的描述语言,往往是结构化很强,比如Html,Xml(及其变形体)之类的。于开发语言相比,此类语言逻辑性较弱但结构更好可读性更强更容易理解,并对自动化工具非常友好,可以于界面的拖拽配置结合的更加完美。这样的剥离,可以是的底层逻辑和上层界面独立变化,甚至不同的人员开发(这一点在web开发上表现的应该很明显...),两者之间的耦合性非常的小,coder们的负担,陡然减少(好吧,一个很挫的资源架构也会额外增加开发人员的负担,Symbian同学,请不要对号入座...)。
结构和格式
Android的资源文件,是由目录结构,Xml格式的文件,和纯数据文件构成。从格式上来看,无疑,学习门槛非常低。Xml作为coder们的瑞士军刀,哪怕使不习惯,弄得清楚并会用至少是没有问题。从配套的工具来看,Android的ADT,提供了一套可视化的配置工具,说不上特别好用,但至少是差强人意能凑合着用,比不上iPhone的,调戏Symbian还是没有问题的[强档广告首播:有道词典 for iPhone新版火热上线,增加了超强单词本功能,特有的触电式颤抖单词切换功能,让你欲罢不能,持有相关设备的童鞋不要犹豫,一拥而上吧...]。
Android的资源文件,覆盖面超级广,只要是和界面相关的,都可以用资源文件表示,比如:UI的样式,菜单,配置文件,各种描述性字符串,图片,音频视频文件,动画,颜色,尺寸,风格和样式,等等等。所有的资源文件(不考虑asset,它和讨论暂无关联...),都放在res目录下,不同类别的资源,需要放置在不同的特定名称的子文件夹中,或者是写在特定文件名的文件中(或者ms不是必须的,但,不用在这里特立独行,寻章办事也挺好...)。比如,所有作为UI背景之类的图片,都需要扔在drawable这类的文件夹中,所有字符串相关的,都会放到values目录下形如strings.xml这样的文件中(如下图所示,是一个资源文件目录结构的截图...)。
每个xml文件,都有一定的约定。比如一个字符串,会放在<string></string>这样的xml element中(如下图所示...),你可以通过eclipse的ADT插件提供的可是界面去填而不关注具体规范,也可以直接人肉打造,前者对于新手来说更为直观,后者对于老鸟而言更为迅捷。
可配置性
程序逻辑总是不变应万变的,但界面往往是需要能够72变。首先一种变化因素,就是状态。想象一下,我们往往会有这样类似的需求,一个按钮,我们需要没有按下去的时候是一种背景,按的过程中刷的变成另一副模样,当它可用的时候需要鲜鲜亮的一个样子,不可用的时候最好是灰不溜秋没人愿点的怂样,诸如此类。传统编程模型下(Symbian,哥叫你出来当模特...),我们总是需要不厌其烦的用代码控制这样的事情。监听不同的事件,见缝插针的切换背景,并祈祷上天,千万别让哥调整,否则哥和你没完。
在Android中,做这个事情,变得简单许多,通过预设的一些Xml属性,能够轻松的搞定。如上图所示,是Radio Button的背景。通过搭配不同的属性,就可以自动转换背景。比如第一个<item>,说的是当Radio Button被选中,并且具有焦点的时候,显示btn_radio_on这幅图片,而最后一个<item>,说的是前述条件都不满足,并且处于选中状态,那么显示btn_radio_on这幅图片。另外一个更易变的因素,就是手机硬件/软件环境了,毕竟,不是家家都是苹果,一个平台搭一款手机,手机款形多样化,几乎是避免不了的问题。没有人希望自己做的软件在大屏幕手机上闪亮光鲜,换个小屏幕就惨不忍睹,竖屏看像那么回事横屏看就挤做一团。还有就是语言环境了,做为一个有国际眼光的coder,作面向世界的NB软件是咱的梦想,但我们不能因为自己的梦想逼迫大家都去学中文,做一款软件可以根据手机的语言环境选择最合适展示的语言,很多时候,是一个需要具备的功能点。
在Android中,实现这些,都是举手之劳。方法就是将和环境相关的资源,放入特定名称的文件夹中。比如,表示简体中文字符信息的资源,可以放到values-zh-rCN中去,当系统语言环境为简体中文时,就会呈现出中文的字符信息。在Android中,很多相关配置项,都可以按照这样的方式参与到资源自适应的活动中来,包括屏幕大小,屏幕朝向,屏幕分辨率,语言环境,触屏类型,SDK版本等等。系统会给所有配置项一个优先级(或者说权重,次序之类的),当用户提供了多份资源的时候,系统会根据优先级从高到底淘汰备选资源,如果淘汰仅剩了一个,那就是最符合当前系统软硬件语言环境的资源项,如果一个不剩,择启用默认项(最是形如values这样没有任何尾巴目录中的资源...)。因此,默认的资源是非常重要的,它必须是其他所有可选资源项的超集,否则在资源选择失败的情况下,应用会凄凉的崩溃。
R类
在使用资源后,界面逻辑与底层逻辑的耦合被降低了,但这不意味着,两者没有关联了。比如,需要为某个按钮增加一个点击事件,就需要定位到所需的那个按钮;再比如,你需要使用某个字符串资源,通知用户某件事情,就需要能定位到资源中放置的该字串。
最显而易见的一种方式,就是通过字符串比较,用名字信息在资源的xml描述文件中定位到所需的内容,加载并使用。这种方式,解决了查找的问题,但反复的字符串比较,势必带来严重的效率隐患。因此,在Android中,类似于Symbian的方法,引入了一个R类。
它的基本思想是,通过增加一个额外的编译器,为所有的资源项,都赋予一个32位的整形数来表示,同一个资源像的不同配置,都使用同一个id。这个整形数,就相当于这个资源项的门牌号码,能够帮助定位到对应的资源项。所有的这些整形数,都以常量的方式,整合到一个Java类中,这个类就是R类。这样,在程序中,就可以通过使用这个R类,来查找所需的资源,这就将字符串比较,简化成了一个整形数的比较,大大的节约了开销。
不得不说,这整套逻辑和Symbian中的资源文件预编译一致。但两者很不同的点在于Symbian中的整形数,代表的是一个二进制流的偏移量,资源中的内容在编译时决定了。而Android中的整形数,是一个有逻辑意义的数值,它表达了这个资源所处的资源包,类别,和脚标,它的具体内容在运行时才确定,这使得它的灵活性大大增强,付出的则是一定的效率代价。实现
按照惯例,还是要说实现的,以一个查找流程为示例。当在Activity中需要使用字符串的,会调用它的getString方法,传入R.stirng.xxx的一个整形数,换取一个符合当前机器环境配置的字符串。
getString,追根溯源,来到AssetManager类中。Asset类,其实是一个空壳,它仅仅是提供了一些便利的接口,而将请求,通过JNI的接口,传入到了底层C++实现的类库中。
在底层的实现,主要是在C++实现的,AssetManager,ResourceTypes等等之中。其中:
- JNI文件在:framework/base/core/jni
- 头文件在:framework/base/include/utils
- CPP文件在:framework/base/libs/utils
具体实现,和前述的算法逻辑是一致的。每一个资源的id,32位,高8位表示资源包,低16位用于描述脚标,中间8位,用来说明类别。所有资源中的文件,都被预处理了,放入到了一系列的队列和表中,通过id,可以查到具体的位置。然后根据缓存的环境设置对象,跑一次淘汰算法,获得匹配的资源对象的对应文件和偏移量。然后将值读取出来,通过JNI接口,拷贝回去。
以上这些描述,并不能帮助了解真实的实现细节,主要是为了促使大家对读取资源的效率有一个比较直观的认知。整个资源读取的流程比较长,但是实现在C++中,可以预想,效率比Java高一些,开发人员,应该能够根据自己的需求,决定是否将内容写入资源文件中(还是写在代码中...),是不是需要自己稍微缓存一下,诸如此类。