第3章 程序设计基础
在上一章的学习中,主要了解了Eclipse+ADT的开发流程,对其有了初步的认识和了解。对初学者来说,这一章的内容比较烦琐,但是又必须掌握,这也是进行Android开发必须经过的第一步,有了这个基础,我们将进行Android应用程序设计。
3.1 Android程序框架
上一章我们建立了AndroidTest项目,在项目中,所有的代码是由ADT插件自动生成的,我们并没有对其进行编码,所以没有对其框架进行分析。其实每一个平台都有自己的结构框架,比如,在最初学习Java或者C/C++时,第一个程序总是main方法,以及文件类型和存储方式等。本节将对Android平台的目录结构、文件类型及其负责的功能和Android平台的main方法进行剖析。
3.1.1 Android项目目录结构
有了前两章的基础,再来打开上一章建立的AndroidTest项目,分析其项目目录结构,对Android项目进一步深入了解。首先启动Eclipse,展开“Package Explorer”导航器中的“AndroidTest”项目,如图3-1所示。
图3-1 JDK安装检查Android项目结构
与一般的Java项目一样,src文件夹是项目的所有包及源文件(.java),res文件夹中则包含了项目中的所有资源,比如,程序图标(drawable)、布局文件(layout)、常量(values)等。下面来介绍其他Java项目中没有的gen文件夹中的R.java文件和每个Android项目都必须有的AndroidManfest.xml文件。
R.java是在建立项目时自动生成的,这个文件是只读模式,不能更改,R.java文件是定义该项目所有资源的索引文件。先来看看AndroidTest项目的R.java文件,如代码清单3-1所示。
代码清单3-1 R.java
package com.examples.android.helloactivity; 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=0x7f040001; public static final int hello=0x7f040000; } }
可以看到这里定义了很多常量,仔细一看就发现这些常量的名字都与res文件夹中的文件名相同,再次证明R.java文件中所存储的是该项目所有资源的索引。有了这个文件,在程序中使用资源将变得更加方便,可以很快地找到要使用的资源,由于这个文件不能被手动编辑,所以当在项目中加入了新的资源时,只需要刷新一下该项目,R.java文件便自动生成了所有资源的索引。
AndroidManfest.xml文件则包含了该项目中所使用的Activity、Service、Receiver,先来打开AndroidTest项目中的AndroidManfest.xml文件,如代码清单3-2所示。
代码清单3-2 AndroidManfest.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" package=" com.examples.android.helloactivity" android:versionCode="1" android:versionName="1.0" > <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".HelloActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
代码清单3-2中,intent-filters描述了Activity启动的位置和时间。每当一个Activity(或操作系统)要执行一个操作时,它将创建出一个Intent的对象,这个Intent对象能承载的信息可描述你想做什么,你想处理什么数据,数据的类型,以及一些其他信息。而Android则会和每个Application所暴露的intent-filter的数据进行比较,找到最合适Activity来处理调用者所指定的数据和操作。下面我们来仔细分析AndroidManfest.xml文件,如表3-1所示。
表3-1 AndroidManfest.xml分析
下面我们看看资源文件中一些常量的定义,如String.xml,如代码清单3-3所示。
代码清单3-3 String.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, HelloActivity!</string> <string name="app_name">HelloWorld</string> </resources>
这个文件很简单,就定义了两个字符串资源,与R.java中对应的索引如代码清单3-4所示。
代码清单3-4 R.java中的String类
public static final class string{ public static final int app_name=0x7f040001; public static final int hello=0x7f040000; }
在程序中装载并使用这个字符串资源如代码清单3-5所示。
代码清单3-5 String资源的使用
Resources r = this.getContext().getResources(); String appname = ((String) r.getString(R.string.appname)); String hello = ((String) r.getString(R.string.hello));
基本上可以定义出项目中所有使用的常量,例如颜色。所以,可根据需要对资源常量进行定义。下面是定义了颜色的常量colors.xml,如代码清单3-6所示。
代码清单3-6 colors.xml
<?xml version="1.0" encoding="utf-8"? <resources> <color name="status_idle">#cccccc</color> <color name="status_done">#637a47</color> <color name="status_sync">#cc9900</color> <color name="status_error">#ac4444</color> </resources>
现在我们来分析AndroidTest项目的布局文件(layout),首先打开res→layout→main.xml文件,如代码清单3-7所示。
代码清单3-7 main.xml
<?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/hello" /> </LinearLayout>
在代码清单3-7中,有以下几个布局和参数。
❑ < LinearLayout>:线性版面配置,在这个标签中,所有元件都是由上到下排成的
❑ android:orientation:表示这个介质的版面配置方式是从上到下垂直地排列其内部的视图。这里“vertical”表示是水平排列。
❑ android:layout_width:定义当前视图在屏幕上所占的宽度,fill_parent/ match_parent即填充整个屏幕。
❑ android:layout_height:定义当前视图在屏幕上所占的高度,fill_parent/ match_parent即填充整个屏幕。
❑ wrap_content:随着文字大小的不同而改变这个视图的宽度或高度。
❑ layout_weight :用于给一个线性布局中的多个视图的重要度赋值。所有视图都有layout_weight值,默认为零,即需要显示多大的视图就占据多大的屏幕空间。如果值大于零,则将父视图中的可用空间分割,分割大小具体取决于每一个视图的layout_weight值和该值在当前屏幕布局的整体layout_weight值,以及在其他视图屏幕布局的layout_weight值中所占的比例。
在这里,布局中设置了一个TextView,用来配置文本标签Widget,其中设置的属性android:layout_width为整个屏幕的宽度,android:layout_height可以根据文字来改变高度,而android:text则设置了这个TextView要显示的文字内容,这里引用了@string中的hello字符串,即String.xml文件中的hello所代表的字符串资源。hello字符串的内容"Hello World, HelloActivity!"这就是在AndroidTest项目运行时看到的字符串。
最后,分析AndroidTest项目的主程序文件HelloActivity.java,如代码清单3-8所示。
代码清单3-8 HelloActivity.java
package com.examples.android.helloactivity; import android.app.Activity; import android.os.Bundle; public class HelloActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
主程序HelloActivity类继承自Activity类,重写了void onCreate(Bundle savedInstance State)方法。在onCreate方法中通过setContentView(R.layout.main)设置了Activity要显示的布局文件(/layout/main.xml)。
到这里,是不是明白了为什么在创建项目时没有进行编码就可以直接运行程序呢?当然,这也是Android开发的特点,这样可以很轻松地将代码和UI分开,在国际化和程序维护方面有着巨大的作用。如果Android程序需要自适应国际化,比如说多国语言等问题,那么就可以定义不同语言的UI布局,在程序装载时调用不同的布局。而且,如果需要修改UI的一些问题,就不必查看代码了,直接更改这些布局文件即可,是不是很方便?当然,这需要开发者在开发时使用这种MVC框架,尽量减少使用“硬编码”。
3.1.2 Android应用解析
上面了解了Android应用程序的目录结构和其中每个文件的功能,要对其进行应用开发,还需要对Android应用构造进行深入的分析。下面介绍一下Android应用开发中所应用到的重要模块。
❑ Activity
❑ Intent
❑ Content Provider
❑ Service
当然,并非所有的Android应用程序都必须由这4部分组成,它可以根据开发者需求来进行组合,比如,上面建立的HelloActivity项目就只使用了Activity这一个模块。但是,对于任何一个应用程序来说,都必须在AndroidManfest.xml文件中声明使用到的模块。
1.Activity
Android中最基本的模块就是Activity,在之前的HelloActivity项目中已经使用过。我们称之为“活动”,在应用程序中,一个活动(Activity)通常就是一个单独的屏幕。每一个活动都被实现为一个独立的类,并且继承于从活动基类,活动类将会显示由视图控件组成的用户接口,并对事件作出响应。例如,HelloActivity项目中的HelloActivity.java即继承了活动(Activity)类。大多数的应用都是由多个Activity显示组成,例如,对一个文本信息应用而言,第一个屏幕用来显示发送消息的联系人列表,第二个屏幕用来写文本消息和选择收件人,第三个屏幕查看消息历史或者消息设置操作等。
这里的每一个屏幕就是一个活动,很容易实现从一个屏幕到一个新的屏幕,并且完成新的活动。当一个新的屏幕打开后,前一个屏幕将会暂停,并保存在历史栈中。用户可以返回到历史栈中的前一个屏幕,当屏幕不再使用时,还可以从历史栈中删除。
简单理解,Activity就代表着用户所能看到的屏幕,它主要处理了应用程序的整体性工作,例如,为用户显示指定的View,启动其他Activity,监听系统事件(按键事件、触摸屏事件等)等。
所有应用的Activity都继承于Android提供的基类android.app.Activity类,其他的Activity继承该父类后,通过父类的方法来实现各种功能,这种设计在其他领域也较为常见。
2.Intent
在Android中,利用Intent类实现了在Activity1与Activity2之间的切换(以及启动Service等)。Intent类主要用于描述应用的功能。在Intent的描述结构中,有动作(Action)和动作对应的数据(data)两个最重要的部分。其中,典型的动作类型有:MAIN、VIEW、PICK、EDIT等,而动作对应的数据以URI的形式表示。例如,如果要查看一个人的联系方式,需要先创建一个动作类型为VIEW的Intent,以及一个表示这个动作对应的数据(这里就是这个人)的URI。
从一个屏幕导航到另一个屏幕,我们需要解析各种Intent。为了实现向前导航,首先,我们调用Activity的startActivity(Intent myIntent)方法。此时,系统为找到最匹配myIntent的Intent对应的Activity,就将在所有已安装的应用程序中定义的IntentFilter中查找。而匹配的对应的新的Activity在接收到myIntent的通知后,开始运行。当startActivity方法被调用时,将触发解析myIntent的动作,该机制提供了两个关键好处。
(1)Activities能够重复利用从其他组件中以Intent的形式产生的请求。
(2)Activities可以在任何时候被具有相同Action的新的Activity取代。
下面举例说明两个Activity之间的切换。运行效果:当应用程序启动时,显示布局main.xml,如图3-2所示,当我们单击“切换”按钮时,屏幕显示布局main2.xml,如图3-3所示,再单击“切换”按钮,又回到如图3-2 所示的状态。就这样通过Intent完成了两个Activity之间的切换。
图3-2 Activity1
图3-3 Activity2
以下是两个Activity的代码。
代码清单3-9 Activity1.java
package com.example.android.Examples_03_01; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; / ** * 在Examples_02_01项目中一共使用了两个Activity, * 每使用一个Activity,都必须在AndroidManifest.xml中 * 进行声明 */ public class Activity1 extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 设置显示main.xml布局 */ setContentView(R.layout.main); /* findViewById(R.id.button1)取得布局main.xml中的button1 */ Button button = (Button) findViewById(R.id.button1); /* 监听button的事件信息 */ button.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /* 新建一个Intent对象 */ Intent intent = new Intent(); /* 指定intent要启动的类 */ intent.setClass(Activity1.this, Activity2.class); /* 启动一个新的Activity */ startActivity(intent); /* 关闭当前的Activity */ Activity1.this.finish(); } }); } }
代码清单3-10 Activity02.java
package com.example.android.Examples_03_01; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class Activity2 extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 设置显示main2.xml布局 */ setContentView(R.layout.main2); /* findViewById(R.id.button2)取得布局main.xml中的button2 */ Button button = (Button) findViewById(R.id.button2); /* 监听button的事件信息 */ button.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /* 新建一个Intent对象 */ Intent intent = new Intent(); /* 指定intent要启动的类 */ intent.setClass(Activity2.this, Activity1.class); /* 启动一个新的Activity */ startActivity(intent); /* 关闭当前的Activity */ Activity2.this.finish(); } }); } }
代码清单3-11 main.xml
<?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" > <Button android:id="@+id/button1" android:layout_width="100px" android:layout_height="wrap_content" android:layout_x="100px" android:layout_y="80px" android:text="跳转到Activity2" > </Button> </LinearLayout>
代码清单3-12 Main2.xml
<?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> <Button android:id="@+id/button2" android:layout_width="100px" android:layout_height="wrap_content" android:layout_x="100px" android:layout_y="80px" android:text="返回Activity1" > </Button> </LinearLayout>
如代码清单3-9所示,需要在AndroidManifest.xml中声明使用的Activity2,如代码清单3-13所示。
代码清单3-13 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.Examples_03_01" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Activity1" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="Activity2"></activity> </application> <uses-sdk android:minSdkVersion="8" /> </manifest>
问:Android应用如何对外部事件(如当电话呼入时,或者数据网络可用时,或者到了晚上时)做出响应?
答:使用IntentReceiver。
❑ 在相应的事件发生时,IntentReceiver使用NotificationManager通知用户,但它并不能生成UI。
❑ 它在AndroidManifest.xml中注册,也可以在代码中使用Context.registerReceiver()进行注册。
❑ 当IntentReceiver被触发时,应用不必对请求调用IntentReceiver,系统会在需要时启动应用。
❑ 各种应用还可以通过使用Context.broadcastIntent()将它们自己的intentreceiver广播给其他应用。
3.Content Provider
Android应用能够将它们的数据保存到文件和SQLite数据库中,甚至是任何有效的设备中。利用Content Provider实现应用数据与其他的应用共享。因为Content Provider类实现了一组标准的方法,能够让其他的应用保存或读取此内容提供器处理的各种数据类型。
Content Provider可以理解为在不同的应用包之间共享数据的工具。在Android中,默认的系统数据库为SQLite。但是在Android中,使用方法略有区别。在Android中,每一个应用都单独运行在各自的进程中,当一个应用需要访问其他应用的数据时,这里就需要在不同的虚拟机之间传递数据,这样的情况操作起来可能有些困难(正常情况下,你不能读取其他应用的db文件)。Content Provider就解决了上述困难。
在Android中,Content Povider是一个特殊的存储数据的类型,它提供了一套标准的接口用来获取和操作数据。并且,Android自身也提供了现成的Content Provider:Contacts、Browser、CallLog、Settings、MediaStore。当通过Content Resolver提供的方法来使用Content Provider时,应用可以通过唯一的Content Resolver interface来使用具体的某个Content Provider。其中,Content Resolver提供的方法包括query()、insert()、update()等。要使用这些方法,还会涉及URI。可以将它理解成string形式的Content Provider的完全路径。
4.Service
Service即“服务”的意思,既然是服务,那么Service将是一个生命周期长而且没有用户界面的程序。比如,一个正在从播放列表中播放歌曲的媒体播放器,在这个媒体播放器应用中,应该会有多个Activity,让使用者可以选择歌曲并播放歌曲。然而,音乐重放这个功能并没有对应的Activity,因为使用者会认为在导航到其他屏幕时音乐应该还在播放。在这个例子中,媒体播放器这个Activity会使用Context.Startservice()来启动一个Service,从而可以在后台保持音乐的播放。同时,系统也将保持这个Service一直执行,直到这个Service运行结束。另外,还可以通过使用Context.Bindservice()方法连接到一个Service上(如果这个Service当前还没有处于启动状态,则将启动它)。当连接到一个Service之后,还可用Service提供的接口与它进行通信。以媒体播放器为例,还可以执行暂停、重播等操作。
3.2 Android程序UI设计
在前面章节的例子中,我们已经接触了TextView、Button等UI控件,其实这里所说的UI就是布局文件(layout),UI是一个应用程序展现给用户的界面,一个应用程序要想受用户喜爱,那么UI可不能差。自从Android SDK 1.0_r2版本开始,ADT提供了UI预览的功能。现在只需要打开一个Android项目的“/res/layout/main.xml”并右键单击鼠标右键,在弹出的快捷菜单中选择“Open With”→ “Android Layout Editor”命令,或者直接双击main.xml文件,即可以切换到UI设计界面,如图3-4所示。
图3-4 Android Layout Editor命令
左边的layouts标签的内容则是一些线性布局,可以使用它轻松地完成对布局的排版,比如,横向或者纵向布局。Views标签则是一些UI控件,可以将这些控件直接拖动到右边的窗口进行编辑,这些UI控件的类型如图3-5所示。
图3-5 Android layout Editor
当然,还可以单击右下角的main.xml标签来切换到XML编辑器,对代码进行编排,如图3-6所示。将这些功能配合起来使用,基本可以满足开发者需求。
图3-6 XML编辑器
3.3 Java语言在Android程序中的使用
Android应用程序采用Java语言编写,Java语法和C/C++有很大的相似性,但也有一些特别之处。
3.3.1 Interface的使用
从名字上看,Interface即为接口的意思,多用于实现回调(Call Back)方法。
在Interface的定义中,一般的代码架构如代码清单3-14所示。
代码清单3-14 InterfaceServer.java
public class InterfaceServer { public interface OnClickListener{ public void onClick(); } private OnClickListener mOnClickListener=null; public void onClick(){ if(mOnClickListener!=null) mOnClickListener.onClick(); } public void setOnClickListener(OnClickListener l){ mOnClickListener = l; }
对于Interface内部的方法而言,只需要声明,而不需要具体实现。从编译器的角度来看,Interface会被认为是一个指向方法的指针。
使用InterfaceServer的代码一般如代码清单3-15所示。
代码清单3-15使用InterfaceServer
public void addToButton { Button b = (Button)findViewById(R.id.button); onClickListener l = new OnClickListener(){ public void onClick(View v){ TextView tv1 = (TextView) findViewById(R.id.tv1); tv1.setText("The Button has been clicked"); } }; b.setOnClickListener(l); }
3.3.2 abstract class的使用
abstract是一个修饰符,其类似于Atatic这样的关键字。Android程序中常用abstract修饰一个类,如abstract class,当然能它也可以修饰一些变量或是方法。
抽象类所包含的方法可以只是定义,也可以是已实现的。对于没有实现的方法,基于该方法的子类必须实现;而对于已经实现的方法,子类可以重写该方法,若没有重写,则使用父类的方法。
在一定程度上,abstract class可以代替Interface,例如,3.3.1节中Interface的例子做如下的abstract class替换,其效果是等价的。
代码清单3-16 InterfaceServer.java
public class InterfaceServer { abstract class OnClickListener2{ public void onClick2(); } private OnClickListener2 mOnClickListener2=null; public void onClick2(){ if(mOnClickListener2!=null) mOnClickListener2.onClick2(); } public void setOnClickListener2(OnClickListener2 l){ mOnClickListener2 = l; }
3.3.3 Interface与abstract class的区别
从语法角度讲,接口和抽象类有以下区别。
❑ Java语法规定,一个子类只能有一个父类,但可以实现多个接口。
❑ abstract class可以代替Interface。
❑ 定义Interface时,只需要列出所包含方法的定义而不必实现。而定义Abstract类时,方法必须有实现部分,这就是所谓的默认实现,除非该方法也是Abstract类型。
❑ 接口的子类必须实现接口所定义的全部方法,而抽象类的子类不必实现抽象类所定义的任何方法,除非该方法是Abstract或者子类想重写某个方法。
❑ 接口中的成员变量必须是Static Final类型(实际应用中则很少包含变量,因为接口多用于引用),而abstract class内部可以包含任意变量。
从应用的角度来讲,Interface和abstract class的区别在于:Interface提供了一个方法集合的接口,该接口用于客户端和服务端的方法调用,如图3-7所示。
图3-7 Interface的使用机制
……
接口一般是由服务端定义,比如操作系统,客户端根据自己的需求对接口做不同的实现;而abstract class则仅提供了一个基类,该基类没有任何服务端或者客户端的概念,它的作用就是为了继承并重写,如图3-8所示。
图3-8 abstract class的使用机制
3.3.4 for循环的使用
除了传统的for循环(语法是for(inti=0;i<N;i++){};)以外还有foreach循环。举例如代码清单3-17所示。
代码清单3-17 For循环语法
public void doSomething(){ int[] ages = new int[20]; for(int age:ages){ //to do,add something to process age. Log.i("Haiii" . "The age is" +age); … } }
该for语法是对某个集合进行循环,第1 个参数是循环过程集合元素值的引用,第2个参数是集合对象。第1个参数的类型必须和集合元素的类型相同。
以下代码示例了传统的for语法和新for语法的等效使用,但由于编译器对新for语法的优化,其执行效率将更高。
代码清单3-18传统for语法和新for语法比较
public void doSomething(){ int[] counts =new int[20]; int total1=0,total2=0; for(int i=0;i<20;i++) total1 += counts[i]; for(int count:counts) total2 += count; }
3.3.5 Map类的使用
在Android系统中,有着多种存储数据的方式,例如,文件、数据库及程序内参数式存储、网络存储等。对于参数式存储时,使用的就是Map类。Map本身是Interface,Java基于该接口实现三个具体的Map类,分别是HashMap、TreeMap,以及EnumMap,常用的为HashMap,本节也主要介绍HashMap。
Map定义了访问特定集合的标准方法,这种集合用来存储key-value类型的键值对,比如,对于name:Haiii和age:22这两组数据来讲,其中name、age称为键(key),与此对应的是键值(value)。在一个Map集合类中,每对键或值其类型都可以是任意的,比如int、String等都是可以的。
Map类又是一个类模板,一个Map类对象在初始化时必须指定键的类型,可以是任何Object类,比如,Map<String,Object> mMap= new HashMap<String,Object>()。
<>里面的数据类型用于指定Map集合中“键值对”的类型。
给Map集合添加和删除键值对的方法如表3-2所示。
表3-2 Map集合添加和删除键值对的方法
Map类没有提供直接遍历键值对的方法,要遍历所有键值对需要一个中间过程。Map提供了3个方法用于间接遍历键值对,如下:
❑ entrySet() 返回所有键值对类型为Set对象。
❑ keySet() 返回所有键值对类型为Set对象。
❑ valueSet() 返回所有键值对类型为Collection对象。
要得到具体的键值对,需要再解析Set和Collection对象,但仅有这两个对象还不能获得键值对,还需要借助于Iterator类。到这里,可能觉得有些复杂,别着急,结果马上就要出来了。
Set、Collection、Iterator实际上是Map内部进行操作的3个辅助类,要得到具体Map键值对,如代码清单3-19所示。
代码清单3-19得到具体的Map键值对
Map<String,Object> mMap = new HashMap<String,Object>(); Iterator kv = mMap.entrySet().iterator(); Iterator k = mMap.keySet().iterator(); Iterator v = mMap.values().iterator(); Int size = mMap.size(); for(int i = 0;i<size;i++) { Map.Entryentry = (Map.Entry)kv.next(); Object key = entry.getKey(); Object value = entry.getValue(); }
用以上代码读取键值对时,Object可以强制转换为int类型。
3.3.6 Integer与String之间的转换
在实际程序设计中,经常需要把Integer类型转换为String类型,或者相反,Java类库中提供了这样的方法。
❑ 整形转换为字符串:String.valueOf(Stringstr)。
❑ 字符串转换为整形:Integer.parseInt(int)、Interger.parseLong(long)等。
面向对象编程中,一般不能直接调用类中的方法,而是需要先定义类的一个对象,然后才能使用其包含的方法。而以上两个方法直接调用String和Integer的方法,这就是static关键字的作用。
在定义一个类时,如果其中的方法声明为static,那么外部程序就可以直接调用该方法,该方法所引用的一些变量也必须为static类型的变量。
Java内部有一种安全机制:对于一个普通的类,必须声明该类的对象才能访问类中的方法或者变量,实现这种安全机制的是Java编译器。编译器隐藏了所有类的地址,因此不能通过类名称定位到类的地址;而如果使用static修饰符,无论是变量还是方法,编译器都会把该变量或者方法的名称导出,以便程序能够根据类名定位到类所在的地址,从而能够调用相应的方法或者变量。
3.3.7 synchronized同步关键字
synchronized关键字属于操作系统的范畴,与同步对应的是异步。在程序设计的概念中,有同步调用或者异步调用,同步是指该段代码(方法)从调用开始,直到内部执行完毕后才能返回;异步调用是指调用该段代码(方法)后立即返回,无论该段代码内部所执行的物理操作是否执行完毕,异步代码一般存在于多线程程序设计中,单线程程序内部不存在异步调用。
synchronized关键字的作用就是告诉操作系统,在执行该关键字所限定的代码片段内,不允许被其他线程打断。在一般的操作系统设计中,会提供一个类似于synchronized的API方法,而Java则是给了这样一个关键字,相当于说,Java编译器为操作系统分担了一部分工作。
那么,什么情况下需要使用synchronized关键字呢?凡是需要某段代码在执行时不被其他线程打断时,都可以加上synchronized关键字,举例如代码清单3-20所示。
代码清单3-20 synchronized关键字
public class MyMusicWidgetProvider{ private static MyMusicWidgetProvider sInstance; static synchronized MyMusicWidgetProvider getInstance(){ if(sInstance==null) sInstance=new MyMusicWidgetProvider(); returnsInstance; } }
以上代码定义了一个类,并希望该类在运行时仅有一个实例,每次调用该类所包含的方法时,先得到该类的实例,然后再通过实例调用其他方法。该类有一个static的getInstance()方法,该方法的作用就是检查是否存在一个实例,如果没有就创建一个,否则返回存在的实例。
getInstance()方法前面加了synchronized关键字,为的是该方法在执行时不能被其他线程打断,那么,为什么有这样的要求呢?试想一下,假设没有使用该关键字,第1个线程A在执行getInstance()方法时,方法体中的new运算符刚刚创建了一个MyMusicWidgetProvider实例,但还没来得及把实例赋值给sInstance,而此时B线程又来调用getInstance()方法,出现这种情况的原因是:sInstance= new MyMusicWidgetProvider()这句代码会被Java编译器编译成多条机器指令,其中给sInstance赋值的机器指令和创建一个MyMusicWidgetProvider对象的机器指令是分开的。此时对于B线程来讲,它会重新检查sInstance是否为空,由于A线程还没有来得及给sIntance赋值,因此,B线程就会再次创建一个新的MyMusicWidgetProvider实例,当B线程暂停并返回A线程时,A线程会把第1次创建的实例重新赋值给sInstance,这就会导致B线程将来对MyMusicWidgetProvider实例的错误引用,因为B线程所创建实例的地址被A线程修改了。
因此,这就要求getInstance()一旦开始执行,必须执行完毕后才能返回,中间不能被其他线程打断。
sychronized除了约束整个方法外,也可以约束一小段代码,这样做的好处有两个:一个是可以避免定义新方法;另一个是能够避免一些线程同步问题,如代码清单3-21所示。
代码清单3-21 synchronized约束小段代码
Object obj;
int c;
void addMethod(){
synchronized(obj){
c++;
//其他代码
}
}
obj可以是任意类型的一个对象,该对象的唯一作用就是给synchronized提供一个“锁”。同步设计的原则之一是尽量减少同步中包含的代码大小,从而在线程间能够更平衡地运行。因此,同步一小段代码是一个不错的做法。同时,假设addMethod()内部还调用了其他同步方法,那么,从这个同步跳到另外的同步会增加线程间死锁的风险,因此不同步整个addMethod()而仅同步局部代码,这就是第2个好处。
3.3.8 new的使用
Java语言中,new的作用是为一个对象(Object)分配内存,代码清单3-22所示代码说明了为各种Object分配内存的方法。
代码清单3-22为各种Object分配内存的方法
int a = 20; int A[]= new int[100]; float A2[]= new float[100]; int A3[]= {10,20,30}; String str = new String(); String str1 = ; String str2 = null; String[] Str = new String[100]; MyMusicWidgetProvider myProvider = new MyMusicWidgetProvider(); str1 += "Android is from... "; MyMusicWidgetProvider commonProvider= myProvider.getInstance()
一般情况下,没有用new修饰符定义的数据都是在栈(Stack)中分配内存,但有一个例外,对于String定义的变量,总是从系统内存堆(Heap)中分配内存,栈中仅有对该String的引用。另外,从系统堆中分配的实际内存大小并不是按指定的大小分配的,比如,int A[]=new int[100]所分配的内存大小并不是100B,而是128B,内存分配机制为了提高分配效率以及分配算法的可实现性,实际上的内存颗粒大小会按照2的幂次方进行划分,实际分配的内存大小是最接近指定大小的一个值。另外,最小的内存颗粒大小会根据不同的内存分配算法有所不同,一般会取512B或者1KB。
问:在Android上使用Java与在PC上使用Java的编程风格有何差别?
答:Android是一种嵌入式系统,在追求程序效率、降低所需资源上是孜孜不倦的,程序员心中始终要有该意识。在PC上进行编程时,清晰完整的程序结构比运行效率显得更重要,因此,程序结构上经常呈现多层结构,比如getter、setter等;而在Android上编程时,尽管需要清晰的程序结构,但对于一些小的结构,则是效率重于结构。因此,能在一个函数或引用中实现的功能就要避免进行多层嵌套调用。
3.4 本章小结
本章主要介绍了Android应用程序框架、UI设计,以及Java在Android程序中的使用。首先彻底地分析了上一章的AndroidTest项目,从其项目目录结构、文件功能等方面分析了Android应用程序的基本框架,其次逐一分析了Android应用程序的构成,并分别通过示例程序演示了其功能的运用。其次介绍了有关UI设计的工具,使得程序界面更加漂亮。最后介绍了在Android程序中Java语法的使用。
关键知识点测评
1.以下有关Android程序目录结构的说法,不正确一个是( )。
A.Android程序中包含其他Java项目中没有的gen文件夹
B.AndroidManfest.xml文件则包含了该项目中所使用的Activity、Service、Receiver
C.程序的布局文件(layout)中配置了程序的线性版面,视图位置、高度等
D.R.java文件可以更改,被编辑
2.以下有关Android应用的叙述,正确的一个是( )。
A.所有的Android应用程序都有Activity、Intent、Content Provider、Service 4个模块构造而成
B.一个活动(Activity)通常就是一个单独的屏幕,它不需要继承其他基类
C.Intent可以实现Activity与Activity之间的切换件
D.Service能够让其他的应用保存或读取此内容提供器处理的各种数据类型
3.以下有关interface与abstract class区别的描述,不正确的是( )。
A.定义abstract类时,所有方法不需要有实现部分
B.一个子类只能有一个父类,但可以实现多个接口
C.接口中的成员变量必须是static final,而Abstract class内部可以包含任意变量
D.接口的子类必须实现接口所定义的全部方法,而抽象类的子类不必实现抽象类所定义的任何方法