官术网_书友最值得收藏!

第3章 程序設(shè)計(jì)基礎(chǔ)

在上一章的學(xué)習(xí)中,主要了解了Eclipse+ADT的開發(fā)流程,對(duì)其有了初步的認(rèn)識(shí)和了解。對(duì)初學(xué)者來(lái)說(shuō),這一章的內(nèi)容比較煩瑣,但是又必須掌握,這也是進(jìn)行Android開發(fā)必須經(jīng)過(guò)的第一步,有了這個(gè)基礎(chǔ),我們將進(jìn)行Android應(yīng)用程序設(shè)計(jì)。

3.1 Android程序框架

上一章我們建立了AndroidTest項(xiàng)目,在項(xiàng)目中,所有的代碼是由ADT插件自動(dòng)生成的,我們并沒(méi)有對(duì)其進(jìn)行編碼,所以沒(méi)有對(duì)其框架進(jìn)行分析。其實(shí)每一個(gè)平臺(tái)都有自己的結(jié)構(gòu)框架,比如,在最初學(xué)習(xí)Java或者C/C++時(shí),第一個(gè)程序總是main方法,以及文件類型和存儲(chǔ)方式等。本節(jié)將對(duì)Android平臺(tái)的目錄結(jié)構(gòu)、文件類型及其負(fù)責(zé)的功能和Android平臺(tái)的main方法進(jìn)行剖析。

3.1.1 Android項(xiàng)目目錄結(jié)構(gòu)

有了前兩章的基礎(chǔ),再來(lái)打開上一章建立的AndroidTest項(xiàng)目,分析其項(xiàng)目目錄結(jié)構(gòu),對(duì)Android項(xiàng)目進(jìn)一步深入了解。首先啟動(dòng)Eclipse,展開“Package Explorer”導(dǎo)航器中的“AndroidTest”項(xiàng)目,如圖3-1所示。

圖3-1 JDK安裝檢查Android項(xiàng)目結(jié)構(gòu)

與一般的Java項(xiàng)目一樣,src文件夾是項(xiàng)目的所有包及源文件(.java),res文件夾中則包含了項(xiàng)目中的所有資源,比如,程序圖標(biāo)(drawable)、布局文件(layout)、常量(values)等。下面來(lái)介紹其他Java項(xiàng)目中沒(méi)有的gen文件夾中的R.java文件和每個(gè)Android項(xiàng)目都必須有的AndroidManfest.xml文件。

R.java是在建立項(xiàng)目時(shí)自動(dòng)生成的,這個(gè)文件是只讀模式,不能更改,R.java文件是定義該項(xiàng)目所有資源的索引文件。先來(lái)看看AndroidTest項(xiàng)目的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;
      }
      }

可以看到這里定義了很多常量,仔細(xì)一看就發(fā)現(xiàn)這些常量的名字都與res文件夾中的文件名相同,再次證明R.java文件中所存儲(chǔ)的是該項(xiàng)目所有資源的索引。有了這個(gè)文件,在程序中使用資源將變得更加方便,可以很快地找到要使用的資源,由于這個(gè)文件不能被手動(dòng)編輯,所以當(dāng)在項(xiàng)目中加入了新的資源時(shí),只需要刷新一下該項(xiàng)目,R.java文件便自動(dòng)生成了所有資源的索引。

AndroidManfest.xml文件則包含了該項(xiàng)目中所使用的Activity、Service、Receiver,先來(lái)打開AndroidTest項(xiàng)目中的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啟動(dòng)的位置和時(shí)間。每當(dāng)一個(gè)Activity(或操作系統(tǒng))要執(zhí)行一個(gè)操作時(shí),它將創(chuàng)建出一個(gè)Intent的對(duì)象,這個(gè)Intent對(duì)象能承載的信息可描述你想做什么,你想處理什么數(shù)據(jù),數(shù)據(jù)的類型,以及一些其他信息。而Android則會(huì)和每個(gè)Application所暴露的intent-filter的數(shù)據(jù)進(jìn)行比較,找到最合適Activity來(lái)處理調(diào)用者所指定的數(shù)據(jù)和操作。下面我們來(lái)仔細(xì)分析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>

這個(gè)文件很簡(jiǎn)單,就定義了兩個(gè)字符串資源,與R.java中對(duì)應(yīng)的索引如代碼清單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;
      }

在程序中裝載并使用這個(gè)字符串資源如代碼清單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));

基本上可以定義出項(xiàng)目中所有使用的常量,例如顏色。所以,可根據(jù)需要對(duì)資源常量進(jìn)行定義。下面是定義了顏色的常量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>

現(xiàn)在我們來(lái)分析AndroidTest項(xiàng)目的布局文件(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中,有以下幾個(gè)布局和參數(shù)。

? < LinearLayout>:線性版面配置,在這個(gè)標(biāo)簽中,所有元件都是由上到下排成的

? android:orientation:表示這個(gè)介質(zhì)的版面配置方式是從上到下垂直地排列其內(nèi)部的視圖。這里“vertical”表示是水平排列。

? android:layout_width:定義當(dāng)前視圖在屏幕上所占的寬度,fill_parent/ match_parent即填充整個(gè)屏幕。

? android:layout_height:定義當(dāng)前視圖在屏幕上所占的高度,fill_parent/ match_parent即填充整個(gè)屏幕。

? wrap_content:隨著文字大小的不同而改變這個(gè)視圖的寬度或高度。

? layout_weight :用于給一個(gè)線性布局中的多個(gè)視圖的重要度賦值。所有視圖都有l(wèi)ayout_weight值,默認(rèn)為零,即需要顯示多大的視圖就占據(jù)多大的屏幕空間。如果值大于零,則將父視圖中的可用空間分割,分割大小具體取決于每一個(gè)視圖的layout_weight值和該值在當(dāng)前屏幕布局的整體layout_weight值,以及在其他視圖屏幕布局的layout_weight值中所占的比例。

在這里,布局中設(shè)置了一個(gè)TextView,用來(lái)配置文本標(biāo)簽Widget,其中設(shè)置的屬性android:layout_width為整個(gè)屏幕的寬度,android:layout_height可以根據(jù)文字來(lái)改變高度,而android:text則設(shè)置了這個(gè)TextView要顯示的文字內(nèi)容,這里引用了@string中的hello字符串,即String.xml文件中的hello所代表的字符串資源。hello字符串的內(nèi)容"Hello World, HelloActivity!"這就是在AndroidTest項(xiàng)目運(yùn)行時(shí)看到的字符串。

最后,分析AndroidTest項(xiàng)目的主程序文件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方法中通過(guò)setContentView(R.layout.main)設(shè)置了Activity要顯示的布局文件(/layout/main.xml)。

到這里,是不是明白了為什么在創(chuàng)建項(xiàng)目時(shí)沒(méi)有進(jìn)行編碼就可以直接運(yùn)行程序呢?當(dāng)然,這也是Android開發(fā)的特點(diǎn),這樣可以很輕松地將代碼和UI分開,在國(guó)際化和程序維護(hù)方面有著巨大的作用。如果Android程序需要自適應(yīng)國(guó)際化,比如說(shuō)多國(guó)語(yǔ)言等問(wèn)題,那么就可以定義不同語(yǔ)言的UI布局,在程序裝載時(shí)調(diào)用不同的布局。而且,如果需要修改UI的一些問(wèn)題,就不必查看代碼了,直接更改這些布局文件即可,是不是很方便?當(dāng)然,這需要開發(fā)者在開發(fā)時(shí)使用這種MVC框架,盡量減少使用“硬編碼”。

3.1.2 Android應(yīng)用解析

上面了解了Android應(yīng)用程序的目錄結(jié)構(gòu)和其中每個(gè)文件的功能,要對(duì)其進(jìn)行應(yīng)用開發(fā),還需要對(duì)Android應(yīng)用構(gòu)造進(jìn)行深入的分析。下面介紹一下Android應(yīng)用開發(fā)中所應(yīng)用到的重要模塊。

? Activity

? Intent

? Content Provider

? Service

當(dāng)然,并非所有的Android應(yīng)用程序都必須由這4部分組成,它可以根據(jù)開發(fā)者需求來(lái)進(jìn)行組合,比如,上面建立的HelloActivity項(xiàng)目就只使用了Activity這一個(gè)模塊。但是,對(duì)于任何一個(gè)應(yīng)用程序來(lái)說(shuō),都必須在AndroidManfest.xml文件中聲明使用到的模塊。

1.Activity

Android中最基本的模塊就是Activity,在之前的HelloActivity項(xiàng)目中已經(jīng)使用過(guò)。我們稱之為“活動(dòng)”,在應(yīng)用程序中,一個(gè)活動(dòng)(Activity)通常就是一個(gè)單獨(dú)的屏幕。每一個(gè)活動(dòng)都被實(shí)現(xiàn)為一個(gè)獨(dú)立的類,并且繼承于從活動(dòng)基類,活動(dòng)類將會(huì)顯示由視圖控件組成的用戶接口,并對(duì)事件作出響應(yīng)。例如,HelloActivity項(xiàng)目中的HelloActivity.java即繼承了活動(dòng)(Activity)類。大多數(shù)的應(yīng)用都是由多個(gè)Activity顯示組成,例如,對(duì)一個(gè)文本信息應(yīng)用而言,第一個(gè)屏幕用來(lái)顯示發(fā)送消息的聯(lián)系人列表,第二個(gè)屏幕用來(lái)寫文本消息和選擇收件人,第三個(gè)屏幕查看消息歷史或者消息設(shè)置操作等。

這里的每一個(gè)屏幕就是一個(gè)活動(dòng),很容易實(shí)現(xiàn)從一個(gè)屏幕到一個(gè)新的屏幕,并且完成新的活動(dòng)。當(dāng)一個(gè)新的屏幕打開后,前一個(gè)屏幕將會(huì)暫停,并保存在歷史棧中。用戶可以返回到歷史棧中的前一個(gè)屏幕,當(dāng)屏幕不再使用時(shí),還可以從歷史棧中刪除。

簡(jiǎn)單理解,Activity就代表著用戶所能看到的屏幕,它主要處理了應(yīng)用程序的整體性工作,例如,為用戶顯示指定的View,啟動(dòng)其他Activity,監(jiān)聽系統(tǒng)事件(按鍵事件、觸摸屏事件等)等。

所有應(yīng)用的Activity都繼承于Android提供的基類android.app.Activity類,其他的Activity繼承該父類后,通過(guò)父類的方法來(lái)實(shí)現(xiàn)各種功能,這種設(shè)計(jì)在其他領(lǐng)域也較為常見。

2.Intent

在Android中,利用Intent類實(shí)現(xiàn)了在Activity1與Activity2之間的切換(以及啟動(dòng)Service等)。Intent類主要用于描述應(yīng)用的功能。在Intent的描述結(jié)構(gòu)中,有動(dòng)作(Action)和動(dòng)作對(duì)應(yīng)的數(shù)據(jù)(data)兩個(gè)最重要的部分。其中,典型的動(dòng)作類型有:MAIN、VIEW、PICK、EDIT等,而動(dòng)作對(duì)應(yīng)的數(shù)據(jù)以URI的形式表示。例如,如果要查看一個(gè)人的聯(lián)系方式,需要先創(chuàng)建一個(gè)動(dòng)作類型為VIEW的Intent,以及一個(gè)表示這個(gè)動(dòng)作對(duì)應(yīng)的數(shù)據(jù)(這里就是這個(gè)人)的URI。

從一個(gè)屏幕導(dǎo)航到另一個(gè)屏幕,我們需要解析各種Intent。為了實(shí)現(xiàn)向前導(dǎo)航,首先,我們調(diào)用Activity的startActivity(Intent myIntent)方法。此時(shí),系統(tǒng)為找到最匹配myIntent的Intent對(duì)應(yīng)的Activity,就將在所有已安裝的應(yīng)用程序中定義的IntentFilter中查找。而匹配的對(duì)應(yīng)的新的Activity在接收到myIntent的通知后,開始運(yùn)行。當(dāng)startActivity方法被調(diào)用時(shí),將觸發(fā)解析myIntent的動(dòng)作,該機(jī)制提供了兩個(gè)關(guān)鍵好處。

(1)Activities能夠重復(fù)利用從其他組件中以Intent的形式產(chǎn)生的請(qǐng)求。

(2)Activities可以在任何時(shí)候被具有相同Action的新的Activity取代。

下面舉例說(shuō)明兩個(gè)Activity之間的切換。運(yùn)行效果:當(dāng)應(yīng)用程序啟動(dòng)時(shí),顯示布局main.xml,如圖3-2所示,當(dāng)我們單擊“切換”按鈕時(shí),屏幕顯示布局main2.xml,如圖3-3所示,再單擊“切換”按鈕,又回到如圖3-2 所示的狀態(tài)。就這樣通過(guò)Intent完成了兩個(gè)Activity之間的切換。

圖3-2 Activity1

圖3-3 Activity2

以下是兩個(gè)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項(xiàng)目中一共使用了兩個(gè)Activity,
      * 每使用一個(gè)Activity,都必須在AndroidManifest.xml中
      * 進(jìn)行聲明
      */
      public class Activity1 extends Activity
      {
          public void onCreate(Bundle savedInstanceState)
          {
              super.onCreate(savedInstanceState);
              /* 設(shè)置顯示main.xml布局 */
              setContentView(R.layout.main);
              /* findViewById(R.id.button1)取得布局main.xml中的button1 */
              Button button = (Button) findViewById(R.id.button1);
              /* 監(jiān)聽button的事件信息 */
              button.setOnClickListener(new Button.OnClickListener() {
                  public void onClick(View v)
                  {
                      /* 新建一個(gè)Intent對(duì)象 */
                      Intent intent = new Intent();
                      /* 指定intent要啟動(dòng)的類 */
                      intent.setClass(Activity1.this, Activity2.class);
                      /* 啟動(dòng)一個(gè)新的Activity */
                      startActivity(intent);
                      /* 關(guān)閉當(dāng)前的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);
              /* 設(shè)置顯示main2.xml布局 */
              setContentView(R.layout.main2);
              /* findViewById(R.id.button2)取得布局main.xml中的button2 */
              Button button = (Button) findViewById(R.id.button2);
              /* 監(jiān)聽button的事件信息 */
              button.setOnClickListener(new Button.OnClickListener() {
                  public void onClick(View v)
                  {
                      /* 新建一個(gè)Intent對(duì)象 */
                      Intent intent = new Intent();
                      /* 指定intent要啟動(dòng)的類 */
                      intent.setClass(Activity2.this, Activity1.class);
                      /* 啟動(dòng)一個(gè)新的Activity */
                      startActivity(intent);
                      /* 關(guān)閉當(dāng)前的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="跳轉(zhuǎn)到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>

問(wèn):Android應(yīng)用如何對(duì)外部事件(如當(dāng)電話呼入時(shí),或者數(shù)據(jù)網(wǎng)絡(luò)可用時(shí),或者到了晚上時(shí))做出響應(yīng)?

:使用IntentReceiver。

? 在相應(yīng)的事件發(fā)生時(shí),IntentReceiver使用NotificationManager通知用戶,但它并不能生成UI。

? 它在AndroidManifest.xml中注冊(cè),也可以在代碼中使用Context.registerReceiver()進(jìn)行注冊(cè)。

? 當(dāng)IntentReceiver被觸發(fā)時(shí),應(yīng)用不必對(duì)請(qǐng)求調(diào)用IntentReceiver,系統(tǒng)會(huì)在需要時(shí)啟動(dòng)應(yīng)用。

? 各種應(yīng)用還可以通過(guò)使用Context.broadcastIntent()將它們自己的intentreceiver廣播給其他應(yīng)用。

3.Content Provider

Android應(yīng)用能夠?qū)⑺鼈兊臄?shù)據(jù)保存到文件和SQLite數(shù)據(jù)庫(kù)中,甚至是任何有效的設(shè)備中。利用Content Provider實(shí)現(xiàn)應(yīng)用數(shù)據(jù)與其他的應(yīng)用共享。因?yàn)镃ontent Provider類實(shí)現(xiàn)了一組標(biāo)準(zhǔn)的方法,能夠讓其他的應(yīng)用保存或讀取此內(nèi)容提供器處理的各種數(shù)據(jù)類型。

Content Provider可以理解為在不同的應(yīng)用包之間共享數(shù)據(jù)的工具。在Android中,默認(rèn)的系統(tǒng)數(shù)據(jù)庫(kù)為SQLite。但是在Android中,使用方法略有區(qū)別。在Android中,每一個(gè)應(yīng)用都單獨(dú)運(yùn)行在各自的進(jìn)程中,當(dāng)一個(gè)應(yīng)用需要訪問(wèn)其他應(yīng)用的數(shù)據(jù)時(shí),這里就需要在不同的虛擬機(jī)之間傳遞數(shù)據(jù),這樣的情況操作起來(lái)可能有些困難(正常情況下,你不能讀取其他應(yīng)用的db文件)。Content Provider就解決了上述困難。

在Android中,Content Povider是一個(gè)特殊的存儲(chǔ)數(shù)據(jù)的類型,它提供了一套標(biāo)準(zhǔn)的接口用來(lái)獲取和操作數(shù)據(jù)。并且,Android自身也提供了現(xiàn)成的Content Provider:Contacts、Browser、CallLog、Settings、MediaStore。當(dāng)通過(guò)Content Resolver提供的方法來(lái)使用Content Provider時(shí),應(yīng)用可以通過(guò)唯一的Content Resolver interface來(lái)使用具體的某個(gè)Content Provider。其中,Content Resolver提供的方法包括query()、insert()、update()等。要使用這些方法,還會(huì)涉及URI。可以將它理解成string形式的Content Provider的完全路徑。

4.Service

Service即“服務(wù)”的意思,既然是服務(wù),那么Service將是一個(gè)生命周期長(zhǎng)而且沒(méi)有用戶界面的程序。比如,一個(gè)正在從播放列表中播放歌曲的媒體播放器,在這個(gè)媒體播放器應(yīng)用中,應(yīng)該會(huì)有多個(gè)Activity,讓使用者可以選擇歌曲并播放歌曲。然而,音樂(lè)重放這個(gè)功能并沒(méi)有對(duì)應(yīng)的Activity,因?yàn)槭褂谜邥?huì)認(rèn)為在導(dǎo)航到其他屏幕時(shí)音樂(lè)應(yīng)該還在播放。在這個(gè)例子中,媒體播放器這個(gè)Activity會(huì)使用Context.Startservice()來(lái)啟動(dòng)一個(gè)Service,從而可以在后臺(tái)保持音樂(lè)的播放。同時(shí),系統(tǒng)也將保持這個(gè)Service一直執(zhí)行,直到這個(gè)Service運(yùn)行結(jié)束。另外,還可以通過(guò)使用Context.Bindservice()方法連接到一個(gè)Service上(如果這個(gè)Service當(dāng)前還沒(méi)有處于啟動(dòng)狀態(tài),則將啟動(dòng)它)。當(dāng)連接到一個(gè)Service之后,還可用Service提供的接口與它進(jìn)行通信。以媒體播放器為例,還可以執(zhí)行暫停、重播等操作。

3.2 Android程序UI設(shè)計(jì)

在前面章節(jié)的例子中,我們已經(jīng)接觸了TextView、Button等UI控件,其實(shí)這里所說(shuō)的UI就是布局文件(layout),UI是一個(gè)應(yīng)用程序展現(xiàn)給用戶的界面,一個(gè)應(yīng)用程序要想受用戶喜愛,那么UI可不能差。自從Android SDK 1.0_r2版本開始,ADT提供了UI預(yù)覽的功能?,F(xiàn)在只需要打開一個(gè)Android項(xiàng)目的“/res/layout/main.xml”并右鍵單擊鼠標(biāo)右鍵,在彈出的快捷菜單中選擇“Open With”→ “Android Layout Editor”命令,或者直接雙擊main.xml文件,即可以切換到UI設(shè)計(jì)界面,如圖3-4所示。

圖3-4 Android Layout Editor命令

左邊的layouts標(biāo)簽的內(nèi)容則是一些線性布局,可以使用它輕松地完成對(duì)布局的排版,比如,橫向或者縱向布局。Views標(biāo)簽則是一些UI控件,可以將這些控件直接拖動(dòng)到右邊的窗口進(jìn)行編輯,這些UI控件的類型如圖3-5所示。

圖3-5 Android layout Editor

當(dāng)然,還可以單擊右下角的main.xml標(biāo)簽來(lái)切換到XML編輯器,對(duì)代碼進(jìn)行編排,如圖3-6所示。將這些功能配合起來(lái)使用,基本可以滿足開發(fā)者需求。

圖3-6 XML編輯器

3.3 Java語(yǔ)言在Android程序中的使用

Android應(yīng)用程序采用Java語(yǔ)言編寫,Java語(yǔ)法和C/C++有很大的相似性,但也有一些特別之處。

3.3.1 Interface的使用

從名字上看,Interface即為接口的意思,多用于實(shí)現(xiàn)回調(diào)(Call Back)方法。

在Interface的定義中,一般的代碼架構(gòu)如代碼清單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;
        }

對(duì)于Interface內(nèi)部的方法而言,只需要聲明,而不需要具體實(shí)現(xiàn)。從編譯器的角度來(lái)看,Interface會(huì)被認(rèn)為是一個(gè)指向方法的指針。

使用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是一個(gè)修飾符,其類似于Atatic這樣的關(guān)鍵字。Android程序中常用abstract修飾一個(gè)類,如abstract class,當(dāng)然能它也可以修飾一些變量或是方法。

抽象類所包含的方法可以只是定義,也可以是已實(shí)現(xiàn)的。對(duì)于沒(méi)有實(shí)現(xiàn)的方法,基于該方法的子類必須實(shí)現(xiàn);而對(duì)于已經(jīng)實(shí)現(xiàn)的方法,子類可以重寫該方法,若沒(méi)有重寫,則使用父類的方法。

在一定程度上,abstract class可以代替Interface,例如,3.3.1節(jié)中Interface的例子做如下的abstract class替換,其效果是等價(jià)的。

代碼清單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的區(qū)別

從語(yǔ)法角度講,接口和抽象類有以下區(qū)別。

? Java語(yǔ)法規(guī)定,一個(gè)子類只能有一個(gè)父類,但可以實(shí)現(xiàn)多個(gè)接口。

? abstract class可以代替Interface。

? 定義Interface時(shí),只需要列出所包含方法的定義而不必實(shí)現(xiàn)。而定義Abstract類時(shí),方法必須有實(shí)現(xiàn)部分,這就是所謂的默認(rèn)實(shí)現(xiàn),除非該方法也是Abstract類型。

? 接口的子類必須實(shí)現(xiàn)接口所定義的全部方法,而抽象類的子類不必實(shí)現(xiàn)抽象類所定義的任何方法,除非該方法是Abstract或者子類想重寫某個(gè)方法。

? 接口中的成員變量必須是Static Final類型(實(shí)際應(yīng)用中則很少包含變量,因?yàn)榻涌诙嘤糜谝茫?,而abstract class內(nèi)部可以包含任意變量。

從應(yīng)用的角度來(lái)講,Interface和abstract class的區(qū)別在于:Interface提供了一個(gè)方法集合的接口,該接口用于客戶端和服務(wù)端的方法調(diào)用,如圖3-7所示。

圖3-7 Interface的使用機(jī)制

……

接口一般是由服務(wù)端定義,比如操作系統(tǒng),客戶端根據(jù)自己的需求對(duì)接口做不同的實(shí)現(xiàn);而abstract class則僅提供了一個(gè)基類,該基類沒(méi)有任何服務(wù)端或者客戶端的概念,它的作用就是為了繼承并重寫,如圖3-8所示。

圖3-8 abstract class的使用機(jī)制

3.3.4 for循環(huán)的使用

除了傳統(tǒng)的for循環(huán)(語(yǔ)法是for(inti=0;i<N;i++){};)以外還有foreach循環(huán)。舉例如代碼清單3-17所示。

代碼清單3-17 For循環(huán)語(yǔ)法

        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語(yǔ)法是對(duì)某個(gè)集合進(jìn)行循環(huán),第1 個(gè)參數(shù)是循環(huán)過(guò)程集合元素值的引用,第2個(gè)參數(shù)是集合對(duì)象。第1個(gè)參數(shù)的類型必須和集合元素的類型相同。

以下代碼示例了傳統(tǒng)的for語(yǔ)法和新for語(yǔ)法的等效使用,但由于編譯器對(duì)新for語(yǔ)法的優(yōu)化,其執(zhí)行效率將更高。

代碼清單3-18傳統(tǒng)for語(yǔ)法和新for語(yǔ)法比較

        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系統(tǒng)中,有著多種存儲(chǔ)數(shù)據(jù)的方式,例如,文件、數(shù)據(jù)庫(kù)及程序內(nèi)參數(shù)式存儲(chǔ)、網(wǎng)絡(luò)存儲(chǔ)等。對(duì)于參數(shù)式存儲(chǔ)時(shí),使用的就是Map類。Map本身是Interface,Java基于該接口實(shí)現(xiàn)三個(gè)具體的Map類,分別是HashMap、TreeMap,以及EnumMap,常用的為HashMap,本節(jié)也主要介紹HashMap。

Map定義了訪問(wèn)特定集合的標(biāo)準(zhǔn)方法,這種集合用來(lái)存儲(chǔ)key-value類型的鍵值對(duì),比如,對(duì)于name:Haiii和age:22這兩組數(shù)據(jù)來(lái)講,其中name、age稱為鍵(key),與此對(duì)應(yīng)的是鍵值(value)。在一個(gè)Map集合類中,每對(duì)鍵或值其類型都可以是任意的,比如int、String等都是可以的。

Map類又是一個(gè)類模板,一個(gè)Map類對(duì)象在初始化時(shí)必須指定鍵的類型,可以是任何Object類,比如,Map<String,Object> mMap= new HashMap<String,Object>()。

<>里面的數(shù)據(jù)類型用于指定Map集合中“鍵值對(duì)”的類型。

給Map集合添加和刪除鍵值對(duì)的方法如表3-2所示。

表3-2 Map集合添加和刪除鍵值對(duì)的方法

Map類沒(méi)有提供直接遍歷鍵值對(duì)的方法,要遍歷所有鍵值對(duì)需要一個(gè)中間過(guò)程。Map提供了3個(gè)方法用于間接遍歷鍵值對(duì),如下:

? entrySet() 返回所有鍵值對(duì)類型為Set對(duì)象。

? keySet() 返回所有鍵值對(duì)類型為Set對(duì)象。

? valueSet() 返回所有鍵值對(duì)類型為Collection對(duì)象。

要得到具體的鍵值對(duì),需要再解析Set和Collection對(duì)象,但僅有這兩個(gè)對(duì)象還不能獲得鍵值對(duì),還需要借助于Iterator類。到這里,可能覺得有些復(fù)雜,別著急,結(jié)果馬上就要出來(lái)了。

Set、Collection、Iterator實(shí)際上是Map內(nèi)部進(jìn)行操作的3個(gè)輔助類,要得到具體Map鍵值對(duì),如代碼清單3-19所示。

代碼清單3-19得到具體的Map鍵值對(duì)

      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();
      }

用以上代碼讀取鍵值對(duì)時(shí),Object可以強(qiáng)制轉(zhuǎn)換為int類型。

3.3.6 Integer與String之間的轉(zhuǎn)換

在實(shí)際程序設(shè)計(jì)中,經(jīng)常需要把Integer類型轉(zhuǎn)換為String類型,或者相反,Java類庫(kù)中提供了這樣的方法。

? 整形轉(zhuǎn)換為字符串:String.valueOf(Stringstr)。

? 字符串轉(zhuǎn)換為整形:Integer.parseInt(int)、Interger.parseLong(long)等。

面向?qū)ο缶幊讨?,一般不能直接調(diào)用類中的方法,而是需要先定義類的一個(gè)對(duì)象,然后才能使用其包含的方法。而以上兩個(gè)方法直接調(diào)用String和Integer的方法,這就是static關(guān)鍵字的作用。

在定義一個(gè)類時(shí),如果其中的方法聲明為static,那么外部程序就可以直接調(diào)用該方法,該方法所引用的一些變量也必須為static類型的變量。

Java內(nèi)部有一種安全機(jī)制:對(duì)于一個(gè)普通的類,必須聲明該類的對(duì)象才能訪問(wèn)類中的方法或者變量,實(shí)現(xiàn)這種安全機(jī)制的是Java編譯器。編譯器隱藏了所有類的地址,因此不能通過(guò)類名稱定位到類的地址;而如果使用static修飾符,無(wú)論是變量還是方法,編譯器都會(huì)把該變量或者方法的名稱導(dǎo)出,以便程序能夠根據(jù)類名定位到類所在的地址,從而能夠調(diào)用相應(yīng)的方法或者變量。

3.3.7 synchronized同步關(guān)鍵字

synchronized關(guān)鍵字屬于操作系統(tǒng)的范疇,與同步對(duì)應(yīng)的是異步。在程序設(shè)計(jì)的概念中,有同步調(diào)用或者異步調(diào)用,同步是指該段代碼(方法)從調(diào)用開始,直到內(nèi)部執(zhí)行完畢后才能返回;異步調(diào)用是指調(diào)用該段代碼(方法)后立即返回,無(wú)論該段代碼內(nèi)部所執(zhí)行的物理操作是否執(zhí)行完畢,異步代碼一般存在于多線程程序設(shè)計(jì)中,單線程程序內(nèi)部不存在異步調(diào)用。

synchronized關(guān)鍵字的作用就是告訴操作系統(tǒng),在執(zhí)行該關(guān)鍵字所限定的代碼片段內(nèi),不允許被其他線程打斷。在一般的操作系統(tǒng)設(shè)計(jì)中,會(huì)提供一個(gè)類似于synchronized的API方法,而Java則是給了這樣一個(gè)關(guān)鍵字,相當(dāng)于說(shuō),Java編譯器為操作系統(tǒng)分擔(dān)了一部分工作。

那么,什么情況下需要使用synchronized關(guān)鍵字呢?凡是需要某段代碼在執(zhí)行時(shí)不被其他線程打斷時(shí),都可以加上synchronized關(guān)鍵字,舉例如代碼清單3-20所示。

代碼清單3-20 synchronized關(guān)鍵字

      public class MyMusicWidgetProvider{
        private static MyMusicWidgetProvider sInstance;
        static synchronized MyMusicWidgetProvider getInstance(){
            if(sInstance==null)
                sInstance=new MyMusicWidgetProvider();
            returnsInstance;
        }
      }

以上代碼定義了一個(gè)類,并希望該類在運(yùn)行時(shí)僅有一個(gè)實(shí)例,每次調(diào)用該類所包含的方法時(shí),先得到該類的實(shí)例,然后再通過(guò)實(shí)例調(diào)用其他方法。該類有一個(gè)static的getInstance()方法,該方法的作用就是檢查是否存在一個(gè)實(shí)例,如果沒(méi)有就創(chuàng)建一個(gè),否則返回存在的實(shí)例。

getInstance()方法前面加了synchronized關(guān)鍵字,為的是該方法在執(zhí)行時(shí)不能被其他線程打斷,那么,為什么有這樣的要求呢?試想一下,假設(shè)沒(méi)有使用該關(guān)鍵字,第1個(gè)線程A在執(zhí)行g(shù)etInstance()方法時(shí),方法體中的new運(yùn)算符剛剛創(chuàng)建了一個(gè)MyMusicWidgetProvider實(shí)例,但還沒(méi)來(lái)得及把實(shí)例賦值給sInstance,而此時(shí)B線程又來(lái)調(diào)用getInstance()方法,出現(xiàn)這種情況的原因是:sInstance= new MyMusicWidgetProvider()這句代碼會(huì)被Java編譯器編譯成多條機(jī)器指令,其中給sInstance賦值的機(jī)器指令和創(chuàng)建一個(gè)MyMusicWidgetProvider對(duì)象的機(jī)器指令是分開的。此時(shí)對(duì)于B線程來(lái)講,它會(huì)重新檢查sInstance是否為空,由于A線程還沒(méi)有來(lái)得及給sIntance賦值,因此,B線程就會(huì)再次創(chuàng)建一個(gè)新的MyMusicWidgetProvider實(shí)例,當(dāng)B線程暫停并返回A線程時(shí),A線程會(huì)把第1次創(chuàng)建的實(shí)例重新賦值給sInstance,這就會(huì)導(dǎo)致B線程將來(lái)對(duì)MyMusicWidgetProvider實(shí)例的錯(cuò)誤引用,因?yàn)锽線程所創(chuàng)建實(shí)例的地址被A線程修改了。

因此,這就要求getInstance()一旦開始執(zhí)行,必須執(zhí)行完畢后才能返回,中間不能被其他線程打斷。

sychronized除了約束整個(gè)方法外,也可以約束一小段代碼,這樣做的好處有兩個(gè):一個(gè)是可以避免定義新方法;另一個(gè)是能夠避免一些線程同步問(wèn)題,如代碼清單3-21所示。

代碼清單3-21 synchronized約束小段代碼

      Object obj;
      int c;
      void addMethod(){
        synchronized(obj){
            c++;
            //其他代碼
        }
      }

obj可以是任意類型的一個(gè)對(duì)象,該對(duì)象的唯一作用就是給synchronized提供一個(gè)“鎖”。同步設(shè)計(jì)的原則之一是盡量減少同步中包含的代碼大小,從而在線程間能夠更平衡地運(yùn)行。因此,同步一小段代碼是一個(gè)不錯(cuò)的做法。同時(shí),假設(shè)addMethod()內(nèi)部還調(diào)用了其他同步方法,那么,從這個(gè)同步跳到另外的同步會(huì)增加線程間死鎖的風(fēng)險(xiǎn),因此不同步整個(gè)addMethod()而僅同步局部代碼,這就是第2個(gè)好處。

3.3.8 new的使用

Java語(yǔ)言中,new的作用是為一個(gè)對(duì)象(Object)分配內(nèi)存,代碼清單3-22所示代碼說(shuō)明了為各種Object分配內(nèi)存的方法。

代碼清單3-22為各種Object分配內(nèi)存的方法

      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()

一般情況下,沒(méi)有用new修飾符定義的數(shù)據(jù)都是在棧(Stack)中分配內(nèi)存,但有一個(gè)例外,對(duì)于String定義的變量,總是從系統(tǒng)內(nèi)存堆(Heap)中分配內(nèi)存,棧中僅有對(duì)該String的引用。另外,從系統(tǒng)堆中分配的實(shí)際內(nèi)存大小并不是按指定的大小分配的,比如,int A[]=new int[100]所分配的內(nèi)存大小并不是100B,而是128B,內(nèi)存分配機(jī)制為了提高分配效率以及分配算法的可實(shí)現(xiàn)性,實(shí)際上的內(nèi)存顆粒大小會(huì)按照2的冪次方進(jìn)行劃分,實(shí)際分配的內(nèi)存大小是最接近指定大小的一個(gè)值。另外,最小的內(nèi)存顆粒大小會(huì)根據(jù)不同的內(nèi)存分配算法有所不同,一般會(huì)取512B或者1KB。

問(wèn):在Android上使用Java與在PC上使用Java的編程風(fēng)格有何差別?

:Android是一種嵌入式系統(tǒng),在追求程序效率、降低所需資源上是孜孜不倦的,程序員心中始終要有該意識(shí)。在PC上進(jìn)行編程時(shí),清晰完整的程序結(jié)構(gòu)比運(yùn)行效率顯得更重要,因此,程序結(jié)構(gòu)上經(jīng)常呈現(xiàn)多層結(jié)構(gòu),比如getter、setter等;而在Android上編程時(shí),盡管需要清晰的程序結(jié)構(gòu),但對(duì)于一些小的結(jié)構(gòu),則是效率重于結(jié)構(gòu)。因此,能在一個(gè)函數(shù)或引用中實(shí)現(xiàn)的功能就要避免進(jìn)行多層嵌套調(diào)用。

3.4 本章小結(jié)

本章主要介紹了Android應(yīng)用程序框架、UI設(shè)計(jì),以及Java在Android程序中的使用。首先徹底地分析了上一章的AndroidTest項(xiàng)目,從其項(xiàng)目目錄結(jié)構(gòu)、文件功能等方面分析了Android應(yīng)用程序的基本框架,其次逐一分析了Android應(yīng)用程序的構(gòu)成,并分別通過(guò)示例程序演示了其功能的運(yùn)用。其次介紹了有關(guān)UI設(shè)計(jì)的工具,使得程序界面更加漂亮。最后介紹了在Android程序中Java語(yǔ)法的使用。

關(guān)鍵知識(shí)點(diǎn)測(cè)評(píng)

1.以下有關(guān)Android程序目錄結(jié)構(gòu)的說(shuō)法,不正確一個(gè)是(?。?/p>

A.Android程序中包含其他Java項(xiàng)目中沒(méi)有的gen文件夾

B.AndroidManfest.xml文件則包含了該項(xiàng)目中所使用的Activity、Service、Receiver

C.程序的布局文件(layout)中配置了程序的線性版面,視圖位置、高度等

D.R.java文件可以更改,被編輯

2.以下有關(guān)Android應(yīng)用的敘述,正確的一個(gè)是(?。?。

A.所有的Android應(yīng)用程序都有Activity、Intent、Content Provider、Service 4個(gè)模塊構(gòu)造而成

B.一個(gè)活動(dòng)(Activity)通常就是一個(gè)單獨(dú)的屏幕,它不需要繼承其他基類

C.Intent可以實(shí)現(xiàn)Activity與Activity之間的切換件

D.Service能夠讓其他的應(yīng)用保存或讀取此內(nèi)容提供器處理的各種數(shù)據(jù)類型

3.以下有關(guān)interface與abstract class區(qū)別的描述,不正確的是( )。

A.定義abstract類時(shí),所有方法不需要有實(shí)現(xiàn)部分

B.一個(gè)子類只能有一個(gè)父類,但可以實(shí)現(xiàn)多個(gè)接口

C.接口中的成員變量必須是static final,而Abstract class內(nèi)部可以包含任意變量

D.接口的子類必須實(shí)現(xiàn)接口所定義的全部方法,而抽象類的子類不必實(shí)現(xiàn)抽象類所定義的任何方法

主站蜘蛛池模板: 章丘市| 铜梁县| 河西区| 靖江市| 门头沟区| 西藏| 洛南县| 阳谷县| 手游| 资兴市| 蒙自县| 永嘉县| 长白| 安顺市| 通化市| 屯昌县| 垣曲县| 岑溪市| 江孜县| 涞水县| 广德县| 广东省| 射洪县| 凤翔县| 靖远县| 民权县| 凤山县| 凤冈县| 昌乐县| 延寿县| 蒙阴县| 长岛县| 西藏| 泾阳县| 赤水市| 汝阳县| 扬州市| 克山县| 兴业县| 团风县| 通许县|