1.4 剖析Android 4.0程序的组织结构

1.目录结构介绍

资源是Android应用程序不可或缺的部分。资源是你想包含和引入到应用程序里面的一些外部元素,如图片、音频、视频、文本字符串、布局、主题等。每个Android应用程序包含一个资源目录(res/)和资产目录(assets/),但资产不常用,因为它们的应用很少,仅在需要读取原始字节流时才需要保存数据在assets/目录。Res/和assets/目录均在Android项目树的顶端,和源代码目录(src/)处在同一级上。资源和资产从表面上看没多大区别,不过总体上,在存储外部内容时资源用得更多。真正的区别在于任何放置在资源目录里的内容可以通过应用程序的R类访问,这是被Android编译过的。而任何存放在资产目录里的内容会保持它的原始文件格式,为了读取它,必须使用AssetManager来以字节流的方式读取文件。所以保持文件和数据在资源中(res/)中会更方便访问。

2.Resource目录及其下文件详解

res/目录下可以有以下几个子目录,部分目录开发工具并没有自动创建,根据需要可以自行创建,介绍如下所示。

(1)src/:专门存放编写的Java源代码的包。

(2)android 4.0/:存放Android自身的jar包。

(3)gen/:该目录不用开发人员维护,但又是非常重要的目录。用来存放由Android开发工具所生成的目录。该目录下的所有文件都不是我们创建的,而是由ADT自动生成的。该目录下的R.java文件非常重要。

(4)assets/:该目录用来存放应用中用到的类似于视频文件、MP3等媒体文件。

(5)res/:res是resource的缩写,称该目录为资源目录。该目录可以存放一些图标、界面文件、应用中用到的文字信息。

(6)AndroidManifest.xml:该文件是功能清单文件,列出了应用中所使用的所有组件,如Activity,以及后面要学习的广播接收者、服务等组件。

(7)proguard.cfg:Proguard是Android的混淆器的配置文件,用来防止程序被反编译,如果需要使用该功能,只需要在project.properties文件中添加一行配置信息progruard.config= proguard.cfg,如下图所示。

(8)project.properties:该文件存放了项目对应的一些环境配置,如应用要求运行的最低Android版本,同时可以包含proguard的配置等信息。

资源被编译到最终的APK文件里。Android创建了一个被称为R的类,这样在Java代码中可以通过它关联到对应的资源文件。R类包含的子类的命名由资源在res/目录下的文件夹名称所决定。

关于res/子目录的一些补充说明。

(1)res/drawable。

res/目录下有三个dawable文件夹——drawable-*dpi,区别只是将图标按分辨率高低来放入不同的目录。drawable-hdpi用来存放高分辨率的图标;drawable-mdpi用来存放中等分辨率的图标;drawable-ldpi用来存放低分辨率的图标。程序运行时可以根据手机分辨率的高低选取相应目录下的图标。不过,如果不想准备过多的图片,那么也可以只准备一张图标,将其放入三个目录的任何一个中去。

(2)res/values/文件夹下常放的文件如下所示。

① strings.xml。

用来定义字符串和数值,在Activity中使用getResources().getString(resourceId)或getResources(). getText(resourceId)取得资源。

在Eclipse中对res/values下的文件左键双击或右键单击后选择“Open With”→“Android Resource Editor”编辑器打开。默认是Resources显示,单击旁边的“strings.xml”,显示代码内容,如下图所示。

HelloWorld项目的strings.xml文件内容如下所示。

<?xml version="1.0" encoding="utf-8"?>
<resources>

<string name="hello">Hello World,HelloWorldActivity!</string> <string name="app_name">HelloWorld</string>
</resources>

每个string标签声明了一个字符串,name属性指定其引用名。

为什么需要把应用中出现的文字单独存放在strings.xml文中呢?

原因有二,一是为了国际化,Android建议将在屏幕上显示的文字定义在strings.xml中,如果今后需要进行国际化,例如,我们开发的应用本来是面向国内用户的,当然要在屏幕上使用中文,而如今我们要让应用走向世界,打入日本市场,当然需要在手机屏幕上显示日语,如果没有把文字信息定义在strings.xml中,就需要修改程序内容了。但当我们把所有屏幕上出现的文字信息都集中存放在strings.xml文件之后,只需要再提供一个strings.xml文件,将里面的汉字信息都修改为日语,再运行程序时,Android操作系统会根据用户手机的语言环境和国家来自动选择相应的strings.xml文件,这时手机界面就会显示出日语。这样做非常方便国际化。二是为了减少应用的体积,降低数据冗余。假设在应用中要使用“我们一直在努力”这段文字10000次,如果我们不将“我们一直在努力”定义在strings.xml文件中,而是在每次使用时直接写上这几个字,这样下来程序中将有70000个字,这70000个字占136KB的空间。而由于手机的资源有限,其CPU的处理能力及内存是非常有限的,136KB对于手机程序来说是个不小的空间,我们在开发手机应用时一定要记住“能省内存就省内存”。而如果将这几个字定义在strings.xml中,在每次使用到的地方通过Resources类来引用该文字,只占用了14B,因此对降低应用体积效果是非常有效的。当然我们可能在开发时并不会用到这么多的文字信息,但是“不以善小而不为,不以恶小而为之”,作为手机应用开发人员,一定要养成良好的编程习惯。

② arrays.xml。

用来定义数组,在Activity中使用getResources().getStringArray(resourceId)获取一个String数组。

代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<resources>
      <string-array name="sports">
          <item>足球</item>
          <item>篮球</item>
          <item>太极</item>
          <item>乒乓球</item>
</string-array>
</resources>

③ colors.xml。

用来定义颜色和颜色字串数值,可以在Activity中使用getResources().getDrawable(resour- ceId)以及getResources().getColor(resourceId)取得这些资源。代码如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <color name="contents_text">#ff000000</color>
</resources>

④ dimens.xml。

用来定义尺寸数据,在Activity中使用getResources().getDimension(resourceId)取得这些资源,代码如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <dimen name="height">80dip</dimen>
</resources>

⑤ styles.xml。

定义样式,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="sharpText">
         <item name="android:textSize">18px</item>
         <item name="android:textColor">#008</item>
    </style>
</resources>

需要注意的是,Android中的资源文件不要以数字作为文件名,否则会导致错误。

(3)res/layout/目录下的布局文件简介。

本例中的布局文件是ADT默认自动创建的main.xml文件。在Eclipse中,双击main.xml文件,或者右键单击以选择相应的编辑器,选用“Android Layout Editor”。在编辑区出现“Layout编辑器”的预览效果。

可以单击Layout选项卡旁边的main.xml,切换到代码模式以编辑。

与在网页中布局中使用HTML文件一样,Android在XML文件中使用XML元素来设定屏幕布局。每个文件包含整个屏幕或部分屏幕,被编译进一个视图资源,可以被传递给Activity.setContentView或被其他布局文件引用。文件保存在工程的res/layout/ 目录下,它被Android资源编辑器编译。

下面介绍HelloWorld项目的main.xml文件的内容,其代码如下所示。

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>

下面逐元素进行分析说明。

① <LinearLayout>元素。

LinearLayout翻译成中文是线性布局,所谓线性布局就是在该元素下的所有子元素会根据其“orientation”属性的值来决定是按行或者是按列逐个显示。

② <TextView>元素。

该元素与HTML中的<label>元素比较相似,也是一种显示控件。

其属性text指定在该元素上面显示的文字内容。建议将该文字内容在strings.xml文件中进行定义,之后再在main.xml文件中通过“@string/stringName”的方式进行引用。

3.gen/目录下的R.java文件详解

R.java文件中默认有attr、drawable、layout、string四个静态内部类,每个静态内部类分别对应一种资源,如layout静态内部类对应layout中的界面文件,其中每个静态内部类中的静态常量分别定义一条资源标识符,如“public static final int main=0x7f030000”对应的是layout目录下的main.xml文件,如下图所示。

由于目前drawable-*dpi目录下都只有icon.png一个图片文件,因此此时不同像素的同名的icon.png文件在drawable内部类中只有一个icon属性。如果我们在drawable-*dpi目录下再添加一幅图片,那么会有什么效果出现呢?如下图所示。

sharpandroid.png

现在已经理解了 R.java文件中内容的来源,也即是当开发者在res/目录中任何一个子目录中添加相应类型的文件之后,ADT会在R.java文件中相应的匿名内部类当中自动生成一条静态int类型的常量,对添加的文件进行索引。如果在layout目录下再添加一个新的界面,那么在public static final class layout中也会添加相应的静态int常量。相反,当在res目录下删除任何一个文件后,其在R.java中对应的记录会被ADT自动删除。例如,在strings.xml中添加一条记录,在R.java的string内部类中也会自动增加一条记录。

R.java文件会给开发程序带来很大的便利,例如,在程序中使用public static final int icon= 0x7f020000就可以找到其对应的icon.png图片。

R.java文件除了有自动标识资源的“索引”功能之外,还有另一个主要的功能,当res目录中的某个资源在应用中没有被使用到时,在该应用被编译时系统就不会把对应的资源编译到该应用的APK包中,这样可以节省Android手机的资源。

4.组件标识符

通过对R.java文件的介绍,已经了解了R文件的索引作用,它可以检索到应用中需要使用的资源。下面介绍如何通过R.java文件来引用所需要的资源。

(1)在Java程序当中,可以按照Java的语法来引用。

① R.resource_type.resource_name。

需要注意的是,resource_name不需要文件的后缀名。例如,上面的icon.png文件的资源标识符可以通过如下方式获取。

R.drawable.icon

② android.R.resource_type.resource_name。

Android系统本身自带了很多的资源,也可以进行引用,只是需要在前面加上“android.”以声明该资源来自Android系统。

(2)在XML文件中引用资源的语法如下所示。

① @[package:]type/name。

使用自己包下的资源可以省略package。

在XML文件中,main.xml以及AndroidMainfest.xml文件通过@drawable/icon的方式获取。其中@代表的是R.java类,drawable代表R.java中的静态内部类drawable,/icon代表静态内部类drawable中的静态属性icon。而该属性可以指向res目录下的drawable-* dpi中的icon.png图标。

其他类型的文件也比较类似。凡是在R文件中定义的资源都可以通过@ Static_inner_ classes_name/resourse_name的方式获取,如@id/button,@string/app_name。

② 如果访问的是Android系统中带的文件,则要添上包名“android:”,如android:textColor= "@android:color/red"。

(3)@+id/string_name表达式。

顺便说一下,在布局文件中需要为一些组件添加id属性作为标示时,可以使用表达式@+ id/string_name,其中“+”表示在R.java的名为id的内部类中添加一条记录。如"@+id/button"的含义是在R.java文件中的id这个静态内部类添加一条常量名为button,该常量就是该资源的标识符。如果id这个静态内部类不存在,则会先生成它。通过该方式生成的资源标识符,仍然可以以@id/string_name的方式引用。示例代码片段如下所示。

<RelativeLayout
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
>
<Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/cancle_button"
      android:layout_alignParentRight="true"
      android:id="@+id/cancle"/>
<Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_toLeftOf="@id/cancle"
      android:layout_alignTop="@id/cancle"
      android:text="@string/ok_button"/>
</RelativeLayout>

其中,android:id="@+id/cancle"将其所在的Button标识为cancle,在第二个Button中通过"@id/cancle"对第一个Button进行引用。

5.AndroidManifest.xml介绍

如果要查看AndroidManifest.xml的详细信息,可以到帮助文档中去了解。路径如下:“Dec Guide”→“The AndroidManifest.xml File”超链接。

每个应用程序都有一个功能清单文件AndroidManifest.xml(一定是这个名字)在它的根目录里。这个清单文件给Android系统提供了关于这个应用程序的基本信息,系统在运行任何程序代码之前必须知道这些信息。今后在开发Activity、Broadcast、Service过程中都要在AndroidManifest.xml中进行定义。另外如果使用到系统自带的服务,如拨号服务、应用安装服务、GPRS服务等都必须在AndroidManifest.xml中声明权限。

AndroidManifest.xml主要包含以下功能:

•命名应用程序的Java应用包,这个包名用来唯一标识应用程序。

•描述应用程序的组件——活动、服务、广播接收者、内容提供者;对实现每个组件和公布其功能(比如,能处理哪些意图消息)的类进行命名。这些声明使得Android系统了解这些组件以及它们在什么条件下可以被启动。

•决定应用程序组件运行在哪个进程里面。

•声明应用程序所必须具备的权限,用于访问受保护的部分API以及和其他应用程序交互。

•声明应用程序其他的必备权限,用于组件之间的交互。

•列举测试设备Instrumentation类,用来提供应用程序运行时所需的环境配置及其他信息,这些声明只在程序开发和测试阶段存在,发布前将被删除。

•声明应用程序所要求的Android API的最低版本级别。

•列举application所需要链接的库。

程序中使用的所有组件都会在功能清单文件中列出来,所以先分析功能清单文件。只有下列元素才是功能清单文件中的合法元素,应用开发者不能添加自己的元素或属性。

<?xml version="1.0" encoding="utf-8"?>
<manifest>
     <uses-permission />
     <permission />
     <permission-tree />
     <permission-group />
     <instrumentation />
     <uses-sdk />
     <uses-configuration />
     <uses-feature />
     <supports-screens />
     <application>
         <activity>
         <intent-filter>
             <action />
             <category />
             <data />
         </intent-filter>
         <meta-data />
     </activity>
     <activity-alias>
           <intent-filter> …</intent-filter>
           <meta-data />
     </activity-alias>
     <service>
           <intent-filter> … </intent-filter>
           <meta-data/>
     </service>
     <receiver>
           <intent-filter> … </intent-filter>
           <meta-data />
      </receiver>
      <provider>
            <grant-uri-permission />
            <path-permission />
            <meta-data />
        </provider>
        <uses-library />
    </application>
</manifest>

下面以HelloWorld项目的功能清单文件为例进行讲解。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.wangjialin.hello"
      android:versionCode="1"
      android:versionName="1.0" >
      <uses-sdk android:minSdkVersion="14" />

<application android:icon="@drawable/ic_launcher" ndroid:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".HelloWorldActivity" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
</manifest>

以下详细介绍各个标签。

(1)<manifest>元素。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.sharpandroid.activity"
       android:versionCode="1"
       android:versionName="1.0">

该元素是AndroidManifest.xml文件的根元素,为必选。根据XML文件的语法,xmlns: android指定该文件的命名空间。功能清单文件会使用http://schemas.android.com/apk/res/ android所指向的一个文件。Package属性是指定Android应用所在的包,以后会经常说到“应用的包”,“应用的包”就是指该属性的内容。Android:versionCode指定应用的版本号。如果应用需要不断升级,在升级的时候应该修改该值。Android:versionName是版本名称,名称的取定可根据爱好而定。

(2)<application>元素。

<application
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name" >
       <activity
            android:label="@string/app_name"
            android:name=".HelloWorldActivity" >
            <intent-filter >
                 <action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>

<application>是非常重要的一个元素,今后开发的许多组件都会在该元素下定义的。该元素为必选元素。

<application>的icon属性是用来设定应用的图标。

该表达式指向的是icon.png图片。在Eclipse中双击icon.png图片,对照关系如下所示。

public final class R {
        public static final class attr {
        }
        public static final class drawable {
        public static final int ic_launcher=0x7f020000;
        public static final int sharpandroid=0x7f020001;
        }

<application>的label属性用来设定应用的名称。指定其属性值所用的表达式@string/ app_name含义与上面的表达式@drawable/icon一样,同样是指向R.java文件中的string静态内部类中的app_name属性所指向的资源。在这里它指向的是strings.xml文件中的一条记录app_name,其值为“Android,你好”,因此,这种表达方式等价于“android:label="Android,你好!"”。

两者结合起来,当程序发布到模拟器上之后,会在应用程序的快速启动区中显示效果,如下图所示。

(3)<activity>元素。

<activity>元素的作用是注册一个Activity信息,当我们在创建HelloWorld这个项目时,指定了Create Activity属性为HelloActivity,之后ADT在生成项目时自动创建了一个Activity名称就是HelloActivity.java,Activity在Android中属于组件,它需要在功能清单文件中进行配置。

<activity>元素的name属性指定的是该Activity的类名。我们可以看到这个属性值.HelloActivity中的“.”,“.”代表的是在上面<manifest>元素的package属性中指定的当前包。因而.HelloActivity的含义等价于com.wangjialin.helllo.HelloActivity.java。

如果Acitivity在应用程序包的子包中,必须在声明的时候在子包前加上“.”。

Activity只能放在“应用的包”或者其子包里面,而不能在“应用的包”以外的包中,这一点必须牢记。

<activity>元素的label属性表示Activity所代表的屏幕的标题,其属性值的表达式在上面已经介绍过了,不再赘述。

该属性值在AVD程序运行到该Activity所代表的界面时,会在标题上显示该值,如下图所示。

(4)<intent-filter>元素。

翻译成中文是意图过滤器。首先简单介绍什么是意图(Intent)。应用程序的核心组件(活动、服务和广播接收器)通过意图被激活,意图代表的是你要做的一件事情,代表你的目的,Android寻找一个合适的组件来响应这个意图,如果需要会启动这个组件一个新的实例,并传递给这个意图对象。

组件通过意图过滤器(intent filters)通告它们所具备的功能——能响应的意图类型。由于Android系统在启动一个组件前必须知道该组件能够处理哪些意图,那么意图过滤器需要在manifest中以<intent-filter>元素指定。一个组件可以拥有多个过滤器,每一个描述该组件具有的不同能力。一个指定目标组件的显式意图将会激活那个指定的组件,意图过滤器不起作用。但是一个没有指定目标的隐式意图只在它能够通过组件过滤器中任一过滤器时才能激活该组件。

第一个过滤器:

<action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />

是最常见的。它表明这个Activity将在应用程序加载器中显示,就是用户在设备上看到的可供加载的应用程序列表。换句话说,这个Activity是应用程序的入口,是用户选择运行这个应用程序后所见到的第一个Activity。

(5)权限Permissions。

HelloWorld项目的功能清单文件中并没有出现Permissions元素,但是Permission也是一个非常重要的节点。Permission是代码对设备上数据的访问限制,这个限制被引入来保护可能会被误用而曲解或破坏用户体验的关键数据和代码,如拨号服务、短信服务等。每个许可被一个唯一的标签所标识。这个标签常常指出了受限的动作。例如,下表是一些Android定义的权限。

Android定义的权限

如申请发送短信服务的权限需要在功能清单文件中添加如下语句:

<uses-permission android:name="android.permission.SEND_SMS"/>

一个功能(feature)最多只能被一个权限许可保护。如果一个应用程序需要访问一个需要特定权限的功能,它必须在manifest元素内使用<uses-permission>元素来声明这一点。这样,当应用程序安装到设备上之后,安装器可以通过检查签署应用程序认证的机构来决定是否授予请求的权限,在某些情况下,会询问用户。如果权限已被授予,那应用程序就能够访问受保护的功能特性。如果没有,访问将失败,但不会给用户任何通知。因此在使用一些系统服务,如拨号、短信、访问互联网、访问SD Card时一定要记得添加相应的权限,否则会出现一些难以预料的错误。

应用程序还可以通过权限许可来保护它自己的组件(活动、服务、广播接收器、内容提供者)。它可以利用Android已经定义(列在android.Manifest.permission里)或其他应用程序已声明的权限许可,或者定义自己的许可。一个新的许可通过<permission>元素声明。例如,一个Activity可以用下面的方式保护:

<manifest … >
    <permission android:name="com.example.project.DEBIT_ACCT" …/>
    …
    <application …>
       <activity android:name="com.example.project.FreneticActivity" … >
               android:permission="com.example.project.DEBIT_ACCT"
               … >
        …
        </activity>
    </application>
     …
    <uses-permission android:name="com.example.project.DEBIT_ACCT" />
     …
</manifest>

注意,在这个例子里,DEBIT_ACCT许可并非仅仅在<permission>元素中声明,如果该应用程序的其他组件要使用到该组件,那么它同样在<uses-permission>元素里声明。

(6)库Libraries。

每个应用程序都链接到默认的Android库,这个库包含了基础应用程序开发包(实现了基础类,如活动、服务、意图、视图、按钮、应用程序、内容提供者,等等)。

然而,一些包处于它们自己的库中。如果你的应用程序使用了其他开发包中的代码,它必须请求链接到它们。这个manifest必须包含一个单独的<uses-library>元素来命名每一个库,如在进行单元测试的时候需要引入其所需要的库。

代码片段如下:

<application android:icon="@drawable/icon"
                android:label="@string/app_name">
      <uses-library android:name="android.test.runner" />
</application>

6.如何在文档中查找权限信息

在前面我们介绍了几种常用的权限,但是如何了解Android平台中的所有权限信息呢?如查找CALL_PHONE这个权限信息,可以按如下步骤操作。在SDK解压目录下找到docs文档的子目录,如G:\Android 4.0\android-sdk-windows\docs,运行其中的index.html文件,页面如下图所示。

单击“Reference”,如下图所示。

单击方框中的android超链接,进入如下界面。

单击方框中的Mainfest.permission超链接,进入Mainfest.permission类页面,如下图所示。

向下寻找,则会找到CALL_PHONE这个权限信息,如下图所示。

单击CALL_PHONE超链接,进入下一个界面,如下图所示。

从中得知申请该权限的值为“android.permission.CALL_PHONE”。

以后如果需要申请其他任何权限,都可以通过该方式在Mainfest.permission类中找到相关的信息。