Android 开发环境 - 获取标识ID

在上一章谈了 XML 描述档中界面元件的各种「android:」开头的属性。要使用一个界 面元件,第一件事就是定义出界面描述档。大部分的界面元件 (如 LinearLayout、TextView) 不需要在程序中作后续处理,因此可以直接描述。不过对于那些将在程序中被参考 (reference) 到的界面元件(如按钮 Button、文字输入栏位 EditText),我们需要透过在 XML 描述档 中,定义该界面元件的「 android:id 」识别符号属性。之后在程序中所有与这个界面元件有 关的操作,都能根据「 android:id」 识别符号来在调用这个界面元件。<EditText android:id="@+id/height" />前面章节提过,写作时最好将 XML 描述档属性分行列出,以易于阅读 (增加可读性 )。 而我们的范例却将 android:id 属性直接摆在 EditText 标签后。其实这么做同样是基于 易 于阅读的考量。当然你也可以将「android:id」属性分行列出,或是将「android:id」属性 放在属性列表的 中间或最后头,这些作法都是允许的,本书中一律使用将 android:id 属 性直接摆在界面元件标签后的写法。

android:id 属性的内容长得比较特别:@+id/height「height」是这个界面元件的 android:id。以后的程序中会使用「 R.id.height」来取 得 这 个 界 面 元 件 。

「 @+id」的意思是我们可以通过这个识别符号来控制所对应的界面元件 ,「R」类别会自动配置一个位址给这个界面元件。

「R」类别的内容则可以透过查看 R.java 得知。

XML 描述档与 R.java 档在 Android 系统中,我们使用 XML 来定义 UI。

但是有些稍微有经验的开发者可能会 有 疑问:

「用 XML 来描述界面固然方便,但是对于手机程序来说,直接用 XML 档桉是不是太占空间了?」。

没错,如果 Android 是直接使用 XML 来储存界面描述到手机上的话,一定会使用比起 现在大的多的档桉空间。

解决的方法是 Android 并不直接使用 XML 档桉,而是透过 Android 开发工具,自动将 XML 描述档转换成资源档桉。

一旦应用程序要使用某个界面或 是任何种类的资源 (字串、图片、图示、音效 …),都使用索引来查询。

当你建立一个 BMI 新专桉,打开位于 「src/com/demo/android/bmi 」 目录下的「R.java」档,你可以看到如下的程序码:[code]/* AUTO-GENERATED FILE. DO NOT MODIFY.
*

  • This class was automatically generated by the
  • aapt tool from the resource data it found. It
  • should not be modified by hand.
    */

package com.demo.android.bmi;

public final class R {
public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040000;
}
}[/code]在照着前一章新增了 XML 描述后,再次打开打开 「src/com/demo/android/bmi 」 目录下的 「R.java」 档 ,你可以看到如下的程序码:[code]/* AUTO-GENERATED FILE. DO NOT MODIFY.
*

  • This class was automatically generated by the
  • aapt tool from the resource data it found. It
  • should not be modified by hand.
    */

package com.demo.android.bmi;

public final class R {
public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class id {
public static final int height=0x7f050000; public static final int result=0x7f050003; public static final int submit=0x7f050002; public static final int suggest=0x7f050004; public static final int weight=0x7f050001;
}
public static final class layout {

public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040000;
}
}[/code]我们看到在 R.java 档桉中,分别有 attr (属性)、drawable (图片、图示)、id (识别符号)、layout (界面描述)、string (文字) 这几种资源型态,就 XML 描述档中的 id 来 说,开发工具会根据 XML 描述档中指定的 id,生成对应的资源,并自动指定一个位址。

Google 官方文件是这么解释「R.java」档案的作用的:[code]A project’s R.java file is an index into all the resources defined in the file. You use this class in your source code as a sort of short-hand way to refer to resources you’ve included in your project. This is particularly powerful with the code-completio n features of IDEs like Eclipse because it lets you quickly and interactively locate the specific reference you’re looking for.

The important thing to notice for now is the inner class named “layout”, and its member field “main”. The Eclipse plugin noticed that you added a new XML layout file and then regenerated this R.java file. As you add other resources to your projects you’ll see R.java change to keep up.[/code]有了「R.java」做中介,在 XML 描述档中,我们可以透过@[类型]/[识别符号] 这样的语法来为某个界面元件提供识别符号,以供程序控制。

例如,我们可以用 「@+id/height 」来为对应供输入身高数字的 EditText 元件提供识别符号。

将字串抽离 XML 当我们在 res 资料夹中新增各种一个 XML 档桉,或是一张图片时,开发工具会从 res 资料夹中手机集,并将各种资源整成一个索引,自动产生出 R.java 档。透过这个特性,我 们可以进一步加工我们的 XML 描 述 档 ,让界 面 更 易 于 维 护 。开 启 res/values/strings.xm l, 原始的内容为<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">BMI</string> </resources>上面只定义了一个字串「app_name」,用来表示应用程序名称(在之后讲解AndroidManifest.xml 档桉时将会用到)。

我们看到表示字串的格式为

文字叙述 我们将上一章中的叙述抽取出来,整理进 strings.xml 档桉。

完整的 strings.xml 档桉如下:<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">BMI</string> <string name="height">身高 (cm)</string> <string name="weight">体重 (kg)</string> <string name="bmi_btn">计算 BMI 值</string> <string name="bmi_result"> 你的 BMI 值是 </string> </resources>在 strings.xml 档桉中,我们在原本的 app_name 字串外,自行定义了另外几个字串 。

如果我们再次开启 「R.java」档,我们会发现档桉中的 string 类别中也自动索引了上面 定义好的字串: public static final class string { public static final int app_name=0x7f040000; public static final int bmi_btn=0x7f040003; public static final int bmi_result=0x7f040004; public static final int height=0x7f040001; public static final int weight=0x7f040002; }接着,我们把这些字串应用到之前定义好的 XML 描述档中。

透过使用@string/[ 识别符 号]这样存取 string 类型的格式,来取代 main.xml 档桉中原本写死的文字叙述。

完整的程序码如下:[code]<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android= http://schemas.android.com/apk/res/android android:orientation=“vertical”
android:layout_width=“fill_ parent” android:layout_height=“fill_parent”



<TextView android:layout_width=“fill_ parent”

android:layout_height=“wrap_content” android:text=“@string/weight”
/>




[/code]再次运行 Android 模拟器,我们看到与前一节完全相同的界面。但就界面而言,透过 将描述字串统一集中在 「string.xml」 中,我们以后要修改界面时更有弹性了。

至此我们已经完成了 BMI 应用程序负责「显示 (View)」的部份。

新增 XML 文件我们在前面都只修改到开发工具帮我们产生的档桉, 而事实上,我们所有在 「res」 目 录中所做的修改,开发工具都会自动搜寻,将之整理到「 R.java」中。

因此我们也可以在「src/values 」中建立自己的文字描述档桉。

我们这就在「res/values」目录中建立一个 「advice.xml」档,上面将包含 BMI 程序 算出 BMI 值后将给予的建议文字,完整的档桉如下:[code]

<?xml version="1.0" encoding="utf-8"?> 你该多吃点 体型很棒喔 你该节食了 [/code]打开「 R.java」档,我 们 发 现「advice_light 」、「 advice_average 」、「 advice_heavy」也已整理进「 R.java」档的索引中,以供程序调用。

那么接下来,我们就开始进入到了解 Android 程序流程的部分吧。

接着要观察主要程序逻辑的内容。

打开 “src/com/demo/android/bmi” 目录下的 "Bmi.java"档 桉 ,Eclipse+Android 开发工具已经帮我们预先建立好了基本的程序逻辑。

其 预设的内容如下: package com.demo.android.bmi; import android.app.Activity; import android.os.Bundle; public class Bmi extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } 比起什么标签都对称的 XML 界面描述档来说,这个以 Java 程序语言写成的档桉虽然 篇幅短,但反而要难读得多。

我们将程序直接拆开,分成几个部份来讲解这个 “Bmi.java” 档桉的内容: 第 1 行:package com.demo.android.bmi;这一行的作用是指出这个档桉所在的名称空间。

“package”(包)是其关键字。使用名称 空间的原因是程序一旦扩展到扩展到某个大小,程序中的变数名称、 方法名称、类别名称难免重复, 这时就可以将定义的名称区隔管理在 package 下,以避免相互冲突的情形发 生 。

Java 的 package 设计成与档桉系统结构相对应,如我们的 package 设定是 “package com.demo.android.bmi” ,则这个类别就该在指定目录的"com/demo/android/bmi" 路径下可以找到。

同时也别忘了 Java 程序语言每段叙述语句的结尾处,与大部分的程序语言一样需加 上 一个分号";",以表示一行程序叙述的结束。import android.app.Activity; import android.os.Bundle;程序中预设导入了 “android.app.Activity"跟"android.os.Bundle” 两个 Package,在 所有的 Android 应用程序中都会用到这两个 Package。

“import”(导入)是用作导入 Package 的关键字。

在 Java 语言中,使用到任何 API 前都要事先导入相对应的 Package。

我们马上将学到这两个 Package 的用途。

Android 支援的 Package 与标准的 Java(j2se) 不尽相同。

在写 Android 应用程序时,你偶而可能需要参考可用的 API 列表,以确认使用到的 Package 是否有内建支援。

后续章节中也将讲解如何透过新增 "jar"档来呼叫额外的 Package。

完整的 API 可查阅官方的 package 列表:

第 6,13 行public class Bmi extends Activity { }第 6 行开始了程序的主体。其组成是这样的:

public class Bmi

"Bmi"是这个类别的名称。

"class"则是用作宣告类别关键字。

"public"关键字是用来修 饰"Bmi"这个类别。

表示 "Bmi"是个"公开"的类别,可以从 package 外部取用。

"public class Bmi"后面再加上"extends Activity"叙述,则表示 "Bmi"这个类别的功能、型别等全继承自 "Activity"类别。

"extends"是继承(Inherit) 类别的关键字。

"Activity"是 来自于我们在第 3 行刚导入的 Package。

因此整句话的含意即:"宣告一个公开的 Bmi 类别。

这个 Bmi 类别继承了程序开头 导 入的 Activity 类别"。

"{}"大括号规范了一个程序区块。大括号中的程序表达的这个程序区块的主要内容。

第 7 行:/** Called when the activity is first created. */第 7 行提供了位于其下的函式的注释。

“/* */” 是 Java 语言的多行注解符号,位于其中的文字内容不会被编译。

"/"叙述后多出来的一个 ""号被视为内文。

顺便提醒一下, Java 程序语言中两个斜线 "//"表示的是单行注解符号。

单行注解符号 "//"与多行注解符号 "/* */“不同的地方是,只有与”//"符号同行的文字才不会被编译。@Override public void onCreate(Bundle savedInstanceState) { }第 9 行开始了这个方法 (Method)的主体。其组成是这样的:public void onCreate(Bundle savedInstanceState) { }"onCreate"是这个方法的名称。"void"则是宣告了这个方法的回传值的型别 (type)。

"public"关 键字是用来修饰 "onCreate"这个方法。

表示 "onCreate"是个"公开"的方法,可以由 bmi 类别外部取用。

方法的回传值的型别,即是这个方法的型别。

"onCreate"这个方法使用 "void"型别,表示"onCreate"这个方法不需回传值。

同时,这个方法传入了一个名为"savedInstanceState"的"Bundle"型别参数,"Bundle"型别正是来自我们前面所导入的 Package 之一。

我们并不需要知道太多 "Bundle"型别或"savedInstanceState"实体的细节,只要知道 "Bundle"的内容与手机平台的记 忆 体管理有关 。

当 Android 应用程序启动、换到背景等待、关闭时,都会用到 "savedInstanceState"这 个实体来处理记忆体相关的事宜。

当然,你也可以用其他名称来代替它。

还好 "onCreate"这个方法永远都 是传入"Bundle savedInstanceState"这个参数,写应用程序时只要正确照规定传入即可,你可以不用太去在意它。

给对 Bundle 是什么有兴趣的读者:

"Bundle"可以保存程序上一次关闭(冻结)时的状态。

你可以透过覆写 onFreeze 方法(与 onCreate 方法的作用类似 ) 来保存冻结前的状态。

当程序启动(Activity 重新初始化)时, 会再次呼叫 onCreate 方法,你就能从 savedInstanceState 中得到前一次冻结的状态。

我们 也可以透过"Bundle"来将这个 Activity 的内容传到下一个 Activity 中。

之后讲Activity 时,也会讲解 onCreate/onFreeze 等方法的关系。

"{}"大括号规范了一个程序区块。

大括号中的程序表达 onCreate 这个程序区块的主要 内容。@Override public void onCreate(Bundle savedInstanceState)从前面的讲解中,我们学到了在任何一个 Android 专桉目录里,只要打开"Referenced Libraries"目录的"android.app" 分类,都可以找到 "Act ivity.class"这个类别。

现在我们再深入 一些查看"Act ivity.class" 类别。

你要做的,只是依照图示,找到 Android 工具中的 “Referenced Libraries” 目录,从"android.app" 分类里找到"Act ivity.class"类别,并按下 "Act ivity.class"类别左侧的三角形图示,如 此即可展开这个类别的属性 /方法列表。

我们在 Activity 类别的属性 /方法列表中,发现了现在正要讲解的 onCreate 方法 (Method)。

因为"bmi" 类别继承自 Activity 类别,所以预设"bmi" 类别中其实已经有 "onCreate"方法了。

事实上,“onCreate” 方法正是每个 Activity 类别初始化时都会去呼叫的方法。

“@” 开头的语句表示装饰子 (decorator)语 句 ,“@Override"语句的作用是告诉程序我们要覆写这个"onCreate” 方法。

当我们打开程序时,程序不再使用从"bmi" 类别中继承来的 “onCreate” 方法,而是使用我们在程序中自订的行为。@Override public void onCreate(Bundle savedInstanceState) { }我们讲解了整段程序,其含意是"覆写 bmi 类别中公开的 onCreate 方法。

这个 “onCreate"方法无回传值型别,而且这个方法传入了一个名为"savedInstanceState” 的 Bundle 型别参数。

现在来看看"onCreate" 方法中包含的程序内容。super.onCreate(savedInstanceState);"super"是关键字。

代表着这个 "Bmi"类别的上层类别 (Activity)。 “super.onCreate(savedInstanceState);” 的意思就是:“执行 Activity 类别中 onCreate 方法的内容”。

这麽做的目的是什么呢?

Google Android 将其应用程序的界面称为视图 (View),而负责控制各种动作行为的程 序主体(Controller),则称为活动 (Activity)。因此一个 Android 应用程序,必定会对应 到一个以上的 Activity。

“onCreate” 方法则是每个 Activity 类别初始化时都会去呼叫 的方法。

我们想做的事,是保持原本 “onCreate” 方法预设的动作,然后在其中加入我们想 要的内容。

而 Android 产生的程序预设却覆载(@Override) 了"Bmi" 类别的"onCreate" 方法。

原 本继承自"Activity"类别的"onCreate"方法,其原本内容都被覆载掉了。

因此想将原本的 "onCreate"方法内容保留,并在 其中加入我们的内容的话,就要使用"super"语句。

当程序运 行到我们覆写的 "onCreate"方法时,透过"super.onCreate(savedInstanceState);"语句,会先将 原本"Act ivity"类别中的"onCreate"方法 执行一次,然后再执行我们覆写的 "onCreate"方法里 面其他的程序内容。

我们要执行原本的 "onCreate"方法时,仍然需要提 供原本"onCreate"方法所需的传入参 数。

因此"super.onCreate(savedInstanceState);"语句中,我们将"savedInstanceState"这个参数 传入原本的"onCreate"函式中。

"savedInstanceState"是我们 在"public void onCreate(Bundle savedInstanceState)"语句中所宣告的传入参数。setContentView(R.layout.main);透过萤幕显示的各种元素是按照界面层次结构来描述的。

要将一个显示元素的层次结构 转换显示到一个萤幕上, Activity 会呼叫它用来设定 View 的 “setContentView” 方法,并传入想引用的 XML 描述文件。

当 Activity 被启动并需要显示到萤幕上时,系统会通知 Activity ,并 根 据 引用 的 XML 文件叙述来描绘出使用者界面。

上一章中我们定义好的 res/layout/main.xml 描述档,就是透过这个机制绘出到萤幕上。

setContentView 方法也可以在 Activity 类别中找到。

你可能也注意到 “setContentView” 方法确实是透过 "R.layout.main"来引用 XML 文件 描述档的资源,而不是直接透过 res 目录来引用。