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

第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.接口的子類必須實現接口所定義的全部方法,而抽象類的子類不必實現抽象類所定義的任何方法

主站蜘蛛池模板: 清水县| 宜宾市| 宜昌市| 海门市| 德清县| 汽车| 乳源| 龙游县| 河东区| 山丹县| 广丰县| 图们市| 大城县| 安西县| 高要市| 开鲁县| 温州市| 柏乡县| 旬阳县| 株洲县| 灌南县| 宜宾县| 呼和浩特市| 长治县| 兴宁市| 禄劝| 宝丰县| 南投市| 桂东县| 乐安县| 尼玛县| 威海市| 涟源市| 资源县| 台江县| 前郭尔| 出国| 扬州市| 嘉禾县| 福泉市| 安多县|