天天看點

Android知識點大掃描

  Android知識點大掃描

什麼是 3G

3G,全稱為3rd Generation,中文含義就是指第三代數字通信。

所謂3G,是指将無線通信與國際網際網路等多媒體通信結合的新一代移動通信系統。 3G隻是一種通信技術标準,符合這個标準的技術有WCDMA、CDMA2000、TD-SCDMA三種制式。中國聯通使用的是WCDMA(世界上大部分 3G網絡都采用的是該标準) ;中國電信使用的是CDMA2000 (日、韓和北美使用);中國移動使用的是具有自主知識産權的TD-SCDMA(隻有中國才使用) 。相對第一代模拟制式手機(1G)和第二代GSM、 CDMA等數字手機(2G),3G網絡能處理圖像、音樂、視訊等多種媒體形式,提供包括網頁浏覽、電話會議、電子商務等多種資訊服務。第三代與前兩代的主要差別是在傳輸聲音和資料的速度上有很大的提升。

由于3G商用需要相當浩大的工程,要從目前的2G邁向3G不可能一下就銜接得上,是以前幾年2.5G的手機就出現了。符合2.5G标準的技術有 CDMA2000 1X和GPRS,中國聯通使用的是CDMA2000 1X标準,中國移動使用的是GPRS标準。目前,我們可以把2.5G移動通信技術看作是2G邁向3G的銜接性技術,在2.5G網絡下出現了如WAP、藍牙 (Bluetoot) 等技術。

Android 應用程式架構

src/ java原代碼存放目錄

gen/ 自動生成目錄

gen 目錄中存放所有由Android開發工具自動生成的檔案。目錄中最重要的就是R.java檔案。 這個檔案由Android開發工具自動産生的。Android開發工具會自動根據你放入res目錄的xml界面檔案、圖示與常量,同步更新修改 R.java檔案。正因為R.java檔案是由開發工具自動生成的,是以我們應避免手工修改R.java。R.java在應用中起到了字典的作用,它包含了界面、圖示、常量等各種資源的id,通過R.java,應用可以很友善地找到對應資源。另外編繹器也會檢查R.java清單中的資源是否被使用到,沒有被使用到的資源不會編繹進軟體中,這樣可以減少應用在手機占用的空間。

res/ 資源(Resource)目錄

在這個目錄中我們可以存放應用使用到的各種資源,如xml界面檔案,圖檔或資料。具體請看ppt下方備注欄。

AndroidManifest.xml 功能清單檔案

這個檔案列出了應用程式所提供的功能,在這個檔案中,你可以指定應用程式使用到的服務(如電話服務、網際網路服務、短信服務、GPS服務等等)。另外當你新添加一個Activity的時候,也需要在這個檔案中進行相應配置,隻有配置好後,才能調用此Activity。

default.properties 項目環境資訊,一般是不需要修改此檔案

res/drawable 專門存放png、jpg等圖示檔案。在代碼中使用getResources().getDrawable(resourceId)擷取該目錄下的資源。

res/layout 專門存放xml界面檔案,xml界面檔案和HTML檔案一樣,主要用于顯示使用者操作界面。

res/values 專門存放應用使用到的各種類型資料。不同類型的資料存放在不同的檔案中,如下:

· strings.xml 定義字元串和數值,在Activity中使用getResources().getString(resourceId) 或getResources().getText(resourceId)取得資源。它的作用和 struts中的國際化資源檔案一樣。

<?xml version="1.0" encoding="UTF-8"?>

<resources>

<string name="android"> android</string>

</resources>

· arrays.xml 定義數組。

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

<resources>

       <string-array name="colors">

              <item>red</item>

              <item>yellow</item>    

              <item>green</item>    

              <item>blue</item>  

       </string-array>

</resources>

· colors.xml 定義顔色和顔色字串數值,你可以在Activity中使用getResources().getDrawable(resourceId) 以及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="key_height">50dip</dimen>

</resources>

· styles.xml 定義樣式。

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

<resources>

       <style name="androidText" parent="@style/Text">

              <item name="android:textSize">18px</item>

              <item name="android:textColor">#008</item>

       </style>

</resources>

res/anim/ 編譯成幀動畫的XML檔案。

res/xml/ 在Activity中使用getResources().getXML()讀取該目錄下的XML資源檔案。

res/raw/ 該目錄下的檔案将直接被複制到裝置上。編譯軟體時,這些資料不會被編譯,它們被直接加入到程式安裝包裡。 為了在程式中使用這些資源,你可以調用getResources().openRawResource(ID) , 參數ID形式:R.raw. somefilename。

電話拔号器

因為應用要使用手機的電話服務,是以要在清單檔案AndroidManifest.xml中添加電話服務權限:

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

      package="cn.android.action"

      android:versionCode="1"

      android:versionName="1.0">

      略....

    <uses-sdk android:minSdkVersion=“6" />

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

</manifest>

界面布局:

<?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/inputmobile"/>

    <EditText android:layout_width="fill_parent" android:layout_height="wrap_content"

    android:id="@+id/mobile"/>

    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"

    android:text="@string/button"

    android:id="@+id/button"/>

</LinearLayout>

LinearLayout (線性布局)、AbsoluteLayout(絕對布局)、RelativeLayout(相對布局)、TableLayout(表格布局)、FrameLayout(幀布局)

Activity:

public class DialerAction extends Activity {

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Button button = (Button)findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener(){

       public void onClick(View v) {

           EditText editText = (EditText)findViewById(R.id.mobile);

           Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+ editText.getText()));

           DialerAction.this.startActivity(intent);

       }

        });

    }

}

測試步驟:

1>在Eclipse中運作此應用

2>在Dos視窗中進入android SDK安裝路徑的tools目錄,輸入以下指令再開啟一個Android模拟器:

emulator -data android

   注:android為使用者資料存取檔案,如果該檔案不存在,預設在tools目錄建立該檔案

3>在電話擾号器中輸入上圖現顯的電話号碼

“ 尚未注冊網絡 ” 錯誤資訊的解決辦法

打開Android模拟器時,出現無信号,拔打電話或發短信時,提示“尚未注冊網絡”錯誤資訊的解決方案如下。

l         場景一:你的電腦沒有連接配接上網際網路,同時也沒有在區域網路。

解決辦法:右鍵點選網路上的芳鄰,選擇"屬性",在網絡連接配接視窗中右鍵點選"本地連接配接",選擇"屬性",設定TCP/IP屬性如下:

     IP位址:192.168.1.100

     子網路遮罩:255.255.255.0

     預設網關:192.168.1.100

     首選DNS 伺服器:192.168.1.100

l         場景二:你的電腦沒有連接配接上網際網路,但在區域網路。

解決辦法:右鍵點選網路上的芳鄰,選擇"屬性",在網絡連接配接視窗中右鍵點選"本地連接配接",選擇"屬性",設定TCP/IP屬性如下:

     IP位址:設定成你所在區域網路的IP,如:192.168.1.100

     子網路遮罩:設定成你所在區域網路的掩碼,如:255.255.255.0

     預設網關:設定成你所在區域網路的網關,一般網關的IP格式為:*.*.*.1,如:192.168.1.1

     首選DNS 伺服器:設定成你所在區域網路的路由器IP,一般路由器的IP格式為:*.*.*.1,如:192.168.1.1

最後一種解決方案是:讓你的電腦連接配接上網際網路。

短信發送器

因為應用要使用手機的短信服務,是以要在清單檔案AndroidManifest.xml中添加短信服務權限:

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

      package="cn.android.sms"

      android:versionCode="1"

      android:versionName="1.0">

     略....

     <uses-sdk android:minSdkVersion=“4" />

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

</manifest>

界面布局:

<?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/inputmobile"/>

    <EditText android:layout_width="fill_parent" android:layout_height="wrap_content"

    android:id="@+id/mobile"/>

   <TextView android:layout_width="fill_parent" android:layout_height="wrap_content"

    android:text="@string/content"/>

    <EditText android:layout_width="fill_parent" android:layout_height="wrap_content"

    android:minLines="3"

    android:id="@+id/content"/>

    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"

    android:text="@string/button"

    android:id="@+id/button"/>

</LinearLayout>

Activity主要代碼:

       String mobile = mobileView.getText().toString();

       String content = contentView.getText().toString();

        SmsManager smsManager = SmsManager.getDefault();

        PendingIntent sentIntent = PendingIntent.getBroadcast(SMSSender.this, 0, new Intent(), 0);

       if(content.length()>70){//如果字數超過70,需拆分成多條短信發送

               List<String> msgs = smsManager.divideMessage(content);

                 for(String msg : msgs){

                  smsManager.sendTextMessage(mobile, null, msg, sentIntent, null);

        //最後二個參數為短信已發送的廣播意圖,最後一個參數為短信對方已收到短信的廣播意圖

                  }

       }else{                          

                  smsManager.sendTextMessage(mobile, null, content, sentIntent, null);

       }

       Toast.makeText(SMSSender.this, "短信發送完成", Toast.LENGTH_LONG).show();

對應用進行單元測試

第一步:首先在AndroidManifest.xml中加入下面紅色代碼: <manifest xmlns:android="http://schemas.android.com/apk/res/android"       package="cn.android.action “ android:versionCode="1“ android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name">         <uses-library android:name="android.test.runner" />         .... </application> <uses-sdk android:minSdkVersion="6" /> <instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="cn.android.action" android:label="Tests for My App" /> </manifest> 上面targetPackage指定的包要和應用的package相同。 第二步:編寫單元測試代碼(選擇要測試的方法,右鍵點選 “ Run As ” -- “ Android Junit Test ” ): import android.test.AndroidTestCase; import android.util.Log; public class XMLTest extends AndroidTestCase {      public void testSomething() throws Throwable {         Assert.assertTrue(1 + 1 == 3);      } }

使用檔案進行資料存儲

首先給大家介紹使用檔案如何對資料進行存儲,Activity提供了openFileOutput()方法可以用于把資料輸出到檔案中,具體的實作過程與在J2SE環境中儲存資料到檔案中是一樣的。

public class FileActivity extends Activity {

    @Override public void onCreate(Bundle savedInstanceState) {

        ...

         FileOutputStream outStream = this.openFileOutput("android.txt", Context.MODE_PRIVATE);

         outStream.write("安桌".getBytes());

         outStream.close();  

    }

}

openFileOutput()方法的第一參數用于指定檔案名稱,不能包含路徑分隔符“/” ,如果檔案不存在,Android 會自動建立它。建立的檔案儲存在/data/data/<package name>/files目錄,如: /data/data/cn.android.action/files/android.txt ,通過點選Eclipse菜單“Window”-“Show View”-“Other”,在對話視窗中展開android檔案夾,選擇下面的File Explorer視圖,然後在File Explorer視圖中展開/data/data/<package name>/files目錄就可以看到該檔案。

openFileOutput()方法的第二參數用于指定操作模式,有四種模式,分别為: Context.MODE_PRIVATE    = 0

Context.MODE_APPEND    = 32768

Context.MODE_WORLD_READABLE = 1

Context.MODE_WORLD_WRITEABLE = 2

Context.MODE_PRIVATE:為預設操作模式,代表該檔案是私有資料,隻能被應用本身通路,在該模式下,寫入的内容會覆寫原檔案的内容,如果想把新寫入的内容追加到原檔案中。可以使用Context.MODE_APPEND

Context.MODE_APPEND:模式會檢查檔案是否存在,存在就往檔案追加内容,否則就建立新檔案。

Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用來控制其他應用是否有權限讀寫該檔案。

MODE_WORLD_READABLE:表示目前檔案可以被其他應用讀取; MODE_WORLD_WRITEABLE:表示目前檔案可以被其他應用寫入。

如果希望檔案被其他應用讀和寫,可以傳入:

openFileOutput("android.txt", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);

android有一套自己的安全模型,當應用程式(.apk)在安裝時系統就會配置設定給他一個userid,當該應用要去通路其他資源比如檔案的時候,就需要userid比對。預設情況下,任何應用建立的檔案,sharedpreferences,資料庫都應該是私有的(位于/data/data /<package name>/files),其他程式無法通路。除非在建立時指定了Context.MODE_WORLD_READABLE或者 Context.MODE_WORLD_WRITEABLE ,隻有這樣其他程式才能正确通路。

讀取檔案内容

如果要打開存放在/data/data/<package name>/files目錄應用私有的檔案,可以使用Activity提供openFileInput()方法。

FileInputStream inStream = this.getContext().openFileInput("android.txt");

Log.i("FileTest", readInStream(inStream));

readInStream()的方法請看本頁下面備注。

或者直接使用檔案的絕對路徑:

File file = new File("/data/data/cn.android.action/files/android.txt");

FileInputStream inStream = new FileInputStream(file);

Log.i("FileTest", readInStream(inStream));

注意:上面檔案路徑中的“cn.android.action”為應用所在包,當你在編寫代碼時應替換為你自己應用使用的包。

對于私有檔案隻能被建立該檔案的應用通路,如果希望檔案能被其他應用讀和寫,可以在建立檔案時,指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE權限。

Activity還提供了getCacheDir()和getFilesDir()方法:

getCacheDir()方法用于擷取/data/data/<package name>/cache目錄

getFilesDir()方法用于擷取/data/data/<package name>/files目錄

public static String readInStream(FileInputStream inStream){

       try {

              ByteArrayOutputStream outStream = new ByteArrayOutputStream();

              byte[] buffer = new byte[1024];

              int length = -1;

              while((length = inStream.read(buffer)) != -1 ){

                     outStream.write(buffer, 0, length);

              }

              outStream.close();

              inStream.close();

              return outStream.toString();

       } catch (IOException e) {

              Log.i("FileTest", e.getMessage());

       }

       return null;

}

把檔案存放在 SDCard

使用Activity的openFileOutput()方法儲存檔案,檔案是存放在手機空間上,一般手機的存儲空間不是很大,存放些小檔案還行,如果要存放像視訊這樣的大檔案,是不可行的。對于像視訊這樣的大檔案,我們可以把它存放在SDCard。 SDCard是幹什麼的?你可以把它看作是移動硬碟或U盤。

在模拟器中使用SDCard,你需要先建立一張SDCard卡(當然不是真的SDCard,隻是鏡像檔案)。建立SDCard可以在Eclipse建立模拟器時随同建立,也可以使用DOS指令進行建立,如下:

在Dos視窗中進入android SDK安裝路徑的tools目錄,輸入以下指令建立一張容量為2G的SDCard,檔案字尾可以随便取,建議使用.img:

mksdcard 2048M D:\AndroidTool\sdcard.img

在程式中通路SDCard,你需要申請通路SDCard的權限。

在AndroidManifest.xml中加入通路SDCard的權限如下:

<!-- 在SDCard中建立與删除檔案權限 -->

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

<!-- 往SDCard寫入資料權限 -->

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

要往SDCard存放檔案,程式必須先判斷手機是否裝有SDCard,并且可以進行讀寫。

注意:通路SDCard必須在AndroidManifest.xml中加入通路SDCard的權限

if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){

         File sdCardDir = Environment.getExternalStorageDirectory();//擷取SDCard目錄

         File saveFile = new File(sdCardDir, “android.txt”);

FileOutputStream outStream = new FileOutputStream(saveFile);

outStream.write("安桌".getBytes());

outStream.close();

}

Environment.getExternalStorageState()方法用于擷取SDCard的狀态,如果手機裝有SDCard,并且可以進行讀寫,那麼方法傳回的狀态等于Environment.MEDIA_MOUNTED。

Environment.getExternalStorageDirectory()方法用于擷取SDCard的目錄,當然要擷取SDCard的目錄,你也可以這樣寫:

File sdCardDir = new File("/sdcard"); //擷取SDCard目錄

File saveFile = new File(sdCardDir, "android.txt");

//上面兩句代碼可以合成一句: File saveFile = new File("/sdcard/android.txt");

FileOutputStream outStream = new FileOutputStream(saveFile);

outStream.write("安桌test".getBytes());

outStream.close();

cuihai 2011-03-03 12:10

使用SAX或者DOM或者pull解析XML檔案

在Android平台上可以使用Simple API for XML(SAX) 、 Document Object Model(DOM)和Android附帶的pull解析器解析XML檔案。 下面是本例子要解析的XML檔案:

檔案名稱:android.xml

<?xml version="1.0" encoding="UTF-8"?>

<persons>

       <person id="23">

              <name>李明</name>

              <age>30</age>

       </person>

       <person id="20">

              <name>李向梅</name>

              <age>25</age>

       </person>

</persons>

使用SAX讀取XML檔案

SAX是一個解析速度快并且占用記憶體少的xml解析器,非常适合用于Android等移動裝置。 SAX解析XML檔案采用的是事件驅動,也就是說,它并不需要解析完整個文檔,在按内容順序解析文檔的過程中,SAX會判斷目前讀到的字元是否合法XML 文法中的某部分,如果符合就會觸發事件。所謂事件,其實就是一些回調(callback)方法,這些方法(事件)定義在ContentHandler接口。下面是一些ContentHandler接口常用的方法:

startDocument()

當遇到文檔的開頭的時候,調用這個方法,可以在其中做一些預處理的工作。

endDocument()

和上面的方法相對應,當文檔結束的時候,調用這個方法,可以在其中做一些善後的工作。

startElement(String namespaceURI, String localName, String qName, Attributes atts)

當讀到一個開始标簽的時候,會觸發這個方法。namespaceURI就是命名空間,localName是不帶命名空間字首的标簽名,qName是帶命名空間字首的标簽名。通過atts可以得到所有的屬性名和相應的值。要注意的是SAX中一個重要的特點就是它的流式處理,當遇到一個标簽的時候,它并不會紀錄下以前所碰到的标簽,也就是說,在startElement()方法中,所有你所知道的資訊,就是标簽的名字和屬性,至于标簽的嵌套結構,上層标簽的名字,是否有子元屬等等其它與結構相關的資訊,都是不得而知的,都需要你的程式來完成。這使得SAX在程式設計處理上沒有DOM來得那麼友善。

endElement(String uri, String localName, String name)

這個方法和上面的方法相對應,在遇到結束标簽的時候,調用這個方法。

characters(char[] ch, int start, int length)

這個方法用來處理在XML檔案中讀到的内容,第一個參數為檔案的字元串内容,後面兩個參數是讀到的字元串在這個數組中的起始位置和長度,使用new String(ch,start,length)就可以擷取内容。

隻要為SAX提供實作ContentHandler接口的類,那麼該類就可以得到通知事件(實際上就是SAX調用了該類中的回調方法)。因為 ContentHandler是一個接口,在使用的時候可能會有些不友善,是以,SAX還為其制定了一個Helper類:DefaultHandler,它實作了ContentHandler接口,但是其所有的方法體都為空,在實作的時候,你隻需要繼承這個類,然後重載相應的方法即可。使用SAX解析 android.xml的代碼如下:

public static List<Person> readXML(InputStream inStream) {

   try {

       SAXParserFactory spf = SAXParserFactory.newInstance();

       SAXParser saxParser = spf.newSAXParser(); //建立解析器

       //設定解析器的相關特性,http://xml.org/sax/features/namespaces = true 表示開啟命名空間特性

       //saxParser.setProperty("http://xml.org/sax/features/namespaces",true);

       XMLContentHandler handler = new XMLContentHandler();

       saxParser.parse(inStream, handler);

       inStream.close();

       return handler.getPersons();

   } catch (Exception e) {

       e.printStackTrace();

   }

return null;

}

import java.util.ArrayList;

import java.util.List;

import org.xml.sax.Attributes;

import org.xml.sax.SAXException;

import org.xml.sax.helpers.DefaultHandler;

import cn.android.xml.domain.Person;

public class XMLContentHandler extends DefaultHandler {

       private List<Person> persons = null;

       private Person currentPerson;

       private String tagName = null;//目前解析的元素标簽

       public List<Person> getPersons() {

              return persons;

       }

       @Override public void startDocument() throws SAXException {

              persons = new ArrayList<Person>();

       }

       @Override public void characters(char[] ch, int start, int length) throws SAXException {

              if(tagName!=null){

                     String data = new String(ch, start, length);

                     if(tagName.equals("name")){

                            this.currentPerson.setName(data);

                     }else if(tagName.equals("age")){

                            this.currentPerson.setAge(Short.parseShort(data));

                     }

              }

       }

       @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {

              if(localName.equals("person")){

                     currentPerson = new Person();

                     currentPerson.setId(Integer.parseInt(atts.getValue("id")));

              }

              this.tagName = localName;

       }

       @Override public void endElement(String uri, String localName, String name) throws SAXException {

              if(localName.equals("person")){

                     persons.add(currentPerson);

                     currentPerson = null;

              }

              this.tagName = null;

       }

}

使用DOM讀取XML檔案

除了可以使用 SAX解析XML檔案,大家也可以使用熟悉的DOM來解析XML檔案。 DOM解析XML檔案時,會将XML檔案的所有内容讀取到記憶體中,然後允許您使用DOM API周遊XML樹、檢索所需的資料。使用DOM操作XML的代碼看起來比較直覺,并且,在某些方面比基于SAX的實作更加簡單。但是,因為DOM需要将 XML檔案的所有内容讀取到記憶體中,是以記憶體的消耗比較大,特别對于運作Android的移動裝置來說,因為裝置的資源比較寶貴,是以建議還是采用SAX 來解析XML檔案,當然,如果XML檔案的内容比較小采用DOM是可行的。

import cn.android.xml.domain.Person;

public class DomXMLReader {

public static List<Person> readXML(InputStream inStream) {

       List<Person> persons = new ArrayList<Person>();

       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

       try {

              DocumentBuilder builder = factory.newDocumentBuilder();

              Document dom = builder.parse(inStream);

              Element root = dom.getDocumentElement();

              NodeList items = root.getElementsByTagName("person");//查找所有person節點

              for (int i = 0; i < items.getLength(); i++) {

                     Person person = new Person();

                     //得到第一個person節點

                     Element personNode = (Element) items.item(i);

                     //擷取person節點的id屬性值

                     person.setId(new Integer(personNode.getAttribute("id")));

                     //擷取person節點下的所有子節點(标簽之間的空白節點和name/age元素)

                     NodeList childsNodes = personNode.getChildNodes();

                     for (int j = 0; j < childsNodes.getLength(); j++) {

                     Node node = (Node) childsNodes.item(j);

                     //判斷是否為元素類型

                  if(node.getNodeType() == Node.ELEMENT_NODE){                                                Element childNode = (Element) node;

                                        //判斷是否name元素

                         if ("name".equals(childNode.getNodeName())) {

                          //擷取name元素下Text節點,然後從Text節點擷取資料                                    person.setName(childNode.getFirstChild().getNodeValue());

                         } else if (“age”.equals(childNode.getNodeName())) {

                     person.setAge(new Short(childNode.getFirstChild().getNodeValue()));

                         }

                     }

                      }

                  persons.add(person);

              }

              inStream.close();

       } catch (Exception e) {

              e.printStackTrace();

       }

       return persons;

}

使用Pull解析器讀取XML檔案

除了可以使用 SAX和DOM解析XML檔案,大家也可以使用Android内置的Pull解析器解析XML檔案。 Pull解析器的運作方式與 SAX 解析器相似。它提供了類似的事件,如:開始元素和結束元素事件,使用parser.next()可以進入下一個元素并觸發相應事件。事件将作為數值代碼被發送,是以可以使用一個switch對感興趣的事件進行處理。當元素開始解析時,調用parser.nextText()方法可以擷取下一個Text類型元素的值。

public class PullXMLReader {

public static List<Person> readXML(InputStream inStream) {

       XmlPullParser parser = Xml.newPullParser();

       try {

       parser.setInput(inStream, "UTF-8");

       int eventType = parser.getEventType();

       Person currentPerson = null;

       List<Person> persons = null;

       while (eventType != XmlPullParser.END_DOCUMENT) {

              switch (eventType) {

              case XmlPullParser.START_DOCUMENT://文檔開始事件,可以進行資料初始化處理

                     persons = new ArrayList<Person>();

                     break;

              case XmlPullParser.START_TAG://開始元素事件

                     String name = parser.getName();

                     if (name.equalsIgnoreCase("person")) {

                            currentPerson = new Person();

                            currentPerson.setId(new Integer(parser.getAttributeValue(null, "id")));

                     } else if (currentPerson != null) {

                            if (name.equalsIgnoreCase("name")) {

                     currentPerson.setName(parser.nextText());// 如果後面是Text元素,即傳回它的值

                            } else if (name.equalsIgnoreCase("age")) {

                                   currentPerson.setAge(new Short(parser.nextText()));

                            }

                     }

                     break;

                  case XmlPullParser.END_TAG://結束元素事件

                     if (parser.getName().equalsIgnoreCase("person") && currentPerson != null) {

                            persons.add(currentPerson);

                            currentPerson = null;

                     }

                     break;

              }

              eventType = parser.next();

       }

       inStream.close();

       return persons;

       } catch (Exception e) {

              e.printStackTrace();

       }

       return null;

}

}

使用Pull解析器生成XML檔案

有些時候,我們需要生成一個XML檔案,生成XML檔案的方法有很多,如:可以隻使用一個StringBuilder組拼XML内容,然後把内容寫入到檔案中;或者使用DOM API生成XML檔案,或者也可以使用pull解析器生成XML檔案,這裡推薦大家使用Pull解析器。

使用Pull解析器生成一個與android.xml檔案内容相同的myandroid.xml檔案,代碼在本頁下方備注

使用代碼如下(生成XML檔案):

File xmlFile = new File("myandroid.xml");

FileOutputStream outStream = new FileOutputStream(xmlFile);

OutputStreamWriter outStreamWriter = new OutputStreamWriter(outStream, "UTF-8");

BufferedWriter writer = new BufferedWriter(outStreamWriter);

writeXML(persons, writer);

writer.flush();

writer.close();

如果隻想得到生成的xml字元串内容,可以使用StringWriter:

StringWriter writer = new StringWriter();

writeXML(persons, writer);

String content = writer.toString();

public static String writeXML(List<Person> persons, Writer writer){

    XmlSerializer serializer = Xml.newSerializer();

    try {

        serializer.setOutput(writer);

        serializer.startDocument("UTF-8", true);

      //第一個參數為命名空間,如果不使用命名空間,可以設定為null

        serializer.startTag("", "persons");

        for (Person person : persons){

            serializer.startTag("", "person");

            serializer.attribute("", "id", person.getId().toString());

            serializer.startTag("", "name");

            serializer.text(person.getName());

            serializer.endTag("", "name");

            serializer.startTag("", "age");

            serializer.text(person.getAge().toString());

            serializer.endTag("", "age");

           serializer.endTag("", "person");

        }

        serializer.endTag("", "persons");

        serializer.endDocument();

        return writer.toString();

    } catch (Exception e) {

        e.printStackTrace();

    }

    return null;

}

使用SharedPreferences進行資料存儲

很多時候我們開發的軟體需要向使用者提供軟體參數設定功能,例如我們常用的QQ,使用者可以設定是否允許陌生人添加自己為好友。對于軟體配置參數的儲存,如果是window軟體通常我們會采用ini檔案進行儲存,如果是j2se應用,我們會采用properties屬性檔案進行儲存。如果是 Android應用,我們最适合采用什麼方式儲存軟體配置參數呢?Android平台給我們提供了一個SharedPreferences類,它是一個輕量級的存儲類,特别适合用于儲存軟體配置參數。使用SharedPreferences儲存資料,其背後是用xml檔案存放資料,檔案存放在/data /data/<package name>/shared_prefs目錄下:

SharedPreferences sharedPreferences=getSharedPreferences("android", Context.MODE_PRIVATE);

Editor editor = sharedPreferences.edit();//擷取編輯器

editor.putString("name", "安桌");

editor.putInt("age", 4);

editor.commit();//送出修改

生成的android.xml檔案内容如下:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>

<map>

<string name="name">安桌</string>

<int name="age" value="4" />

</map>

因為SharedPreferences背後是使用xml檔案儲存資料,getSharedPreferences(name,mode)方法的第一個參數用于指定該檔案的名稱,名稱不用帶字尾,字尾會由Android自動加上。方法的第二個參數指定檔案的操作模式,共有四種操作模式,這四種模式前面介紹使用檔案方式儲存資料時已經講解過。如果希望SharedPreferences背後使用的xml檔案能被其他應用讀和寫,可以指定 Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE權限。

另外Activity還提供了另一個getPreferences(mode)方法操作SharedPreferences,這個方法預設使用目前類不帶包名的類名作為檔案的名稱。

通路SharedPreferences中的資料

通路SharedPreferences中的資料代碼如下:

SharedPreferences sharedPreferences=getSharedPreferences("android", Context.MODE_PRIVATE);

//getString()第二個參數為預設值,如果preference中不存在該key,将傳回預設值

String name = sharedPreferences.getString("name", "");

int age = sharedPreferences.getInt("age", 1);

如果通路其他應用中的Preference,前提條件是:該preference建立時指定了 Context.MODE_WORLD_READABLE或者Context.MODE_WORLD_WRITEABLE權限。如:有個<package name>為cn.android.action的應用使用下面語句建立了preference。

getSharedPreferences("android", Context.MODE_WORLD_READABLE);

其他應用要通路上面應用的preference,首先需要建立上面應用的Context,然後通過Context 通路preference ,通路preference時會在應用所在包下的shared_prefs目錄找到preference :

Context otherAppsContext = createPackageContext("cn.android.action", Context.CONTEXT_IGNORE_SECURITY);

SharedPreferences sharedPreferences = otherAppsContext.getSharedPreferences("android", Context.MODE_WORLD_READABLE);

String name = sharedPreferences.getString("name", "");

int age = sharedPreferences.getInt("age", 0);

如果不通過建立Context通路其他應用的preference,可以以讀取xml檔案方式直接通路其他應用preference對應的xml檔案,如:

File xmlFile = new File(“/data/data/<package name>/shared_prefs/android.xml”);//<package name>應替換成應用的包名

使用嵌入式關系型SQLite資料庫存儲資料

除了可以使用檔案或SharedPreferences存儲資料,還可以選擇使用SQLite資料庫存儲資料。

在Android平台上,內建了一個嵌入式關系型資料庫—SQLite,SQLite3支援 NULL、INTEGER、REAL(浮點數字)、TEXT(字元串文本)和BLOB(二進制對象)資料類型,雖然它支援的類型隻有五種,但實際上 sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等資料類型,隻不過在運算或儲存時會轉成對應的五種資料類型。 SQLite最大的特點是你可以儲存任何類型的資料到任何字段中,無論這列聲明的資料類型是什麼。例如:可以在Integer類型的字段中存放字元串,或者在布爾型字段中存放浮點數,或者在字元型字段中存放日期型值。 但有一種情況例外:定義為INTEGER PRIMARY KEY的字段隻能存儲64位整數, 當向這種字段中儲存除整數以外的資料時,将會産生錯誤。 另外, SQLite 在解析CREATE TABLE 語句時,會忽略 CREATE TABLE 語句中跟在字段名後面的資料類型資訊,如下面語句會忽略 name字段的類型資訊:

CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))

SQLite可以解析大部分标準SQL語句,如:

查詢語句:select * from 表名 where 條件子句 group by 分組字句 having ... order by 排序子句

如:select * from person select * from person order by id desc

        select name from person group by name having count(*)>1

分頁SQL與mysql類似,下面SQL語句擷取5條記錄,跳過前面3條記錄

select * from Account limit 5 offset 3 或者 select * from Account limit 3,5

插入語句:insert into 表名(字段清單) values(值清單)。如:

insert into person(name, age) values(‘傳智’,3)

更新語句:update 表名 set 字段名=值 where 條件子句。如:

update person set name=‘傳智‘ where id=10

删除語句:delete from 表名 where 條件子句。如:delete from person where id=10

使用SQLiteOpenHelper對資料庫進行版本管理

因為我們開發的軟體可能會安裝在成百上千個使用者的手機上,如果應用使用到了SQLite資料庫,我們必須在使用者初次使用軟體時建立出應用使用到的資料庫表結構及添加一些初始化記錄,另外在軟體更新的時候,也需要對資料表結構進行更新。那麼,我們如何才能實作在使用者初次使用或更新軟體時自動在使用者的手機上建立出應用需要的資料庫表呢?總不能在每個需要安裝此軟體的手機上通過手工方式建立資料庫表吧?因為這種需求是每個資料庫應用都要面臨的,是以在 Android系統,為我們提供了一個名為SQLiteOpenHelper的抽象類,必須繼承它才能使用,它是通過對資料庫版本進行管理來實作前面提出的需求。

為了實作對資料庫版本進行管理,SQLiteOpenHelper類提供了兩個重要的方法,分别是 onCreate(SQLiteDatabase db)和onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion),前者用于初次使用軟體時生成資料庫表,後者用于更新軟體時更新資料庫表結構。當調用SQLiteOpenHelper的 getWritableDatabase()或者getReadableDatabase()方法擷取用于操作資料庫的SQLiteDatabase執行個體的時候,如果資料庫不存在,Android系統會自動生成一個資料庫,接着調用onCreate()方法,onCreate()方法在初次生成資料庫時才會被調用,在onCreate()方法裡可以生成資料庫表結構及添加一些應用使用到的初始化資料。onUpgrade()方法在資料庫的版本發生變化時會被調用,一般在軟體更新時才需改變版本号,而資料庫的版本是由程式員控制的,假設資料庫現在的版本是1,由于業務的變更,修改了資料庫表結構,這時候就需要更新軟體,更新軟體時希望更新使用者手機裡的資料庫表結構,為了實作這一目的,可以把原來的資料庫版本設定為2(有同學問設定為3行不行?當然可以,如果你願意,設定為100也行),并且在onUpgrade()方法裡面實作表結構的更新。當軟體的版本更新次數比較多,這時在onUpgrade()方法裡面可以根據原版号和目标版本号進行判斷,然後作出相應的表結構及資料更新。

getWritableDatabase()和getReadableDatabase()方法都可以擷取一個用于操作資料庫的 SQLiteDatabase執行個體。但getWritableDatabase() 方法以讀寫方式打開資料庫,一旦資料庫的磁盤空間滿了,資料庫就隻能讀而不能寫,倘若使用的是getWritableDatabase() 方法就會出錯。getReadableDatabase()方法先以讀寫方式打開資料庫,如果資料庫的磁盤空間滿了,就會打開失敗,當打開失敗後會繼續嘗試以隻讀方式打開資料庫。

public class DatabaseHelper extends SQLiteOpenHelper {

    //類沒有執行個體化,是不能用作父類構造器的參數,必須聲明為靜态

         private static final String name = "android"; //資料庫名稱

         private static final int version = 1; //資料庫版本

         public DatabaseHelper(Context context) {

//第三個參數CursorFactory指定在執行查詢時獲得一個遊标執行個體的工廠類,設定為null,代表使用系統預設的工廠類

                super(context, name, null, version);

         }

        @Override public void onCreate(SQLiteDatabase db) {

              db.execSQL("CREATE TABLE IF NOT EXISTS person (personid integer primary key autoincrement, name varchar(20), age INTEGER)");  

         }

        @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

               db.execSQL("DROP TABLE IF EXISTS person");

               onCreate(db);

         }

}

上面onUpgrade()方法在資料庫版本每次發生變化時都會把使用者手機上的資料庫表删除,然後再重新建立。一般在實際項目中是不能這樣做的,正确的做法是在更新資料庫表結構時,還要考慮使用者存放于資料庫中的資料不會丢失。

使用SQLiteDatabase操作SQLite資料庫

Android提供了一個名為SQLiteDatabase的類,該類封裝了一些操作資料庫的API,使用該類可以完成對資料進行添加 (Create)、查詢(Retrieve)、更新(Update)和删除(Delete)操作(這些操作簡稱為CRUD)。對 SQLiteDatabase的學習,我們應該重點掌握execSQL()和rawQuery()方法。 execSQL()方法可以執行insert、delete、update和CREATE TABLE之類有更改行為的SQL語句; rawQuery()方法可以執行select語句。

execSQL()方法的使用例子:

SQLiteDatabase db = ....;

db.execSQL("insert into person(name, age) values('安桌', 4)");

db.close();

執行上面SQL語句會往person表中添加進一條記錄,在實際應用中, 語句中的“安桌”這些參數值會由使用者輸入界面提供,如果把使用者輸入的内容原樣組拼到上面的insert語句, 當使用者輸入的内容含有單引号時,組拼出來的SQL語句就會存在文法錯誤。要解決這個問題需要對單引号進行轉義,也就是把單引号轉換成兩個單引号。有些時候使用者往往還會輸入像“ & ”這些特殊SQL符号,為保證組拼好的SQL語句文法正确,必須對SQL語句中的這些特殊SQL符号都進行轉義,顯然,對每條SQL語句都做這樣的處理工作是比較煩瑣的。 SQLiteDatabase類提供了一個重載後的execSQL(String sql, Object[] bindArgs)方法,使用這個方法可以解決前面提到的問題,因為這個方法支援使用占位符參數(?)。使用例子如下:

SQLiteDatabase db = ....;

db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"安桌", 4});

db.close();

execSQL(String sql, Object[] bindArgs)方法的第一個參數為SQL語句,第二個參數為SQL語句中占位符參數的值,參數值在數組中的順序要和占位符的位置對應。

SQLiteDatabase的rawQuery() 用于執行select語句,使用例子如下:

SQLiteDatabase db = ....;

Cursor cursor = db.rawQuery(“select * from person”, null);

while (cursor.moveToNext()) {

       int personid = cursor.getInt(0); //擷取第一列的值,第一列的索引從0開始

       String name = cursor.getString(1);//擷取第二列的值

       int age = cursor.getInt(2);//擷取第三列的值

}

cursor.close();

db.close();

rawQuery()方法的第一個參數為select語句;第二個參數為select語句中占位符參數的值,如果select語句沒有使用占位符,該參數可以設定為null。帶占位符參數的select語句使用例子如下:

Cursor cursor = db.rawQuery("select * from person where name like ? and age=?", new String[]{"%傳智%", "4"});

Cursor是結果集遊标,用于對結果集進行随機通路,如果大家熟悉jdbc, 其實Cursor與JDBC中的ResultSet作用很相似。使用moveToNext()方法可以将遊标從目前行移動到下一行,如果已經移過了結果集的最後一行,傳回結果為false,否則為true。另外Cursor 還有常用的moveToPrevious()方法(用于将遊标從目前行移動到上一行,如果已經移過了結果集的第一行,傳回值為false,否則為true )、moveToFirst()方法(用于将遊标移動到結果集的第一行,如果結果集為空,傳回值為false,否則為true )和moveToLast()方法(用于将遊标移動到結果集的最後一行,如果結果集為空,傳回值為false,否則為true ) 。

除了前面給大家介紹的execSQL()和rawQuery()方法, SQLiteDatabase還專門提供了對應于添加、删除、更新、查詢的操作方法: insert()、delete()、update()和query() 。這些方法實際上是給那些不太了解SQL文法的菜鳥使用的,對于熟悉SQL文法的程式員而言,直接使用execSQL()和rawQuery()方法執行 SQL語句就能完成資料的添加、删除、更新、查詢操作。

Insert()方法用于添加資料,各個字段的資料使用ContentValues進行存放。 ContentValues類似于MAP,相對于MAP,它提供了存取資料對應的put(String key, Xxx value)和getAsXxx(String key)方法, key為字段名稱,value為字段值,Xxx指的是各種常用的資料類型,如:String、Integer等。

SQLiteDatabase db = databaseHelper.getWritableDatabase();

ContentValues values = new ContentValues();

values.put("name", "安桌");

values.put("age", 4);

long rowid = db.insert(“person”, null, values);//傳回新添記錄的行号,與主鍵id無關

不管第三個參數是否包含資料,執行Insert()方法必然會添加一條記錄,如果第三個參數為空,會添加一條除主鍵之外其他字段值為Null的記錄。Insert()方法内部實際上通過構造insert語句完成資料的添加,Insert()方法的第二個參數用于指定空值字段的名稱,相信大家對此參數會感到疑惑,此參數的作用是幹嘛的?是這樣的:如果第三個參數values 為Null或者元素個數為0, Insert()方法必然要添加一條除了主鍵之外其它字段為Null值的記錄,為了滿足這條insert語句的文法, insert語句必須給定一個字段名,如:insert into person(name) values(NULL),倘若不給定字段名 , insert語句就成了這樣: insert into person() values(),顯然這不滿足标準SQL的文法。對于字段名,建議使用主鍵之外的字段,如果使用了INTEGER類型的主鍵字段,執行類似insert into person(personid) values(NULL)的insert語句後,該主鍵字段值也不會為NULL。如果第三個參數values 不為Null并且元素的個數大于0 ,可以把第二個參數設定為null。

delete()方法的使用:

SQLiteDatabase db = databaseHelper.getWritableDatabase();

db.delete("person", "personid<?", new String[]{"2"});

db.close();

上面代碼用于從person表中删除personid小于2的記錄。

update()方法的使用:

SQLiteDatabase db = databaseHelper.getWritableDatabase();

ContentValues values = new ContentValues();

values.put(“name”, “安桌”);//key為字段名,value為值

db.update("person", values, "personid=?", new String[]{"1"});

db.close();

上面代碼用于把person表中personid等于1的記錄的name字段的值改為“安桌”。

query()方法實際上是把select語句拆分成了若幹個組成部分,然後作為方法的輸入參數:

SQLiteDatabase db = databaseHelper.getWritableDatabase();

Cursor cursor = db.query("person", new String[]{"personid,name,age"}, "name like ?", new String[]{"%傳智%"}, null, null, "personid desc", "1,2");

while (cursor.moveToNext()) {

         int personid = cursor.getInt(0); //擷取第一列的值,第一列的索引從0開始

        String name = cursor.getString(1);//擷取第二列的值

        int age = cursor.getInt(2);//擷取第三列的值

}

cursor.close();

db.close();

上面代碼用于從person表中查找name字段含有“傳智”的記錄,比對的記錄按personid降序排序,對排序後的結果略過第一條記錄,隻擷取2條記錄。

query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit)方法各參數的含義:

table:表名。相當于select語句from關鍵字後面的部分。如果是多表聯合查詢,可以用逗号将兩個表名分開。

columns:要查詢出來的列名。相當于select語句select關鍵字後面的部分。

selection:查詢條件子句,相當于select語句where關鍵字後面的部分,在條件子句允許使用占位符“?”

selectionArgs:對應于selection語句中占位符的值,值在數組中的位置與占位符在語句中的位置必須一緻,否則就會有異常。

groupBy:相當于select語句group by關鍵字後面的部分

having:相當于select語句having關鍵字後面的部分

orderBy:相當于select語句order by關鍵字後面的部分,如:personid desc, age asc;

limit:指定偏移量和擷取的記錄數,相當于select語句limit關鍵字後面的部分。

public class DatabaseHelper extends SQLiteOpenHelper {

         private static final String name = "android"; //資料庫名稱

         private static final int version = 1; //資料庫版本

         ......略

}

public class HelloActivity extends Activity {

    @Override public void onCreate(Bundle savedInstanceState) {

        ......

        Button button =(Button) this.findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener(){

       public void onClick(View v) {

              DatabaseHelper databaseHelper = new DatabaseHelper(HelloActivity.this);

              SQLiteDatabase db = databaseHelper.getWritableDatabase();

              db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"安桌", 4});

              db.close();

       }});      

    }

}

第一次調用getWritableDatabase()或getReadableDatabase()方法後,SQLiteOpenHelper會緩存目前的SQLiteDatabase執行個體,SQLiteDatabase執行個體正常情況下會維持資料庫的打開狀态,是以在你不再需要SQLiteDatabase執行個體時,請及時調用close()方法釋放資源。一旦SQLiteDatabase執行個體被緩存,多次調用getWritableDatabase()或getReadableDatabase()方法得到的都是同一執行個體。

cuihai 2011-03-03 12:11

使用事務操作SQLite資料庫

使用SQLiteDatabase的beginTransaction()方法可以開啟一個事務,程式執行到endTransaction() 方法時會檢查事務的标志是否為成功,如果程式執行到endTransaction()之前調用了setTransactionSuccessful() 方法設定事務的标志為成功則送出事務,如果沒有調用setTransactionSuccessful() 方法則復原事務。使用例子如下:

SQLiteDatabase db = ....;

db.beginTransaction();//開始事務

try {

    db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"安桌", 4});

    db.execSQL("update person set name=? where personid=?", new Object[]{"傳智", 1});

    db.setTransactionSuccessful();//調用此方法會在執行到endTransaction() 時送出目前事務,如果不調用此方法會復原事務

} finally {

    db.endTransaction();//由事務的标志決定是送出事務,還是復原事務

}

db.close();

上面兩條SQL語句在同一個事務中執行。

使用ContentProvider共享資料

當應用繼承ContentProvider類,并重寫該類用于提供資料和存儲資料的方法,就可以向其他應用共享其資料。雖然使用其他方法也可以對外共享資料,但資料通路方式會因資料存儲的方式而不同,如:采用檔案方式對外共享資料,需要進行檔案操作讀寫資料;采用 sharedpreferences共享資料,需要使用sharedpreferences API讀寫資料。而使用ContentProvider共享資料的好處是統一了資料通路方式。

當應用需要通過ContentProvider對外共享資料時,第一步需要繼承ContentProvider并重寫下面方法:

public class PersonContentProvider extends ContentProvider{

   public boolean onCreate()

   public Uri insert(Uri uri, ContentValues values)

   public int delete(Uri uri, String selection, String[] selectionArgs)

   public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

   public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

   public String getType(Uri uri)}

第二步需要在AndroidManifest.xml使用<provider>對該ContentProvider進行配置,為了能讓其他應用找到該ContentProvider , ContentProvider 采用了authorities(主機名/域名)對它進行唯一辨別,你可以把 ContentProvider看作是一個網站(想想,網站也是提供資料者),authorities 就是他的域名:

<manifest .... >

    <application android:icon="@drawable/icon" android:label="@string/app_name">

        <provider android:name=".PersonContentProvider" android:authorities="cn.android.provider.personprovider"/>

    </application>

</manifest>

注意:一旦應用繼承了ContentProvider類,後面我們就會把這個應用稱為ContentProvider(内容提供者)。

public class PersonContentProvider extends ContentProvider{

       //資料集的MIME類型字元串則應該以vnd.android.cursor.dir/開頭

       public static final String PERSONS_TYPE = "vnd.android.cursor.dir/person";

       //單一資料的MIME類型字元串應該以vnd.android.cursor.item/開頭

       public static final String PERSONS_ITEM_TYPE = "vnd.android.cursor.item/person";

       public static final String AUTHORITY = "cn.android.provider.personprovider";//主機名

       public static final int PERSONS = 1;

       public static final int PERSON = 2;

       public static final Uri PERSONS_URI = Uri.parse("content://" + AUTHORITY + "/person");

       private static final UriMatcher sMatcher;

       static {

              sMatcher = new UriMatcher(UriMatcher.NO_MATCH);//常量UriMatcher.NO_MATCH表示不比對任何路徑的傳回碼

              //如果match()方法比對content://cn.android.provider.personprovider/person路徑,傳回比對碼為PERSONS

              sMatcher.addURI(AUTHORITY, "person", PERSONS);

              //如果match()方法比對content://cn.android.provider.personprovider/person/230路徑,傳回比對碼為PERSON

              sMatcher.addURI(AUTHORITY, "person/#", PERSON);

       }

       private DatabaseHelper databaseHelper;

       @Override

       public boolean onCreate() {

              databaseHelper = new DatabaseHelper(this.getContext());

              return true;

       }

       @Override

       public Uri insert(Uri uri, ContentValues values) {

              SQLiteDatabase db = databaseHelper.getWritableDatabase();

              if (sMatcher.match(uri) != PERSONS) {

                     throw new IllegalArgumentException("Unknown URI " + uri);

              }

              long rowId = db.insert("person", "personid", values);//往person表添加一條記錄

              db.close();

              if(rowId>0){//如果添加成功

                     //ContentUris是content URI的一個輔助類。下面方法負責把rowId和PERSONS_URI連接配接成一個新的Uri,

                     //生成的Uri如:content://cn.android.provider.personprovider/person/10

                     return ContentUris.withAppendedId(PERSONS_URI, rowId);

              }

              throw new SQLException("Failed to insert row into " + uri);//抛出添加失敗資訊

       }

       @Override

       public int delete(Uri uri, String selection, String[] selectionArgs) {

              SQLiteDatabase db = databaseHelper.getWritableDatabase();

              int count = 0;

              switch (sMatcher.match(uri)) {

                     case PERSONS:

                            count = db.delete("person", selection, selectionArgs);

                            break;

                     case PERSON:

                            //下面的方法用于從URI中解析出id,對這樣的路徑content://cn.android.provider.personprovider/person/10 進行解析,傳回值為10

                            long personid = ContentUris.parseId(uri);

                            String where = "personid="+ personid;//删除指定id的記錄

       where += !TextUtils.isEmpty(selection) ? " and ("+ selection +")" : "";//把其它條件附加上

                            count = db.delete("person", where, selectionArgs);

                            break;

                     default:

                            throw new IllegalArgumentException("Unknown URI " + uri);

              }

              db.close();

              return count;

       }

       @Override

       public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

              SQLiteDatabase db = databaseHelper.getWritableDatabase();

              int count = 0;

              switch (sMatcher.match(uri)) {

                     case PERSONS:

                            count = db.update("person", values, selection, selectionArgs);

                            break;

                     case PERSON:

       //下面的方法用于從URI中解析出id,對這樣的路徑content://cn.android.provider.personprovider/person/10 進行解析,傳回值為10

              long personid = ContentUris.parseId(uri);

              String where = "personid="+ personid;//删除指定id的記錄

       where += !TextUtils.isEmpty(selection) ? " and ("+ selection +")" : "";//把其它條件附加上

              count = db.update("person", values, where, selectionArgs);

              break;

              default:

              throw new IllegalArgumentException("Unknown URI " + uri);

              }

              db.close();

              return count;

       }

       @Override

       public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

              SQLiteDatabase db = databaseHelper.getReadableDatabase();

              Cursor cursor;

              switch (sMatcher.match(uri)) {

              case PERSONS:

              cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder);

                            break;

                     case PERSON:

                            //下面的方法用于從URI中解析出id,對這樣的路徑content://cn.android.provider.personprovider/person/10 進行解析,傳回值為10

       long personid = ContentUris.parseId(uri);

       String where = "personid="+ personid;//删除指定id的記錄

       where += !TextUtils.isEmpty(selection) ? " and ("+ selection +")" : "";//把其它條件附加上

       cursor = db.query("person", projection, where, selectionArgs, null, null, sortOrder);

       break;

       default:

       throw new IllegalArgumentException("Unknown URI " + uri);

              }

              cursor.getCount();//在資料庫關閉前擷取所有資料

              db.close();

              return cursor;

       }

       @Override

       public String getType(Uri uri) {

              switch (sMatcher.match(uri)) {

                     case PERSONS:

                            return PERSONS_TYPE;

                     case PERSON:

                            return PERSONS_ITEM_TYPE;

                     default:

                            throw new IllegalArgumentException("Unknown URI " + uri);

              }

       }

}

Uri介紹

Uri代表了要操作的資料,Uri主要包含了兩部分資訊:1》需要操作的ContentProvider ,2》對ContentProvider中的什麼資料進行操作,一個Uri由以下幾部分組成:

ContentProvider(内容提供者)的scheme已經由Android所規定, scheme為:content://

主機名(或叫Authority)用于唯一辨別這個ContentProvider,外部調用者可以根據這個辨別來找到它。

路徑(path)可以用來表示我們要操作的資料,路徑的建構應根據業務而定,如下:

要操作person表中id為10的記錄,可以建構這樣的路徑:/person/10

要操作person表中id為10的記錄的name字段, person/10/name

要操作person表中的所有記錄,可以建構這樣的路徑:/person

要操作xxx表中的記錄,可以建構這樣的路徑:/xxx

當然要操作的資料不一定來自資料庫,也可以是檔案等他存儲方式,如下:

要操作xml檔案中person節點下的name節點,可以建構這樣的路徑:/person/name

如果要把一個字元串轉換成Uri,可以使用Uri類中的parse()方法,如下:

Uri uri = Uri.parse("content://cn.android.provider.personprovider/person")

cuihai 2011-03-03 12:11

UriMatcher類使用介紹

因為Uri代表了要操作的資料,是以我們經常需要解析Uri,并從Uri中擷取資料。Android系統提供了兩個用于操作Uri的工具類,分别為UriMatcher 和ContentUris 。掌握它們的使用,會便于我們的開發工作。

UriMatcher類用于比對Uri,它的用法如下:

首先第一步把你需要比對Uri路徑全部給注冊上,如下:

//常量UriMatcher.NO_MATCH表示不比對任何路徑的傳回碼

UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);

//如果match()方法比對content://cn.android.provider.personprovider/person路徑,傳回比對碼為1

sMatcher.addURI(“cn.android.provider.personprovider”, “person”, 1);//添加需要比對uri,如果比對就會傳回比對碼

//如果match()方法比對content://cn.android.provider.personprovider/person/230路徑,傳回比對碼為2

sMatcher.addURI(“cn.android.provider.personprovider”, “person/#”, 2);//#号為通配符

switch (sMatcher.match(Uri.parse("content://cn.android.provider.personprovider/person/10"))) {

   case 1

    break;

   case 2

    break;

   default://不比對

    break;

}

注冊完需要比對的Uri後,就可以使用sMatcher.match(uri)方法對輸入的Uri進行比對,如果比對就傳回比對碼,比對碼是調用addURI()方法傳入的第三個參數,假設比對content://cn.android.provider.personprovider /person路徑,傳回的比對碼為1

ContentUris類使用介紹

ContentUris類用于擷取Uri路徑後面的ID部分,它有兩個比較實用的方法:

withAppendedId(uri, id)用于為路徑加上ID部分:

Uri uri = Uri.parse("content://cn.android.provider.personprovider/person")

Uri resultUri = ContentUris.withAppendedId(uri, 10);

//生成後的Uri為:content://cn.android.provider.personprovider/person/10

parseId(uri)方法用于從路徑中擷取ID部分:

Uri uri = Uri.parse("content://cn.android.provider.personprovider/person/10")

long personid = ContentUris.parseId(uri);//擷取的結果為:10

使用ContentProvider共享資料

ContentProvider類主要方法的作用:

public boolean onCreate()

該方法在ContentProvider建立後就會被調用, Android開機後, ContentProvider在其它應用第一次通路它時才會被建立。

public Uri insert(Uri uri, ContentValues values)

該方法用于供外部應用往ContentProvider添加資料。

public int delete(Uri uri, String selection, String[] selectionArgs)

該方法用于供外部應用從ContentProvider删除資料。

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

該方法用于供外部應用更新ContentProvider中的資料。

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

該方法用于供外部應用從ContentProvider中擷取資料。

public String getType(Uri uri)

該方法用于傳回目前Url所代表資料的MIME類型。如果操作的資料屬于集合類型,那麼MIME類型字元串應該以 vnd.android.cursor.dir/開頭,例如:要得到所有person記錄的Uri為content: //cn.android.provider.personprovider/person,那麼傳回的MIME類型字元串應該為:“vnd.android.cursor.dir/person”。如果要操作的資料屬于非集合類型資料,那麼MIME類型字元串應該以 vnd.android.cursor.item/開頭,例如:得到id為10的person記錄,Uri為content: //cn.android.provider.personprovider/person/10,那麼傳回的MIME類型字元串應該為:“vnd.android.cursor.item/person”。

使用ContentResolver操作ContentProvider中的資料

當外部應用需要對ContentProvider中的資料進行添加、删除、修改和查詢操作時,可以使用ContentResolver 類來完成,要擷取ContentResolver 對象,可以使用Activity提供的getContentResolver()方法。 ContentResolver 類提供了與ContentProvider類相同簽名的四個方法:

public Uri insert(Uri uri, ContentValues values)

該方法用于往ContentProvider添加資料。

public int delete(Uri uri, String selection, String[] selectionArgs)

該方法用于從ContentProvider删除資料。

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

該方法用于更新ContentProvider中的資料。

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

該方法用于從ContentProvider中擷取資料。

這些方法的第一個參數為Uri,代表要操作的是哪個ContentProvider和對其中的什麼資料進行操作,假設給定的是: Uri.parse(“content://cn.android.provider.personprovider/person/10”),那麼将會對主機名為cn.android.provider.personprovider的ContentProvider進行操作,操作的資料為person 表中id為10的記錄。

使用ContentResolver對ContentProvider中的資料進行添加、删除、修改和查詢操作:

ContentResolver resolver = getContentResolver();

Uri uri = Uri.parse("content://cn.android.provider.personprovider/person");

//添加一條記錄

ContentValues values = new ContentValues();

values.put("name", "android");

values.put("age", 25);

resolver.insert(uri, values);          

//擷取person表中所有記錄

Cursor cursor = resolver.query(uri, null, null, null, "personid desc");

while(cursor.moveToNext()){

       Log.i("ContentTest", "personid="+ cursor.getInt(0)+ ",name="+ cursor.getString(1));

}

//把id為1的記錄的name字段值更改新為liming

ContentValues updateValues = new ContentValues();

updateValues.put("name", "liming");

Uri updateIdUri = ContentUris.withAppendedId(uri, 2);

resolver.update(updateIdUri, updateValues, null, null);

//删除id為2的記錄

Uri deleteIdUri = ContentUris.withAppendedId(uri, 2);

resolver.delete(deleteIdUri, null, null);

從Internet擷取資料

利用HttpURLConnection對象,我們可以從網絡中擷取網頁資料.

URL url = new URL("http://www.sohu.com");

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setConnectTimeout(6* 1000);//設定連接配接逾時

if (conn.getResponseCode() != 200) throw new RuntimeException("請求url失敗");

InputStream is = conn.getInputStream();//得到網絡傳回的輸入流

String result = readData(is, "GBK");

conn.disconnect();

System.out.println(result);

//第一個參數為輸入流,第二個參數為字元集編碼

public static String readData(InputStream inSream, String charsetName) throws Exception{

       ByteArrayOutputStream outStream = new ByteArrayOutputStream();

       byte[] buffer = new byte[1024];

       int len = -1;

       while( (len = inSream.read(buffer)) != -1 ){

              outStream.write(buffer, 0, len);

       }

       byte[] data = outStream.toByteArray();

       outStream.close();

       inSream.close();

       return new String(data, charsetName);

<!-- 通路internet權限 -->

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

利用HttpURLConnection對象,我們可以從網絡中擷取檔案資料.

URL url = new URL("http://photocdn.sohu.com/20100125/Img269812337.jpg");

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setConnectTimeout(6* 1000);

if (conn.getResponseCode() != 200) throw new RuntimeException("請求url失敗");

InputStream is = conn.getInputStream();

readAsFile(is, "Img269812337.jpg");

public static void readAsFile(InputStream inSream, File file) throws Exception{

       FileOutputStream outStream = new FileOutputStream(file);

       byte[] buffer = new byte[1024];

       int len = -1;

       while( (len = inSream.read(buffer)) != -1 ){

              outStream.write(buffer, 0, len);

       }

      outStream.close();

       inSream.close();

}

利用HttpURLConnection對象,我們可以向網絡發送請求參數.

String requestUrl = "http://localhost:8080/android/contanctmanage.do";

Map<String, String> requestParams = new HashMap<String, String>();

requestParams.put("age", "12");

requestParams.put("name", "中國");

StringBuilder params = new StringBuilder();

for(Map.Entry<String, String> entry : requestParams.entrySet()){

       params.append(entry.getKey());

       params.append("=");

       params.append(URLEncoder.encode(entry.getValue(), "UTF-8"));

       params.append("&");

}

if (params.length() > 0) params.deleteCharAt(params.length() - 1);

byte[] data = params.toString().getBytes();

URL realUrl = new URL(requestUrl);

HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();

conn.setDoOutput(true);//發送POST請求必須設定允許輸出

conn.setUseCaches(false);//不使用Cache

conn.setRequestMethod("POST");              

conn.setRequestProperty("Connection", "Keep-Alive");//維持長連接配接

conn.setRequestProperty("Charset", "UTF-8");

conn.setRequestProperty("Content-Length", String.valueOf(data.length));

conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");

DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());

outStream.write(data);

outStream.flush();

if( conn.getResponseCode() == 200 ){

        String result = readAsString(conn.getInputStream(), "UTF-8");

利用HttpURLConnection對象,我們可以向網絡發送xml資料.

StringBuilder xml = new StringBuilder();

xml.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");

xml.append("<M1 V=10000>");

xml.append("<U I=1 D=\"N73\">中國</U>");

xml.append("</M1>");

byte[] xmlbyte = xml.toString().getBytes("UTF-8");

URL url = new URL("http://localhost:8080/android/contanctmanage.do?method=readxml");

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setConnectTimeout(6* 1000);

conn.setDoOutput(true);//允許輸出

conn.setUseCaches(false);//不使用Cache

conn.setRequestMethod("POST");              

conn.setRequestProperty("Connection", "Keep-Alive");//維持長連接配接

conn.setRequestProperty("Charset", "UTF-8");

conn.setRequestProperty("Content-Length", String.valueOf(xmlbyte.length));

conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");

DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());

outStream.write(xmlbyte);//發送xml資料

outStream.flush();

if (conn.getResponseCode() != 200) throw new RuntimeException("請求url失敗");

InputStream is = conn.getInputStream();//擷取傳回資料

String result = readAsString(is, "UTF-8");

outStream.close();

cuihai 2011-03-03 12:12

應用程式元件

Android的核心功能之一就是一個應用程式可以使用其它應用程式的元素(如果那個應用程式允許的話)。比如說,如果你的應用程式需要一個圖檔卷動清單,而另一個應用程式已經開發了一個合用的而又允許别人使用的話,你可以直接調用那個卷動清單來完成工作,而不用自己再開發一個。你的應用程式并沒有吸納或連結其它應用程式的代碼,它隻是在有需求的時候啟動了其它應用程式的那個功能部分。

為達到這個目的,系統必須在一個應用程式的一部分被需要時啟動這個應用程式,并将那個部分的Java對象執行個體化。與在其它系統上的應用程式不同,Android應用程式沒有為應用準備一個單獨的程式入口(比如說,沒有main()方法),而是為系統依照需求執行個體化提供了基本的元件。共有四種元件類型:

Activity

Activity是為使用者操作而展示的可視化使用者界面。比如說,一個activity可以展示一個菜單項清單供使用者選擇,或者顯示一些包含說明的照片。一個短消息應用程式可以包括一個用于顯示做為發送對象的聯系人的清單的activity,一個給標明的聯系人寫短信的activity以及翻閱以前的短信和改變設定的activity。盡管它們一起組成了一個内聚的使用者界面,但其中每個activity都與其它的保持獨立。每個都是以Activity類為基類的子類實作。

一個應用程式可以隻有一個activity,或者,如剛才提到的短信應用程式那樣,包含很多個。每個activity的作用,以及其數目,自然取決于應用程式及其設計。一般情況下,總有一個應用程式被标記為使用者在應用程式啟動的時候第一個看到的。從一個activity轉向另一個的方式是靠目前的activity啟動下一個。

每個activity都被給予一個預設的視窗以進行繪制。一般情況下,這個視窗是滿屏的,但它也可以是一個小的位于其它視窗之上的浮動視窗。一個activity也可以使用超過一個的視窗──比如,在activity運作過程中彈出的一個供使用者反應的小對話框,或是當使用者選擇了螢幕上特定項目後顯示的必要資訊。

視窗顯示的可視内容是由一系列視圖構成的,這些視圖均繼承自View基類。每個視圖均控制着視窗中一塊特定的矩形空間。父級視圖包含并組織它子視圖的布局。葉節點視圖(位于視圖層次最底端)在它們控制的矩形中進行繪制,并對使用者對其直接操作做出響應。是以,視圖是activity與使用者進行互動的界面。比如說,視圖可以顯示一個小圖檔,并在使用者指點它的時候産生動作。Android有很多既定的視圖供使用者直接使用,包括按鈕、文本域、卷軸、菜單項、複選框等等。

視圖層次是由Activity.setContentView()方法放入activity的視窗之中的。上下文視圖是位于視圖層次根位置的視圖對象。(參見使用者界面章節擷取關于視圖及層次的更多資訊。)

服務

服務沒有可視化的使用者界面,而是在一段時間内在背景運作。比如說,一個服務可以在使用者做其它事情的時候在背景播放背景音樂、從網絡上擷取一些資料或者計算一些東西并提供給需要這個運算結果的activity使用。每個服務都繼承自Service基類。

一個媒體播放器播放播放清單中的曲目是一個不錯的例子。播放器應用程式可能有一個或多個activity來給使用者選擇歌曲并進行播放。然而,音樂播放這個任務本身不應該為任何activity所處理,因為使用者期望在他們離開播放器應用程式而開始做别的事情時,音樂仍在繼續播放。為達到這個目的,媒體播放器activity應該啟用一個運作于背景的服務。而系統将在這個activity不再顯示于螢幕之後,仍維持音樂播放服務的運作。

你可以連接配接至(綁定)一個正在運作的服務(如果服務沒有運作,則啟動之)。連接配接之後,你可以通過那個服務暴露出來的接口與服務進行通訊。對于音樂服務來說,這個接口可以允許使用者暫停、回退、停止以及重新開始播放。

如同activity和其它元件一樣,服務運作于應用程式程序的主線程内。是以它不會對其它元件或使用者界面有任何幹擾,它們一般會派生一個新線程來進行一些耗時任務(比如音樂回放)。參見下述程序和線程。

廣播接收器

廣播接收器是一個專注于接收廣播通知資訊,并做出對應處理的元件。很多廣播是源自于系統代碼的──比如,通知時區改變、電池電量低、拍攝了一張照片或者使用者改變了語言選項。應用程式也可以進行廣播──比如說,通知其它應用程式一些資料下載下傳完成并處于可用狀态。

應用程式可以擁有任意數量的廣播接收器以對所有它感興趣的通知資訊予以響應。所有的接收器均繼承自BroadcastReceiver基類。

廣播接收器沒有使用者界面。然而,它們可以啟動一個activity來響應它們收到的資訊,或者用NotificationManager來通知使用者。通知可以用很多種方式來吸引使用者的注意力──閃動背燈、震動、播放聲音等等。一般來說是在狀态欄上放一個持久的圖示,使用者可以打開它并擷取消息。

内容提供者

内容提供者将一些特定的應用程式資料供給其它應用程式使用。資料可以存儲于檔案系統、SQLite資料庫或其它方式。内容提供者繼承于ContentProvider基類,為其它應用程式取用和存儲它管理的資料實作了一套标準方法。然而,應用程式并不直接調用這些方法,而是使用一個ContentResolver對象,調用它的方法作為替代。ContentResolver可以與任意内容提供者進行會話,與其合作來對所有相關互動通訊進行管理。

參閱獨立的内容提供者章節獲得更多關于使用内容提供者的内容。

每當出現一個需要被特定元件處理的請求時,Android會確定那個元件的應用程式程序處于運作狀态,或在必要的時候啟動它。并確定那個相應元件的執行個體的存在,必要時會建立那個執行個體。

激活元件:intent

當接收到ContentResolver發出的請求後,内容提供者被激活。而其它三種元件──activity、服務和廣播接收器被一種叫做intent的異步消息所激活。intent是一個儲存着消息内容的Intent對象。對于activity和服務來說,它指明了請求的操作名稱以及作為操作對象的資料的URI和其它一些資訊。比如說,它可以承載對一個activity的請求,讓它為使用者顯示一張圖檔,或者讓使用者編輯一些文本。而對于廣播接收器而言,Intent對象指明了聲明的行為。比如,它可以對所有感興趣的對象聲明照相按鈕被按下。

對于每種元件來說,激活的方法是不同的:

* 通過傳遞一個Intent對象至Context.startActivity()或Activity.startActivityForResult()以載入(或指定新工作給)一個activity。相應的activity可以通過調用getIntent()方法來檢視激活它的intent。Android通過調用activity的onNewIntent()方法來傳遞給它繼發的intent。

一個activity經常啟動了下一個。如果它期望它所啟動的那個activity傳回一個結果,它會以調用startActivityForResult()來取代startActivity()。比如說,如果它啟動了另外一個activity以使使用者挑選一張照片,它也許想知道哪張照片被選中了。結果将會被封裝在一個Intent對象中,并傳遞給發出調用的activity的onActivityResult()方法。

* 通過傳遞一個Intent對象至Context.startService()将啟動一個服務(或給予正在運作的服務以一個新的指令)。Android調用服務的onStart()方法并将Intent對象傳遞給它。

與此類似,一個Intent可以被調用元件傳遞給Context.bindService()以擷取一個正在運作的目标服務的連接配接。這個服務會經由onBind()方法的調用擷取這個Intent對象(如果服務尚未啟動,bindService()會先啟動它)。比如說,一個activity可以連接配接至前述的音樂回放服務,并提供給使用者一個可操作的(使用者界面)以對回放進行控制。這個activity可以調用bindService()來建立連接配接,然後調用服務中定義的對象來影響回放。

後面一節:遠端方法調用将更詳細的闡明如何綁定至服務。

* 應用程式可以憑借将Intent對象傳遞給Context.sendBroadcast(),Context.sendOrderedBroadcast(),以及Context.sendStickyBroadcast()和其它類似方法來産生一個廣播。Android會調用所有對此廣播有興趣的廣播接收器的onReceive()方法,将intent傳遞給它們。

欲了解更多intent消息的資訊,請參閱獨立章節Intent和Intent濾過器。

關閉元件

内容提供者僅在響應ContentResolver提出請求的時候激活。而一個廣播接收器僅在響應廣播資訊的時候激活。是以,沒有必要去顯式的關閉這些元件。

而activity則不同,它提供了使用者界面,并與使用者進行會話。是以隻要會話依然持續,哪怕對話過程暫時停頓,它都會一直保持激活狀态。與此相似,服務也會在很長一段時間内保持運作。是以Android為關閉activity和服務提供了一系列的方法。

* 可以通過調用它的finish()方法來關閉一個activity。一個activity可以通過調用另外一個activity(它用startActivityForResult() 啟動的)的finishActivity()方法來關閉它。

* 服務可以通過調用它的stopSelf()方法來停止,或者調用Context.stopService()。

系統也會在元件不再被使用的時候或者Android需要為活動元件聲明更多記憶體的時候關閉它。後面的元件的生命周期一節,将對這種可能及附屬情況進行更詳細的讨論。

manifest檔案

當Android啟動一個應用程式元件之前,它必須知道那個元件是存在的。是以,應用程式會在一個manifest檔案中聲明它的元件,這個檔案會被打包到Android包中。這個.apk檔案還将涵括應用程式的代碼、檔案以及其它資源。

這個manifest檔案以XML作為結構格式,而且對于所有應用程式,都叫做AndroidManifest.xml。為聲明一個應用程式元件,它還會做很多額外工作,比如指明應用程式所需連結到的庫的名稱(除了預設的Android庫之外)以及聲明應用程式期望獲得的各種權限。

但manifest檔案的主要功能仍然是向Android聲明應用程式的元件。舉例說明,一個activity可以如下聲明:

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

<manifest . . . >

<application . . . >

<activity android:name="com.example.project.FreneticActivity"

android:icon="@drawable/small_pic.png"

android:label="@string/freneticLabel"

. . . >

</activity>

. . .

</application>

</manifest>

<activity>元素的name屬性指定了實作了這個activity的Activity的子類。icon和label屬性指向了包含展示給使用者的此activity的圖示和标簽的資源檔案。

其它元件也以類似的方法聲明──<service>元素用于聲明服務,<receiver>元素用于聲明廣播接收器,而<provider>元素用于聲明内容提供者。 manifest檔案中未進行聲明的activity、服務以及内容提供者将不為系統所見,進而也就不會被運作。然而,廣播接收器既可以在manifest檔案中聲明,也可以在代碼中進行動态的建立,并以調用Context.registerReceiver()的方式注冊至系統。

欲更多了解如何為你的應用程式建構manifest檔案,請參閱AndroidManifest.xml檔案一章。

Intent過濾器[yl1]

Intent對象可以被顯式的指定目标元件。如果進行了這種指定,Android會找到這個元件(依據manifest檔案中的聲明)并激活它。但如果Intent沒有進行顯式的指定,Android就必須為它找到對于intent來說最合适的元件。這個過程是通過比較Intent對象和所有可能對象的intent過濾器完成的。元件的intent過濾器會告知Android它所能處理的intent類型。如同其它相對于元件很重要的資訊一樣,這些是在manifest檔案中進行聲明的。這裡是上面執行個體的一個擴充,其中加入了針對activity的兩個intent過濾器聲明:

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

<manifest . . . >

<application . . . >

<activity android:name="com.example.project.FreneticActivity"

android:icon="@drawable/small_pic.png"

android:label="@string/freneticLabel"

. . . >

<intent-filter . . . >

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

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

</intent-filter>

<intent-filter . . . >

<action android:name="com.example.project.BOUNCE" />

<data android:type="image/jpeg" />

<category android:name="android.intent.category.DEFAULT" />

</intent-filter>

</activity>

. . .

</application>

</manifest>

示例中的第一個過濾器──action “android.intent.action.MAIN”和類别“android.intent.category.LAUNCHER”的組合──是通常具有的。它标明了這個activity将在應用程式加載器中顯示,就是使用者在裝置上看到的可供加載的應用程式清單。換句話說,這個activity是應用程式的入口,是使用者選擇運作這個應用程式後所見到的第一個activity。

第二個過濾器聲明了這個activity能被賦予一種特定類型的資料。

元件可以擁有任意數量的intent過濾器,每個都會聲明一系列不同的能力。如果它沒有包含任何過濾器,它将隻能被顯式聲明了目标元件名稱的intent激活。

對于在代碼中建立并注冊的廣播接收器來說,intent過濾器将被直接以IntentFilter對象執行個體化。其它過濾器則在manifest檔案中設定。

欲獲得更多intent過濾器的資訊,請參閱獨立章節:Intent和Intent過濾器。

Activity和任務

如前所述,一個activity可以啟動另外一個,甚至包括與它不處于同一應用程式之中的。舉個例子說,假設你想讓使用者看到某個地方的街道地圖。而已經存在一個具有此功能的activity了,那麼你的activity所需要做的工作就是把請求資訊放到一個Intent對象裡面,并把它傳遞給startActivity()。于是地圖浏覽器就會顯示那個地圖。而當使用者按下BACK鍵的時候,你的activity又會再一次的顯示在螢幕上。

對于使用者來說,這看起來就像是地圖浏覽器是你activity所在的應用程式中的一個組成部分,其實它是在另外一個應用程式中定義,并運作在那個應用程式的程序之中的。Android将這兩個activity放在同一個任務中來維持一個完整的使用者體驗。簡單的說,任務就是使用者所體驗到的“應用程式”。它是安排在一個堆棧中的一組相關的activity。堆棧中的根activity就是啟動了這整個任務的那個──一般情況下,它就是使用者在應用程式加載器中所選擇的。而堆棧最上方的activity則是目前運作的──使用者直接對其進行操作的。當一個activity啟動另外一個的時候,新的activity就被壓入堆棧,并成為目前運作的activity。而前一個activity仍保持在堆棧之中。當使用者按下BACK鍵的時候,目前activity出棧,而前一個恢複為目前運作的activity。

堆棧中儲存的其實是對象,是以如果發生了諸如需要多個地圖浏覽器的情況,就會使得一個任務中出現多個同一Activity子類的執行個體同時存在,堆棧會為每個執行個體單獨開辟一個入口。堆棧中的Activity永遠不會重排,隻會壓入或彈出。

任務其實就是activity的堆棧,而不是manifest檔案中的一個類或者元素。是以你無法撇開activity而為一個任務設定一個值。而事實上整個任務使用的值是在根activity中設定的。比如說,下一節我們會談及“任務的affinity”,從affinity中讀出的值将會設定到任務的根activity之中。

任務中的所有activity是作為一個整體進行移動的。整個的任務(即activity堆棧)可以移到前台,或退至背景。舉個例子說,比如目前任務在堆棧中存有四個activity──三個在目前activity之下。當使用者按下HOME鍵的時候,回到了應用程式加載器,然後選擇了一個新的應用程式(也就是一個新任務)。則目前任務遁入背景,而新任務的根activity顯示出來。然後,過了一小會兒,使用者再次回到了應用程式加載器而又選擇了前一個應用程式(上一個任務)。于是那個任務,帶着它堆棧中所有的四個activity,再一次的到了前台。當使用者按下BACK鍵的時候,螢幕不會顯示出使用者剛才離開的activity(上一個任務的根activity)。取而代之,目前任務的堆棧中最上面的activity被彈出,而同一任務中的上一個activity顯示了出來。

上述的種種即是activity和任務的預設行為模式。但是有一些方法可以改變所有這一切。activity和任務的聯系、任務中activity的行為方式都被啟動那個activity的Intent對象中設定的一系列标記和manifest檔案中那個activity中的<activity>元素的系列屬性之間的互動所控制。無論是請求發出者和回應者在這裡都擁有話語權。

我們剛才所說的這些關鍵Intent标記如下:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

FLAG_ACTIVITY_SINGLE_TOP

而關鍵的<activity>屬性是:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

接下來的一節會描述這些标記以及屬性的作用,它們是如何互相影響的,以及控制它們的使用時必須考慮到的因素。

Affinity(吸引力)和新任務

預設情況下,一個應用程式中的activity互相之間會有一種Affinity──也就是說,它們首選都歸屬于一個任務。然而,可以在<activity>元素中把每個activity的taskAffinity屬性設定為一個獨立的affinity。于是在不同的應用程式中定義的activity可以享有同一個affinity,或者在同一個應用程式中定義的activity有着不同的affinity。affinity在兩種情況下生效:當加載activity的Intent對象包含了FLAG_ACTIVITY_NEW_TASK标記,或者當activity的allowTaskReparenting屬性設定為“true”。

FLAG_ACTIVITY_NEW_TASK标記

如前所述,在預設情況下,一個新activity被另外一個調用了startActivity()方法的activity載入了任務之中。并壓入了調用者所在的堆棧。然而,如果傳遞給startActivity()的Intent對象包含了FLAG_ACTIVITY_NEW_TASK标記,系統會為新activity安排另外一個任務。一般情況下,如同标記所暗示的那樣,這會是一個新任務。然而,這并不是必然的。如果已經存在了一個與新activity有着同樣affinity的任務,則activity會載入那個任務之中。如果沒有,則啟用新任務。

allowTaskReparenting屬性

如果一個activity将allowTaskReparenting屬性設定為“true”。它就可以從初始的任務中轉移到與其擁有同一個affinity并轉向前台的任務之中。比如說,一個旅行應用程式中包含的預報所選城市的天氣情況的activity。它與這個應用程式中其它的activity擁有同樣的affinity(預設的affinity)而且允許重定父級。你的另一個activity啟動了天氣預報,于是它就會與這個activity共處與同一任務之中。然而,當那個旅行應用程式再次回到前台的時候,這個天氣預報activity就會被再次安排到原先的任務之中并顯示出來。

如果在使用者的角度看來,一個.apk檔案中包含了多于一個的“應用程式”,你可能會想要為它們所轄的activity安排不一樣的affinity。

加載模式

<activity>元素的launchMode屬性可以設定四種不同的加載模式:

"standard" (預設值)

"singleTop"

"singleTask"

"singleInstance"

這些模式之間的差異主要展現在四個方面:

* 哪個任務會把持對intent做出響應的activity。對“standard”和“singleTop”模式而言,是産生intent(并調用startActivity())的任務──除非Intent對象包含FLAG_ACTIVITY_NEW_TASK标記。而在這種情況下,如同上面Affinitie和新任務一節所述,會是另外一個任務。

相反,對“singleTask”和“singleInstance”模式而言,activity總是位于任務的根部。正是它們定義了一個任務,是以它們絕不會被載入到其它任務之中。

* activity是否可以存在多個執行個體。一個“standard”或“singleTop”的activity可以被多次初始化。它們可以歸屬于多個任務,而一個任務也可以擁有同一activity的多個執行個體。

相反,對“singleTask”和“singleInstance”的activity被限定于隻能有一個執行個體。因為這些activity都是任務的起源,這種限制意味着在一個裝置中同一時間隻允許存在一個任務的執行個體。

* 在執行個體所在的任務中是否會有别的activity。一個“singleInstance”模式的activity将會是它所在的任務中唯一的activity。如果它啟動了别的activity,那個activity将會依據它自己的加載模式加載到其它的任務中去──如同在intent中設定了FLAG_ACTIVITY_NEW_TASK标記一樣的效果。在其它方面,“singleInstance”模式的效果與“singleTask”是一樣的。

剩下的三種模式允許一個任務中出現多個activity。“singleTask”模式的activity将是任務的根activity,但它可以啟動别的activity并将它們置入所在的任務中。“standard”和“singleTop”activity則可以在堆棧的任意位置出現。

* 是否要載入新的類執行個體以處理新的intent。對預設的"standard"模式來說,對于每個新intent都會建立一個新的執行個體以進行響應,每個執行個體僅處理一個intent。“singleTop”模式下,如果activity位于目的任務堆棧的最上面,則重用目前現存的activity來處理新的intent。如果它不是在堆棧頂部,則不會發生重用。而是建立一個新執行個體來處理新的intent并将其推入堆棧。

舉例來說,假設一個任務的堆棧由根activityA和activity B、C和位于堆棧頂部的D組成,即堆棧A-B-C-D。一個針對D類型的activity的intent抵達的時候,如果D是預設的“standard”加載模式,則建立并加載一個新的類執行個體,于是堆棧變為A-B-C-D-D。然而,如果D的載入模式為“singleTop”,則現有的執行個體會對新intent進行處理(因為它位于堆棧頂部)而堆棧保持A-B-C-D的形态。

換言之,如果新抵達的intent是針對B類型的activity,則無論B的模式是“standard”還是“singleTop” ,都會加載一個新的B的執行個體(因為B不位于堆棧的頂部),而堆棧的順序變為A-B-C-D-B。

如前所述,“singleTask”或“singleInstance”模式的activity永遠不會存在多于一個執行個體。是以執行個體将處理所有新的intent。一個“singleInstance”模式的activity永遠保持在堆棧的頂部(因為它是那個堆棧中唯一的一個activity),是以它一直堅守在處理intent的崗位上。然而,對一個“singleTask”模式的activity來說,它上面可能有,也可能沒有别的activity和它處于同一堆棧。在有的情況下,它就不在能夠處理intent的位置上,則那個intent将被舍棄。(即便在intent被舍棄的情況下,它的抵達仍将使這個任務切換至前台,并一直保留)

當一個現存的activity被要求處理一個新的intent的時候,會調用onNewIntent()方法來将intent對象傳遞至activity。(啟動activity的原始intent對象可以通過調用getIntent()方法獲得。)

請注意,當一個新的activity執行個體被建立以處理新的intent的時候,使用者總可以按下BACK鍵來回到前面的狀态(回到前一個activity)。但當使用現存的activity來處理新intent的時候,使用者是不能靠按下BACK鍵回到當這個新intent抵達之前的狀态的。

想獲得更多關于加載模式的内容,請參閱<activity>元素的描述。

清理堆棧

如果使用者離開一個任務很長一段時間,系統會清理該任務中除了根activity之外的所有activity。當使用者再次回到這個任務的時候,除了隻剩下初始化activity尚存之外,其餘都跟使用者上次離開它的時候一樣。這樣做的原因是:在一段時間之後,使用者再次回到一個任務的時候,他們更期望放棄他們之前的所作所為,做些新的事情。

這些屬于預設行為,另外,也存在一些activity的屬性用以控制并改變這些行為:

alwaysRetainTaskState屬性

如果一個任務的根activity中此屬性設定為“true”,則上述預設行為不會發生。任務将在很長的一段時間内保留它堆棧内的所有activity。

clearTaskOnLaunch屬性

如果一個任務的根activity中此屬性設定為“true”,則每當使用者離開這個任務和傳回它的時候,堆棧都會被清空至隻留下rootactivity。換句話說,這是alwaysRetainTaskState的另一個極端。哪怕僅是過了一小會兒,使用者回到任務時,也是見到它的初始狀态。

finishOnTaskLaunch屬性

這個屬性與clearTaskOnLaunch屬性相似,但它僅作用于單個的activity,而不是整個的task。而且它可以使任意activity都被清理,甚至根activity也不例外。當它設定為“true”的時候,此activity僅做為任務的一部分存在于目前回話中,一旦使用者離開并再次回到這個任務,此activity将不複存在。

此外,還有别的方式從堆棧中移除一個activity。如果一個intent對象包含FLAG_ACTIVITY_CLEAR_TOP标記,而且目标任務的堆棧中已經存在了一個能夠響應此intent的activity類型的執行個體。則這個執行個體之上的所有activity都将被清理以使它位于堆棧的頂部來對intent做出響應。如果此時指定的activity的加載模式為“standard”,則它本身也會從堆棧中移除,并加載一個新的執行個體來處理到來的intent。這是因為加載模式為“standard”的activity總會建立一個新執行個體來處理新的intent。

FLAG_ACTIVITY_CLEAR_TOP與FLAG_ACTIVITY_NEW_TASK經常合并使用。這時,這些标記提供了一種定位其它任務中現存的activity并将它們置于可以對intent做出響應的位置的方法。

啟動任務

當一個activity被指定一個“android.intent.action.MAIN”做為動作,以及“android.intent.category.LAUNCHER”做為類别的intent過濾器之後(在前述intent過濾器一節中已經有了這個示例),它就被設定為一個任務的入口點。這樣的過濾器設定會在應用程式加載器中為此activity顯示一個圖示和标簽,以供使用者加載任務或加載之後在任意時間回到這個任務。

第二個能力相當重要:使用者必須可以離開一個任務,并在一段時間後傳回它。出于這個考慮,加載模式被設定為“singleTask”和“singleInstance”的activity總是會初始化一個新任務,這樣的activity僅能用于指定了一個MAIN和LAUNCHER過濾器的情況之下。我們來舉例說明如果沒指定過濾器的情況下會發生的事情:一個intent加載了一個“singleTask”的activity,初始化了一個新任務,使用者在這個任務中花費了一些時間來完成工作。然後使用者按下了HOME鍵。于是任務被要求轉至背景并被主螢幕所掩蓋。因為它并沒有在應用程式加載器中顯示圖示,這将導緻使用者無法再傳回它。

類似的困境也可由FLAG_ACTIVITY_NEW_TASK标記引起。如果此标記使一個activity啟動了一個新任務繼而使用者按下了HOME鍵離開了它,則使用者必須要有一些方法再次回到這個任務。一些實體(諸如通知管理器)總是在另外的任務中啟動新activity,而不是做為它們自己的一部分,是以它們總是将FLAG_ACTIVITY_NEW_TASK标記包含在intent裡面并傳遞給startActivity()。如果你寫了一個能被外部實體使用這個标記調用的activity,你必須注意要給使用者留一個傳回這個被外部實體啟動的任務的方法。

當你不想讓使用者再次傳回一個activity的情況下,可以将<activity>元素的finishOnTaskLaunch設定為“true”。參見前述清理堆棧。.

程序和線程

當一個應用程式開始運作它的第一個元件時,Android會為它啟動一個Linux程序,并在其中執行一個單一的線程。預設情況下,應用程式所有的元件均在這個程序的這個線程中運作。

然而,你也可以安排元件在其他程序中運作,而且可以為任意程序衍生出其它線程。

程序

元件運作所在的程序由manifest檔案所控制。元件元素——<activity>,<service>,<receiver>和<provider>——都有一個process屬性來指定元件應當運作于哪個程序之内。這些屬性可以設定為使每個元件運作于它自己的程序之内,或一些元件共享一個程序而其餘的元件不這麼做。它們也可以設定為令不同應用程式的元件在一個程序中運作——使應用程式的組成部分共享同一個Linux使用者ID并賦以同樣的權限。<application>元素也有一個process屬性,以設定所有元件的預設值。

所有的元件執行個體都位于特定程序的主線程内,而對這些元件的系統調用也将由那個線程進行分發。一般不會為每個執行個體建立線程。是以,某些方法總是運作在程序的主線程内,這些方法包括諸如View.onKeyDown()這樣報告使用者動作以及後面元件生命周期一節所要讨論的生命周期通告的。這意味着元件在被系統調用的時候,不應該施行長時間的抑或阻塞的操作(諸如網絡相關操作或是循環計算),因為這将阻塞同樣位于這個程序的其它元件的運作。你應該如同下面線程一節所叙述的那樣,為這些長時間操作衍生出一個單獨的線程進行處理。

在可用記憶體不足而又有一個正在為使用者進行服務的程序需要更多記憶體的時候,Android有時候可能會關閉一個程序。而在這個程序中運作着的應用程式也是以被銷毀。當再次出現需要它們進行處理的工作的時候,會為這些元件重新建立程序。

在決定結束哪個程序的時候,Android會衡量它們對于使用者的相對重要性。比如說,相對于一個仍有使用者可見的activity的程序,它更有可能去關閉一個其activity已經不為使用者所見的程序。也可以說,決定是否關閉一個程序主要依據在那個程序中運作的元件的狀态。這些狀态将在後續的一節元件生命周期中予以說明。

線程

盡管你可以把你的應用程式限制于一個單獨的程序中,有時,你仍然需要衍生出一個線程以處理背景任務。因為使用者界面必須非常及時的對使用者操作做出響應,是以,控管activity的線程不應用于處理一些諸如網絡下載下傳之類的耗時操作。所有不能在瞬間完成的任務都應安排到不同的線程中去。

線程在代碼中是以标準Java Thread對象建立的。Android提供了很多便于管理線程的類:Looper用于在一個線程中運作一個消息循環,Handler用于處理消息,HandlerThread用于使用一個消息循環啟用一個線程。

遠端過程調用

Android有一個輕量級的遠端過程調用(RPC)機制:即在本地調用一個方法,但在遠端(其它的程序中)進行處理,然後将結果傳回調用者。這将方法調用及其附屬的資料以系統可以了解的方式進行分離,并将其從本地程序和本地位址空間傳送至遠端過程和遠端位址空間,并在那裡重新裝配并對調用做出反應。傳回的結果将以相反的方向進行傳遞。Android提供了完成這些工作所需的所有的代碼,以使你可以集中精力來實作RPC接口本身。

RPC接口可以隻包括方法。即便沒有傳回值,所有方法仍以同步的方式執行(本地方法阻塞直至遠端方法結束)。

簡單的說,這套機制是這樣工作的:一開始,你用簡單的IDL(界面描繪語言)聲明一個你想要實作的RPC接口。然後用aidl工具為這個聲明生成一個Java接口定義,這個定義必須對本地和遠端程序都可見。它包含兩個内部類,如下圖所示:

内部類中有管理實作了你用IDL聲明的接口的遠端方法調用所需要的所有代碼。兩個内部類均實作了IBinder接口。一個用于系統在本地内部使用,你些的代碼可以忽略它;另外一個,我們稱為Stub,擴充了Binder類。除了實作了IPC調用的内部代碼之外,它還包括了你聲明的RPC接口中的方法的聲明。你應該如上圖所示的那樣寫一個Stub的子類來實作這些方法。

一般情況下,遠端過程是被一個服務所管理的(因為服務可以通知系統關于程序以及它連接配接到别的程序的資訊)。它包含着aidl工具産生的接口檔案和實作了RPC方法的Stub的子類。而用戶端隻需要包括aidl工具産生的接口檔案。

下面将說明服務與其用戶端之間的連接配接是如何建立的:

* 服務的用戶端(位于本地)應該實作onServiceConnected()和onServiceDisconnected()方法。這樣,當至遠端服務的連接配接成功建立或者斷開的時候,它們會收到通知。這樣它們就可以調用bindService()來設定連接配接。

* 而服務則應該實作onBind()方法以接受或拒絕連接配接。這取決于它收到的intent(intent将傳遞給bindService())。如果接受了連接配接,它會傳回一個Stub的子類的執行個體。

* 如果服務接受了連接配接,Android将會調用用戶端的onServiceConnected()方法,并傳遞給它一個IBinder對象,它是由服務所管理的Stub的子類的代理。通過這個代理,用戶端可以對遠端服務進行調用。

線程安全方法

在一些情況下,你所實作的方法有可能會被多于一個的線程所調用,是以它們必須被寫成線程安全的。

對于我們上一節所讨論的RPC機制中的可以被遠端調用的方法來說,這是必須首先考慮的。如果針對一個IBinder對象中實作的方法的調用源自這個IBinder對象所在的程序時,這個方法将會在調用者的線程中執行。然而,如果這個調用源自其它的程序,則這個方法将會在一個線程池中選出的線程中運作,這個線程池由Android加以管理,并與IBinder存在于同一程序内;這個方法不會在程序的主線程内執行。反過來說,一個服務的onBind()方法應為服務程序的主線程所調用,而實作了由onBind()傳回的對象(比如說,一個實作了RPC方法的Stub的子類)的方法将為池中的線程所調用。因為服務可以擁有多于一個的用戶端,而同一時間,也會有多個池中的線程調用同一個IBinder方法。是以IBinder方法必須實作為線程安全的。

類似的,一個内容提供者能接受源自其它程序的請求資料。盡管ContentResolver和ContentProvider類隐藏了互動溝通過程的管理細節,ContentProvider會由query(),insert(),delete(),update()和getType()方法來相應這些請求,而這些方法也都是由那個内容提供者的程序中所包涵的線程池提供的,而不是程序的主線程本身。是以這些有可能在同一時間被很多線程調用的方法也必須被實作為線程安全的。

元件生命周期

應用程式元件有其生命周期──由Android初始化它們以相應intent直到這個執行個體被摧毀。在此之間,它們有時是激活的有時則相反。或者,如果它是一個activity,則是可為使用者所見或者不能。這一節讨論了activity、服務以及廣播接收器的生命周期,包括它們在生命周期中的狀态、在狀态之間轉變時通知你的方法、以及當這些程序被關閉或執行個體被摧毀時,這些狀态産生的效果。

Activity生命周期

一個activity主要有三個狀态:

* 當在螢幕前台時(位于目前任務堆棧的頂部),它是活躍或運作的狀态。它就是相應使用者操作的activity。

* 當它失去焦點但仍然對使用者可見時,它處于暫停狀态。即是:在它之上有另外一個activity。這個activity也許是透明的,或者未能完全遮蔽全屏,是以被暫停的activity仍對使用者可見。暫停的activity仍然是存活狀态(它保留着所有的狀态和成員資訊并連接配接至視窗管理器),但當系統處于極低記憶體的情況下,仍然可以殺死這個activity。

* 如果它完全被另一個activity覆寫是,它處于停止狀态。它仍然保留所有的狀态和成員資訊。然而它不在為使用者可見,是以它的視窗将被隐藏,如果其它地方需要記憶體,則系統經常會殺死這個activity。

如果一個activity處于暫停或停止狀态,系統可以通過要求它結束(調用它的finish()方法)或直接殺死它的程序來将它驅出記憶體。當它再次為使用者可見的時候,它隻能完全重新啟動并恢複至以前的狀态。

當一個activity從這個狀态轉變到另一個狀态時,它被以下列protected方法所通知:

void onCreate(Bundle savedInstanceState)

void onStart()

void onRestart()

void onResume()

void onPause()

void onStop()

void onDestroy()

你可以重載所有這些方法以在狀态改變時進行合适的工作。所有的activity都必須實作onCreate()用以當對象第一次執行個體化時進行初始化設定。很多activity會實作onPause()以送出資料變化或準備停止與使用者的互動。

調用父類

所有activity生命周期方法的實作都必須先調用其父類的版本。比如說:

protected void onPause() {

super.onPause();

. . .

}

總得來說,這七個方法定義了一個activity完整的生命周期。實作這些方法可以幫助你監察三個嵌套的生命周期循環:

* 一個activity 完整的生命周期自第一次調用onCreate()開始,直至調用onDestroy()為止。activity在onCreate()中設定所有“全局”狀态以完成初始化,而在onDestroy()中釋放所有系統資源。比如說,如果activity有一個線程在背景運作以從網絡上下載下傳資料,它會以onCreate()建立那個線程,而以onDestroy()銷毀那個線程。

* 一個activity的可視生命周期自onStart()調用開始直到相應的onStop()調用。在此期間,使用者可以在螢幕上看到此activity,盡管它也許并不是位于前台或者正在與使用者做互動。在這兩個方法中,你可以管控用來向使用者顯示這個activity的資源。比如說,你可以在onStart()中注冊一個BroadcastReceiver來監控會影響到你UI的改變,而在onStop()中來取消注冊,這時使用者是無法看到你的程式顯示的内容的。onStart()和onStop()方法可以随着應用程式是否為使用者可見而被多次調用。

* 一個activity的前台生命周期自onResume()調用起,至相應的onPause()調用為止。在此期間,activity位于前台最上面并與使用者進行互動。activity會經常在暫停和恢複之間進行狀态轉換──比如說當裝置轉入休眠狀态或有新的activity啟動時,将調用onPause()方法。當activity獲得結果或者接收到新的intent的時候會調用onResume()方法。是以,在這兩個方法中的代碼應當是輕量級的。

下圖展示了上述循環過程以及activity在這個過程之中曆經的狀态改變。着色的橢圓是activity可以經曆的主要狀态。矩形框代表了當activity在狀态間發生改變的時候,你進行操作所要實作的回調方法。

下表較長的描述了這些方法,并在activity的整個生命周期中定位了它們。

方法

描述

可被殺死

下一個

onCreate()

在activity第一次被建立的時候調用。這裡是你做所有初始化設定的地方──建立視圖、綁定資料至清單等。如果曾經有狀态記錄(參閱後述Saving Activity State。),則調用此方法時會傳入一個包含着此activity以前狀态的包對象做為參數。

總繼之以onStart()。

onStart()

onRestart()

在activity停止後,在再次啟動之前被調用。

總繼之以onStart()。

onStart()

onStart()

當activity正要變得為使用者所見時被調用。

當activity轉向前台時繼以onResume(),在activity變為隐藏時繼以onStop()。

onResume()

or

onStop()

onResume()

在activity開始與使用者進行互動之前被調用。此時activity位于堆棧頂部,并接受使用者輸入。

繼之以onPause()。

onPause()

onPause()

當系統将要啟動另一個activity時調用。此方法主要用來将未儲存的變化進行持久化,停止類似動畫這樣耗費CPU的動作等。這一切動作應該在短時間内完成,因為下一個activity必須等到此方法傳回後才會繼續。

當activity重新回到前台是繼以onResume()。當activity變為使用者不可見時繼以onStop()。

onResume()

or

onStop()

onStop()

當activity不再為使用者可見時調用此方法。這可能發生在它被銷毀或者另一個activity(可能是現存的或者是新的)回到運作狀态并覆寫了它。

如果activity再次回到前台跟使用者互動則繼以onRestart(),如果關閉activity則繼以onDestroy()。

onRestart()

or

onDestroy()

onDestroy()

在activity銷毀前調用。這是activity接收的最後一個調用。這可能發生在activity結束(調用了它的finish()方法)或者因為系統需要空間是以臨時的銷毀了此acitivity的執行個體時。你可以用isFinishing()方法來區分這兩種情況。

nothing

請注意上表中可被殺死一列。它标示了在方法傳回後,還沒執行activity的其餘代碼的任意時間裡,系統是否可以殺死包含此activity的程序。三個方法(onPause()、onStop()和onDestroy())被标記為“是”。onPause()是三個中的第一個,它也是唯一一個在程序被殺死之前必然會調用的方法──onStop()和onDestroy()有可能不被執行。是以你應該用onPause()來将所有持久性資料(比如使用者的編輯結果)寫入存儲之中。

在可被殺死一列中标記為“否”的方法在它們被調用時将保護activity所在的程序不會被殺死。是以隻有在onPause()方法傳回後到onResume()方法被調用時,一個activity才處于可被殺死的狀态。在onPause()再次被調用并傳回之前,它不會被系統殺死。

如後面一節程序和生命周期所述,即使是在這裡技術上沒有被定義為“可殺死”的activity仍然有可能被系統殺死──但這僅會發生在實在沒有其它方法的極端情況之下。

儲存activity狀态

當系統而不是使用者自己出于回收記憶體的考慮,關閉了一個activity之後。使用者會期望當他再次回到那個activity的時候,它仍保持着上次離開時的樣子。

為了擷取activity被殺死前的狀态,你應該為activity實作onSaveInstanceState()方法。Android在activity有可能被銷毀之前(即onPause()調用之前)會調用此方法。它會将一個以名稱-值對方式記錄了activity動态狀态的Bundle對象傳遞給該方法。當activity再次啟動時,這個Bundle會傳遞給onCreate()方法和随着onStart()方法調用的onRestoreInstanceState(),是以它們兩個都可以恢複捕獲的狀态。

與onPause()或先前讨論的其它方法不同,onSaveInstanceState()和onRestoreInstanceState()并不是生命周期方法。它們并不是總會被調用。比如說,Android會在activity易于被系統銷毀之前調用onSaveInstanceState(),但使用者動作(比如按下了BACK鍵)造成的銷毀則不調用。在這種情況下,使用者沒打算再次回到這個activity,是以沒有儲存狀态的必要。

因為onSaveInstanceState()不是總被調用,是以你應該隻用它來為activity儲存一些臨時的狀态,而不能用來儲存持久性資料。而是應該用onPause()來達到這個目的。

協調activity

當一個activity啟動了另外一個的時候,它們都會經曆生命周期變化。一個會暫停乃至停止,而另一個則啟動。這種情況下,你可能需要協調好這些activity:

生命周期回調順序是已經定義好的,尤其是在兩個activity在同一個程序内的情況下:

1. 調用目前activity的onPause()方法。

2. 接着,順序調用新啟動activity的onCreate()、onStart()和onResume()方法。

3. 然後,如果啟動的activity不再于螢幕上可見,則調用它的onStop()方法。

服務生命周期

服務以兩種方式使用:

* 它可以啟動并運作,直至有人停止了它或它自己停止。在這種方式下,它以調用Context.startService()啟動,而以調用Context.stopService()結束。它可以調用Service.stopSelf()或Service.stopSelfResult()來自己停止。不論調用了多少次startService()方法,你隻需要調用一次stopService()來停止服務。

* 它可以通過自己定義并暴露出來的接口進行程式操作。用戶端建立一個到服務對象的連接配接,并通過那個連接配接來調用服務。連接配接以調用Context.bindService()方法建立,以調用Context.unbindService()關閉。多個用戶端可以綁定至同一個服務。如果服務此時還沒有加載,bindService()會先加載它。

這兩種模式并不是完全分離的。你可以綁定至一個用startService()啟動的服務。比如說,一個背景音樂播放服務可以調用startService()并傳遞給它一個包含欲播放的音樂清單的Intent對象來啟動。不久,當使用者想要對播放器進行控制或者檢視目前播放曲目的詳情時,會啟用一個activity,調用bindService()連接配接到服務來完成操作。在這種情況下,直到綁定連接配接關閉stopService()才會真正停止一個服務。

與activity一樣,服務也有一系列你可以實作以用于監控其狀态變化的生命周期方法。但相對于activity要少一些,隻有三個,而且,它們是public屬性,并非protected:

void onCreate()

void onStart(Intent intent)

void onDestroy()

倚仗實作這些方法,你監控服務的兩個嵌套的生命周期循環:

* 服務的完整生命周期始于調用onCreate()而終于onDestroy()方法傳回。如同activity一樣,服務在onCreate()裡面進行它自己的初始化,而在onDestroy()裡面釋放所有資源。比如說,一個音樂回放服務可以在onCreate()中建立播放音樂的線程,而在onDestroy()中停止這個線程。

* 服務的活躍生命周期始于調用onStart()。這個方法用于處理傳遞給startService()的Intent對象。音樂服務會打開Intent來探明将要播放哪首音樂,并開始播放。

服務停止時沒有相應的回調方法──不存在onStop()方法。

onCreate()和onDestroy()方法在所有服務中都會被調用,不論它們是由Context.startService()還是由Context.bindService()所啟動的。而onStart()僅會被startService()所啟用的服務調用。

如果一個服務允許别的程序綁定,則它還會有以下額外的回調方法以供實作:

IBinder onBind(Intent intent)

boolean onUnbind(Intent intent)

void onRebind(Intent intent)

傳遞給bindService的Intent的對象也會傳遞給onBind()回調方法,而傳遞給unbindService()的Intent對象同樣傳遞給onUnbind()。如果服務允許綁定,onBind()将傳回一個供用戶端與服務進行互動的通訊管道。如果有新的用戶端連接配接至服務,則onUnbind()方法可以要求調用onRebind()。

下圖描繪了服務的回調方法。盡管圖中對由startService和startService方法啟動的服務做了區分,但要記住,不論一個服務是怎麼啟動的,它都可能允許用戶端的連接配接,是以任何服務都可以接受onBind()和onUnbind()調用。

cuihai 2011-03-03 12:13

廣播接收器生命周期

廣播接收器隻有一個回調方法:

void onReceive(Context curContext, Intent broadcastMsg)

當廣播消息抵達接收器時,Android調用它的onReceive()方法并将包含消息的Intent對象傳遞給它。廣播接收器僅在它執行這個方法時處于活躍狀态。當onReceive()傳回後,它即為失活狀态。

擁有一個活躍狀态的廣播接收器的程序被保護起來而不會被殺死。但僅擁有失活狀态元件的程序則會在其它程序需要它所占有的記憶體的時候随時被殺掉。

這種方式引出了一個問題:如果響應一個廣播資訊需要很長的一段時間,我們一般會将其納入一個衍生的線程中去完成,而不是在主線程内完成它,進而保證使用者互動過程的流暢。如果onReceive()衍生了一個線程并且傳回,則包涵新線程在内的整個程序都被會判為失活狀态(除非程序内的其它應用程式元件仍處于活躍狀态),于是它就有可能被殺掉。這個問題的解決方法是令onReceive()啟動一個新服務,并用其完成任務,于是系統就會知道程序中仍然在處理着工作。

下一節中,我們會讨論更多程序易誤殺的問題。

程序與生命周期

Android系統會盡可能長的延續一個應用程式程序,但在記憶體過低的時候,仍然會不可避免需要移除舊的程序。為決定保留或移除一個程序,Android将每個程序都放入一個“重要性層次”中,依據則是它其中運作着的元件及其狀态。重要性最低的程序首先被消滅,然後是較低的,依此類推。重要性共分五層,依據重要性清單如下:

1.      前台程序是使用者操作所必須的。當滿足如下任一條件時,程序被認為是處于前台的:

o        它運作着正在與使用者互動的activity(Activity對象的onResume()方法已被調用)。

o        一個正在與使用者互動的activity使用着它提供的一個服務。

o        它包含着一個正在執行生命周期回調方法(onCreate()、onStart()或onDestroy())的Service對象。

o        它包含着一個正在執行onReceive()方法的BroadcastReceiver對象。

任一時間下,僅有少數程序會處于前台,僅當記憶體實在無法供給它們維持同時運作時才會被殺死。一般來說,在這種情況下,裝置已然處于使用虛拟記憶體的狀态,必須要殺死一些前台程序以使用者界面保持響應。

2.      可視程序沒有前台元件,但仍可被使用者在螢幕上所見。當滿足如下任一條件時,程序被認為是可視的:、

o        它包含着一個不在前台,但仍然為使用者可見的activity(它的onPause()方法被調用)。這種情況可能出現在以下情況:比如說,前台activity是一個對話框,而之前的activity位于其下并可以看到。

o        它包含了一個綁定至一個可視的activity的服務。

可視程序依然被視為是很重要的,非到不殺死它們便無法維持前台程序運作時,才會被殺死。

3.      服務程序是由startService()方法啟動的服務,它不會變成上述兩類。盡管服務程序不會直接為使用者所見,但它們一般都在做着使用者所關心的事情(比如在背景播放mp3或者從網上下載下傳東西)。是以系統會盡量維持它們的運作,除非系統記憶體不足以維持前台程序和可視程序的運作需要。

4.      背景程序包含目前不為使用者所見的activity(Activity對象的onStop()方法已被調用)。這些程序與使用者體驗沒有直接的聯系,可以在任意時間被殺死以回收記憶體供前台程序、可視程序以及服務程序使用。一般來說,會有很多背景程序運作,是以它們一般存放于一個LRU(最後使用)清單中以確定最後被使用者使用的activity最後被殺死。如果一個activity正确的實作了生命周期方法,并捕獲了正确的狀态,則殺死它的程序對使用者體驗不會有任何不良影響。

5.      空程序不包含任何活動應用程式元件。這種程序存在的唯一原因是做為緩存以改善元件再次于其中運作時的啟動時間。系統經常會殺死這種程序以保持程序緩存和系統核心緩存之間的平衡。

Android會依據程序中目前活躍元件的重要程度來盡可能高的估量一個程序的級别。比如說,如果一個程序中同時有一個服務和一個可視的activity,則程序會被判定為可視程序,而不是服務程序。

此外,一個程序的級别可能會由于其它程序依賴于它而升高。一個為其它程序提供服務的程序級别永遠高于使用它服務的程序。比如說,如果A程序中的内容提供者為程序B中的用戶端提供服務,或程序A中的服務為程序B中的元件所綁定,則A程序最低也會被視為與程序B擁有同樣的重要性。

因為運作着一個服務的程序重要級别總高于一個背景activity。是以一個activity以啟動一個服務的方式啟動一個長時間運作過程比簡單的衍生一個線程來進行處理要好。尤其是當處理過程比activity本身存在時間要長的情況之下。我們以背景音樂播放和上傳一個相機拍攝的圖檔至網站上為例。使用服務則不論activity發生何事,都至少可以保證操作擁有“服務程序”的權限。如上一節廣播接收器生命周期所提到的,這也正是廣播接收器使用服務,而不是使用線程來處理耗時任務的原因。

使用者界面User Interface

在一個Android應用中,使用者界面是由View和ViewGroup對象建構的。View與ViewGroup都有很多種類,而它們都是View類的子類。

View對象是Android平台中使用者界面展現的基礎機關。View類是它稱為“widgets(工具)”的子類的基礎,它們提供了諸如文本輸入框和按鈕之類的UI對象的完整實作。ViewGroup類同樣為其被稱為“Layouts(布局)”的子類奠定了基礎,它們提供了象流式布局、表格布局以及相對布局之類的布局架構。

View對象是一個資料體,它的屬性存儲了用于螢幕上一塊矩形區域的布局參數及内容。并負責這塊它所轄的這個矩形區域之中所有測量、布局、焦點轉換、卷動以及按鍵/觸摸手勢的處理。作為一個使用者界面對象,View同時也擔任着使用者互動關鍵點以及互動事件接受者的角色。

視圖層次View Hierarchy

在Android平台上,你可以用下圖所示的View和ViewGroup層次圖來定義一個Activity的UI。這個層次樹可随你所願的簡單或者複雜化,你能使用Android預定義的一套工具和布局來建立它,或者使用你自己定義的Views來建立。

為了把一個視圖層次樹展現到螢幕上,你的Activity必須調用setContentView()方法,并傳給它一個根節點對象的引用。Android系統将接受此引用,并用來進行界面的廢止、測量并繪制這棵樹。層次的根結點會要求它的子節點進行自我繪制──進而,每個視圖組節點也負責調用它的子視圖進行自我繪制。子節點将向父節點申請繪制的位置以及大小,而其父類享有子節點繪制的位置及大小的最終決定權。Android依次(自層次樹頂層開始)解析你布局中的元素,執行個體化View并将它們添加到它們的父節點中。因為這個過程是依次進行的,是以如果出現了元素重疊的情況,最後一個繪制的元素将位于所有重疊元素之上顯現。

如欲獲得更多關于視圖層次如何測算以及繪制細節的讨論,情參閱Android如何繪制視圖。

布局Layout

定義并展現你的視圖層次的最常用的方法是使用XML布局檔案。如同HTML一樣,XML為布局提供了一種可讀的結構。XML中的每個元素都是View或ViewGroup對象(抑或它們的子類)。View對象是樹的葉節點,而ViewGroup對象是樹的分支(參閱樓上的視圖層次圖)。

XML元素的名稱與它展現的Java類相對應。是以一個<TextView>元素将在你的UI中生成一個TextView,而<LinearLayout>則建立一個LinearLayout視圖組。當你載入一個布局資源時,Android系統會根據你布局中的元素初始化這些運作時對象。

舉例來說,一個包含文本視圖和一個按鈕的簡單垂直布局如下:

<?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:id="@+id/text"

              android:layout_width="wrap_content"

              android:layout_height="wrap_content"

              android:text="Hello, I am a TextView" />

    <Button android:id="@+id/button"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="Hello, I am a Button" />

</LinearLayout>

請注意:LinearLayout 元素包含了TextView 和Button 對象。你可以在其中另外安置一個LinearLayout (或其它類型的視圖組),以延展這個視圖層次,建構更複雜的布局。

欲獲知更多如何建構UI布局的内容,請參閱聲明布局。

提示:您也可以用Java代碼來繪制View和ViewGroup對象,并用addView(View)方法動态的插入新的View和ViewGroup對象。

您有相當多的方法來對視圖進行布局。使用大量不同種類的視圖組,您可以有近乎無窮的方式來建構子視圖和視圖組。Android提供了一些預定義的視圖組,其中包括LinearLayout, RelativeLayout, AbsoluteLayout, TableLayout, GridLayout以及其它的一些。每個都為定義子視圖和布局結構提供了一套獨特的布局參數。

欲了解其它用于布局的不同種類的視圖組,請參閱普通布局對象。

部件Widgets

部件是為使用者互動界面提供服務的視圖對象。Android提供了一套完整的部件實作,包括按鈕、複選框、文本輸入框等,以助于你快速的建構UI。Android還提供了一些更進階的部件,比如日期選擇、時鐘以及縮放控制。但您并沒有被局限于Android平台提供的這些部件上。如果您想建立一些您自己的定制動作元素,您可以這麼做,隻要定義自己的視圖對象或者擴充或合并現有的部件就行。

更多資訊參閱建構自定義元件。

您可以在android.widget包中找到Android提供的部件清單。

使用者界面事件UI Events

當你在使用者界面中加入了一些視圖和工具之後,你可能想要知道如何讓它們與使用者互動,進而實作你的動作。如欲獲得使用者界面事件通知,你需要做以下兩件事情之一:

    * 定義一個事件偵聽器并将其注冊至視圖。通常情況下,這是你偵聽事件的主要方式。View類包含了一大堆命名類似 On<什麼什麼>Listener的接口,每個都帶有一個叫做On<什麼什麼>()的回調方法。比如:View.OnClickListener (用以處理視圖中的點選),View.OnTouchListener(用以處理視圖中的觸屏事件),以及View.OnKeyListener (用以處理視圖中的裝置按鍵事件)。是以,如果你希望你的視圖在它被”點選”(比如選擇了一個按鈕)的時候獲得通知,你就要實作OnClickListener,定義它的onClick()回調方法(在其中進行相應處理),并将它用setOnClickListener()方法注冊到視圖上。

    * 為視圖覆寫一個現有的回調方法。這種方法主要用于你自己實作了一個View類,并想偵聽其上發生的特定事件。比如說當螢幕被觸摸(onTouchEvent()),當軌迹球發生了移動(onTrackballEvent())或者是裝置上的按鍵被按下(onKeyDown())。這種方式允許你為自己定制的視圖中發生的每個事件定義預設的行為,并決定是否需要将事件傳遞給其它的子視圖。再說一次,這些是View類相關的回調方法,是以你隻能在你建構自定義元件時定義它們。

如何在視圖中處理使用者互動請參見處理使用者界面事件文檔。

菜單Menus

應用程式菜單是應用程式使用者界面中另外一個重要的組成部分。菜單為展現應用程式功能和設定提供了一個可靠的界面。按下裝置上的MENU鍵會調出最普通的應用程式菜單。然而,你也可以加入當使用者長按一個項目時調出的上下文菜單。

菜單也是用視圖層次進行構架的,但你不必自己定義這個架構。你隻要為你的Activity定義onCreateOptionsMenu()和onCreateContextMenu()回調方法,并聲明你想要包含在菜單中的項目就行了。Android将為你的菜單自動建立視圖層次,并在其中繪入你的菜單項。

菜單會自行處理它們的事件,是以你不必為你菜單中的項目注冊事件偵聽器。當你菜單中的一項被標明時,架構将自動調用onOptionsItemSelected()或onContextItemSelected()方法。

如同應用程式布局一樣。你也可以在一個XML檔案中定義你菜單中的項目。

更多資訊,請參閱建立菜單。

進階話題Advanced Topics

一旦你對建立使用者界面的基礎了如指掌,你就可以嘗試着用一些進階功能來建立更加複雜的應用程式界面。

擴充卡Adapter

有時候你會想要用一些無法寫死的資訊來填充視圖組。你想将源于外部的資料綁定到你的視圖中。為達到這個目的,你可以使用AdapterView作為你的視圖組,并用Adapter傳來的資料初始化每個子視圖并填入其中。

AdapterView對象是一個用給定的Adapter對象為基礎建構它的子視圖的ViewGroup實作。而Adapter在你的資料源(可能是一個外部字元串數組)和顯示這些資料的AdapterView之間扮演着一個信使的角色。針對特定的任務有着很多不同的Adapter類實作,比如CursorAdapter依據Cursor讀出一個資料庫的資料,而一個ArrayAdapter則從任一個數組進行讀取。

想要了解如何運用Adapter填充你的視圖,請參見用AdapterView綁定至資料。

風格與主題Styles and Themes

或許你對标準工具的外表不是那麼滿意。為了解決這個問題,你可以建立你自己的風格和主題。

    * 風格是一套包含一個或多個格式化屬性的整體,你可以把它們加諸于你布局中的單個元素之上。比如,你可以定義一個包含特定文本字型大小和顔色的風格,并将它單獨施用于特定的視圖元素。

    * 主題也是一套包含一個或多個格式化屬性的整體,但卻應用于一個應用程式中的所有Activity,或單獨一個Activity。比如說,你可以定義一個包含了特定視窗邊框顔色和版面背景、以及一套字型大小和菜單顔色的主題。這個主題可以施用于特定的Activity抑或整個應用程式。

風格與主題隸屬于資源。Android提供了一些預設的風格和主題供你使用,你也可以定制你自己的風格和主題資源。

想了解更多關于使用風格和主題的内容,請參閱使用風格和主題文檔。

資源和資産Resources and Assets

資源是Android應用程式不可或缺的部分。總體而言,資源是你想包含和引入到應用程式裡面的一些外部元素,比如圖檔、音頻、視訊、文本字元串、布局、主題等。每個Android應用程式包含一個資源目錄(res/)和資産目錄(assets/),資産不經常被使用,因為它們的應用程式很少。你僅在需要讀取原始位元組流時才需要儲存資料為資産。資源和資産目錄均駐留在Android項目樹的頂端,和源代碼目錄(src/)處在同一級上。

資源和資産從表面上看沒多大差別,不過總體上,在存儲外部内容時資源用得更多。真正的差別在于任何放置在資源目錄裡的内容可以通過您的應用程式的R類通路,這是被Android編譯過的。而任何存放在資産目錄裡的内容會保持它的原始檔案格式,為了讀取它,你必須使用AssetManager來以位元組流的方式讀取檔案。是以保持檔案和資料在資源中(res/)中會更友善通路。

在這篇文章中,你将擷取關于Android應用程式經常使用的标準資源類型以及如何在代碼中引用方面的資訊。資源和國際化(Resources and Internationalization)是第一步,可以知道Android如何利用項目資源。然後,可用資源類型(Available Resource Types)彙總描述了各種資源類型及其規格引用。

資源和國際化Resources and Internationalization

資源是外部檔案(即非源代碼檔案),它們被你的代碼使用,并且在編譯時被編譯到你的應用程式中。Android支援很多不同類型的資源檔案,包括XML、PNG和JPEG檔案。XML檔案會由于其所描述的内容不同而形式不同。該文檔描述了所有支援的檔案類型及每種類型的文法或格式。

資源從源代碼中被抽取出來,基于效率考慮,XML檔案被編譯成二進制、可以快速加載的形式。字元串,同樣被壓縮為一種更富效率的存儲形式。由于這些原因,在Android平台中我們就有了這些不同的資源類型。

這是一篇純粹的技術性文檔,它和可用資源(Available Resources)一起覆寫了有關資源的衆多資訊。在使用Android時并不需要記住這篇文檔,但是當你需要它時你應該知道來這裡尋找資訊。

介紹Introduction

這個話題包含了與之相應的術語清單,和一系列在代碼中使用資源的執行個體。關于Android支援的所有資源類型的完整指南,請查閱可用資源(Available Resources)。

Android資源系統記錄應用程式中所有非代碼資産。你可以使用Resources類來通路應用程式中的資源;一般可以通過Context.getResources()獲得這個Resources執行個體。

一個應用程式的資源在生成(build)時被編譯器編譯到應用程式的二進制檔案中。要使用一個資源,你必須把它放置到源代碼樹中的正确位置,并且生成(build)到你的應用程式中。作為編譯過程的一部分,每個資源的标記都會被生成,在你的源代碼中可以使用這些标記-這允許編譯器驗證你的應用程式代碼是否和你定義的資源相比對。

本部分的其餘内容以一個在應用程式中如何使用資源的指南的形式組織。

建立資源Creating Resources

Android支援字元串、位圖以及其他很多種類型的資源。每一種資源的文法、格式以及存放的位置,都會根據其類型的不同而不同。通常,你建立的資源一般來自于三種檔案:XML檔案(除位圖和raw之外的任何檔案)、位圖檔案(圖像)以及Raw檔案(除前面以外的其他東西,如聲音檔案,等等)。事實上,XML檔案也有兩種不同的類型:被原封不動地編譯進包内的檔案和被aapt用來産生資源的檔案。這裡有一個每種資源類型的清單,包括檔案格式、檔案描述以及XML檔案類型的細節。

你可以在你的項目中的res/目錄的适當的子目錄中建立和儲存資源檔案。Android有一個資源編譯器(aapt),它依照資源所在的子目錄及其格式對其進行編譯。這裡有一個每種資源的檔案類型的清單,關于每種類型的描述、文法、格式以及其包含檔案的格式或文法見資源參考。

表一

目錄Directory

資源類型Resource Types

res/anim/

XML檔案,它們被編譯進逐幀動畫(frame by frame animation)或補間動畫(tweened animation)對象

res/drawable/

.png、.9.png、.jpg檔案,它們被編譯進以下的Drawable資源子類型中:

要獲得這種類型的一個資源,可以使用Resource.getDrawable(id)

位圖檔案

9-patches(可變尺寸的位圖)

為了擷取資源類型,使用mContext.getResources().getDrawable(R.drawable.imageId)

注意:放在這裡的圖像資源可能會被aapt工具自動地進行無損壓縮優化。比如,一個真彩色但并不需要256色的PNG可能會被轉換為一個帶調色闆的8位PNG。這使得同等品質的圖檔占用更少的資源。是以我們得意識到這些放在該目錄下的二進制圖像在生成時可能會發生變化。如果你想讀取一個圖像位流并轉換成一個位圖(bitmap),請把圖像檔案放在res/raw/目錄下,這樣可以避免被自動優化。

res/layout/

被編譯為螢幕布局(或螢幕的一部分)的XML檔案。參見布局聲明(Declaring Layout)

res/values/

可以被編譯成很多種類型的資源的XML檔案。

注意:不像其他的res/檔案夾,它可以儲存任意數量的檔案,這些檔案儲存了要建立資源的描述,而不是資源本身。XML元素類型控制這些資源應該放在R類的什麼地方。

盡管這個檔案夾裡的檔案可以任意命名,不過下面使一些比較典型的檔案(檔案命名的慣例是将元素類型包含在該名稱之中):

*                   array.xml定義資料

*                   colors.xml定義color drawable和顔色的字元串值(color string values)。使用Resource.getDrawable()和Resources.getColor()分别獲得這些資源。

*                   dimens.xml定義尺寸值(dimension value)。使用Resources.getDimension()獲得這些資源。

*                   strings.xml定義字元串(string)值(使用Resources.getString()或者Resources.getText()擷取這些資源。getText()會保留在UI字元串上應用的豐富的文本樣式)。

·                                 styles.xml定義樣式(style)對象。

res/xml/

任意的XML檔案,在運作時可以通過調用Resources.getXML()讀取。

res/raw/

直接複制到裝置中的任意檔案。它們無需編譯,添加到你的應用程式編譯産生的壓縮檔案中。要使用這些資源,可以調用Resources.openRawResource(),參數是資源的ID,即R.raw.somefilename。

資源被編進最終的APK檔案中。Android建立了一個封裝類,叫做R,在代碼中你可以使用它來引用這些資源。R包含了根據資源檔案的路徑和名稱命名的子類。

全局資源說明Global Resource Notes

一些資源允許你定義顔色值。Android接受的顔色值可以使用多種web樣式的形式--以下幾種包含十六進制常數的形式:#RGB、#ARGB、#RRGGBB、#AARRGGBB。

所有顔色值支援設定透明度(alpha channel value),前兩位的十六進制數指定了透明了。0在透明度值是全透明。預設值是不透明。

使用資源Using Resources

這一部分描述如何使用你建立的資源。它包含以下主題:

l         代碼中使用資源 - 如何在你的代碼中調用資源進行執行個體化。

l         從其他資源中引用資源 - 你可以從其他資源中引用資源。這就使得你可以重用資源中公共資源值。../../Docs/android_dev_guide/android_dev_guide/developer.android.com/guide/topics/resources/resources-i18n.html - ReferencesToResources

l         支援針對交替配置的交替資源 - 你可以根據主機硬體的語言或顯示配置指定加載不同的資源。

在編譯時,Android産生一個名為R的類,它包含了你的程式中所有資源的資源辨別符。這個類包含了一些子類,每一個子類針對一種Android支援的資源類型,或者你提供的一個資源檔案。每一個類都包含了已編譯資源的一個或多個資源辨別符,你可以在代碼中使用它們來加載資源。下面是一個小的資源檔案,包含了字元串、布局(螢幕或螢幕的一部分)和圖像資源。

注意:R類是一個自動産生的檔案,并沒有設計為可以手動編輯。當資源更新時,它會根據需要重新産生。

package com.google.android.samples;

public final class R {

public static final class string {

public static final int greeting = 0x0204000e;

public static final int start_button_text = 0x02040001;

public static final int submit_button_text = 0x02040008;

public static final main_screen_title = 0x0204000a;

};

public static final class layout {

public static final int start_screen = 0x02070000;

public static final int new_user_pane = 0x02070001;

public static final int select_user_list = 0x02070002;

};

public static final class drawable {

public static final int company_logo = 0x02020005;

public static final int smiling_cat = 0x02020006;

public static final int yellow_fade_background = 0x02020007;

public static final int stretch_button_1 = 0x02020008;

};

};

在代碼中使用資源Using Resources in Code

在代碼中使用資源,隻是要知道所有資源ID和你的被編譯的資源是什麼類型。下面是一個引用資源的文法:

R.resource_type.resource_name

或者

android.R.resource_type.resource_name

其中resource_type是R的子類,儲存資源的一個特定類型。resource_name時在XML檔案定義的資源的name屬性,或者有其他檔案類型為資源定義的檔案名(不包含擴充名)。每一種資源類型都會根據其類型加為一個特定的R子類;要了解R的哪一個子類是關于你的資源類型的,請參考資源參考(resource reference)文檔。被你的應用程式編譯的資源可以不加包名引用(就像R.resource_type.resource_name這樣簡單)。Android包含了很多标準資源,如螢幕樣式和按鈕背景。要在代碼中引用這些資源,你必須使用android進行限定,如android.R.drawable.button_background。

這裡有一些在代碼中使用已編譯資源的正确和錯誤用法的例子。

// Load a background for the current screen from a drawable resource.

this.getWindow().setBackgroundDrawableResource(R.drawable.my_background_image);

// WRONG Sending a string resource reference into a

// method that expects a string.

this.getWindow().setTitle(R.string.main_title);

// RIGHT Need to get the title from the Resources wrapper.

this.getWindow().setTitle(Resources.getText(R.string.main_title));

// Load a custom layout for the current screen.

setContentView(R.layout.main_screen);

// Set a slide in animation for a ViewFlipper object.

mFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.hyperspace_in));

// Set the text on a TextView object.

TextView msgTextView = (TextView)findViewByID(R.id.msg);

msgTextView.setText(R.string.hello_message);

引用資源References to Resources

在屬性(或資源)中提供的值也可以作為資源的引用。這種情況經常使用在布局檔案中,以提供字元串(是以它們可以被本地化<将UI上的字元串放在一個單獨的檔案中,在做國際化時隻需要将它們翻譯成相應的語言版本,然後應用程式根據locale資訊加載相應的字元串檔案——譯者注>)和圖像(它們存在于另外的檔案中),雖然引用可以是任何資源類型,包括顔色和整數。

例如,如果我們有顔色資源(color resources),我們可以編寫一個布局檔案,将文本的顔色設為那些資源中包含的值:

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

<EditText id="text" xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="@color/opaque_red"

android:text="Hello, World!" />

注意,這裡使用“@”字首引入對一個資源的引用——在@[package:]type/name形式中後面的文本是資源的名稱。在這種情況下,我們不需要指定包名,因為我們引用的是我們自己包中的資源。要引用系統資源,你應該這樣寫:

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

<EditText id="text" xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="@android:color/opaque_red"

android:text="Hello, World!" />

另外一個例子,當在布局檔案中提供字元串以便于本地化時,你應該一直使用資源引用。

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

<EditText id="text" xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="@android:color/opaque_red"

android:text="@string/hello_world" />

這種技巧還可以用來建立資源之間的引用。例如,我們可以建立新的drawable資源作為已存在資源的别名。

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

<resources>

<drawable id="my_background">@android:drawable/theme2_background</drawable>

</resources>

引用主題屬性References to Theme Attributes

另外一種資源值允許你引用目前主題中的屬性的值。這個屬性值隻能在樣式資源和XML屬性中使用;它允許你通過将它們改變為目前主題提供的标準變化來改變UI元素的外觀,而不是提供具體的值。

如例中所示,我們在布局資源中使用這個特性将文本顔色設定為标準顔色的一種,這些标準的顔色都是定義在基本系統主題中。

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

<EditText id="text" xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="?android:textDisabledColor"

android:text="@string/hello_world" />

注意,這和資源引用非常類似,除了我們使用一個“?”字首代替了“@”。當你使用這個标記時,你就提供了屬性資源的名稱,它将會在主題中被查找——因為資源工具知道需要的屬性資源,是以你不需要顯示聲明這個類型(如果聲明,其形式就是?android:attr/android:textDisabledColor)。

除了使用這個資源的辨別符來查詢主題中的值代替原始的資源,其命名文法和“@”形式一緻:?[namespace:]type/name,這裡類型可選。

使用系統資源Using System Resources

在系統中的包含了很多應用程式可以使用的資源。所有的這些資源都在“android.R”類下定義。例如,使用下面的代碼你可以在螢幕上顯示标準應用程式的圖示:

public class MyActivity extends Activity {

public void onStart() {

requestScreenFeatures(FEATURE_BADGE_IMAGE);

super.onStart();

setBadgeResource(android.R.drawable.sym_def_app_icon);

}

}

以相似的方式,下面的代碼将對你的螢幕應用系統定義的标準“綠色背景”視覺處理。

public class MyActivity extends Activity

public void onStart() {

super.onStart();

setTheme(android.R.style.Theme_Black);

}

}

替換資源(為了可替換的資源和配置)Alternate Resources

你可以根據UI語言或者裝置上的硬體配置,為你的産品提供不同的資源。注意,盡管你可以包含不同的字元串、布局和其他資源,然而SDK沒有方法供你指定加載哪一個替換資源。Android檢測關于硬體和未知的适當配置,然後适當加載。使用者可以使用裝置上的設定面闆選擇替換語言設定。

為了包含替換資源,需要建立平行的資源檔案夾,而檔案夾的名字後面要使用限定符表示它要應用的配置(語言、螢幕方向等等)。例如,下面的工程包含了字元串資源,一個用于英語,而另外一個用于法語:

MyApp/

res/

values-en/

strings.xml

values-fr/

strings.xml

Android支援幾種類型的限定符,每一個都有不同的值。把它們連接配接在資源檔案夾名稱的後面,使用短橫線隔開。你可以為每一個檔案夾名稱添加多個限定符,但是它們必須按照這裡列出的順序排列。例如,一個包含drawable資源的檔案夾,對于一個完整詳細的配置,可能看起來像:

MyApp/

res/

values-en/

drawable-en-rUS-port-160dpi-finger-qwerty-dpad-480x320/

更典型的是,你隻需指定一些特定的要定義資源的配置選項。你可以放棄完整清單中的任何值,但同時要保證剩下的值仍然保持清單中的順序。

MyApp/

res/

drawable-en-rUS-finger/

drawable-port/

drawable-port-160dpi/

drawable-qwerty/

表2 列舉了合法的限定符目錄名稱,按優先級排序。下表中列舉在上面的限定符比下面的具有更高的優先級,如同Android如何查找最比對的目錄中所描述的那樣。

表2

限定符Qualifier

值Values

移動國家碼MCC和移動網絡碼MNC

手機裝置SIM卡上的移動國家碼和移動網絡碼。比如mcc310-mnc004 (美國,Verizon品牌); mcc208-mnc00 (法國, Orange品牌); mcc234-mnc00 (英國,BT品牌).

如果這個裝置使用一個無線連接配接(GSM電話),則MCC來自SIM卡,而MNC來自該裝置将要附着的網絡。你有時會僅使用MCC,例如包含特定國家合法資源在您的應用程式中。如果您的應用程式指定了MCC/MNC組合的資源,這些資源僅在MCC和MNC都比對的時候才能使用。

語言和區域Language and region

兩個字母的ISO 639-1語言碼和ISO 3166-1-alpha-2區域碼 (以"r"為字首)。比如en-rUS, fr-rFR, es-rES.

這個代碼是大小寫敏感的:語言碼是小寫字母,國家碼是大寫字母。你不能單獨指定一個區域,但是你可以單獨指定一個語言,比如en, fr, es, zh.

螢幕方向Screen orientation

縱向,橫向,正方形(port, land, square)

螢幕像素密度Screen pixel density

92dpi, 108dpi等. 當Android選擇使用哪個資源時,它對螢幕像素密度的處理和其它限定符不同。在文章後面描述的步驟1Android如何查找最比對的目錄中,螢幕密度總被認為是比對的。在步驟4中,如果被考慮的限定符是螢幕密度,Android将選擇在那個位置的最佳比對,而無需繼續步驟5。

觸摸屏類型Touchscreen type

非觸摸式,觸摸筆,手指(notouch, stylus, finger)

鍵盤可用方式Whether the keyboard is available to the user

外在鍵盤,隐藏鍵盤,軟鍵盤(keysexposed, keyshidden, keyssoft)

如果你的應用程式有一個特定的資源隻能通過軟體盤使用,則使用keyssoft值,如果沒有keyssoft資源可用(隻有keysexposed和 keyshidden)并且該裝置顯示了一個軟鍵盤,那麼系統将使用keysexposed資源。

首選文本輸入方法Primary text input method

不支援按鍵,标準鍵盤,12鍵(nokeys, qwerty, 12key)

首選非觸摸式導航方法Primary non-touchscreen

navigation method

不支援導航,滑闆,跟蹤球,滾輪(nonav, dpad, trackball, wheel)

螢幕分辨率Screen dimensions

320x240, 640x480, 等. 更大的分辨率必須先被指定。

SDK版本SDK version

裝置支援的SDK版本,比如v3。Android1.0 SDK是v1,1.1SDK是v2,1.5SDK是v3。

小版本(Minor version)

你目前還不能指定小版本,它總是被設定為0。

這個清單不包含裝置特有的參數比如載波,品牌,裝置/硬體,或者制造商。所有應用程式需要知道的裝置資訊均通過上表中的資源限定符編碼。

所有資源目錄,許可的和未經許可的,都存放在res/目錄下。下面是一些關于許可的資源目錄名稱的指導原則:

l         你可以指定多個限定符,用破折号分開。比如,drawable-en-rUS-land會被應用在美國英語的橫向手機裝置中。

l         限定符必須符合表2中列舉的順序。比如:

*       正确的:values-mcc460-nokeys/

*       錯誤的:values-nokeys-mcc460/

l         限定符的值大小寫敏感。比如一個縱向特定的drawable目錄必須命名為drawable-port,不可以是drawable-PORT或drawable-Port。

l         每個限定符類型僅支援一個值。比如,如果你想使用為法國和西班牙使用相同的drawable檔案,你得需要兩個資源目錄,如drawable-rES/和drawable-rFR/,包含相同的檔案。你不能使用一個名為drawable-rES-rFR的目錄。

l         限定符不能嵌套使用。比如,你不能使用res/drawable/drawable-en。

資源怎麼在代碼中使用How resources are referenced in code

所有的資源均通過它們簡單未經修飾的名字在代碼或資源引用文法中引用。是以如果一個資源命名如下:

    MyApp/res/drawable-port-92dpi/myimage.png

它會被這樣引用:

    R.drawable.myimage (code)

    @drawable/myimage (XML)

如果有多個drawable目錄可用, Android将會選擇其一(如下所述)并從中加載myimage .png。

AndroidHow Android finds the best matching directory如何查找最比對的目錄

Android将從各種潛在的資源中挑選出哪個應該在運作時使用,這取決于裝置的目前配置。這裡的例子假定使用了如下的裝置配置:

區域Locale = en-GB

螢幕方向Screen orientation = port

螢幕像素密度Screen pixel density = 108dpi

觸摸屏類型Touchscreen type = notouch

首選文本輸入方式Primary text input method = 12key

下面說明了Android如何作出選擇:

1.         排除和裝置配置沖突的資源檔案。比如,假定如下的drawables資源目錄可用。那麼drawable-fr-rCA/會被排除,因為它和裝置的區域配置沖突。

MyApp/res/drawable/

MyApp/res/drawable-en/

MyApp/res/drawable-fr-rCA/

MyApp/res/drawable-en-port/

MyApp/res/drawable-en-notouch-12key/

MyApp/res/drawable-port-92dpi/

例外:螢幕像素密度是唯一不用來排除檔案的限定符。即使裝置螢幕密度是108dpi,drawable-port-92dpi/也不會被從清單中排除,因為在這裡所有的螢幕密度都被視為比對。

2.         從表2中選取最高優先級的限定符(從MCC開始,然後自該清單依次往下)。

3.         有沒有哪個可用的資源目錄包含了這個限定符?

&sup2;        如果沒有,回到步驟2然後檢視表2中所列的下一個限定符。在我們的例子中,答案是“沒有”直到我們到達語言這一級。If No, return to step 2 and look at the next qualifier listed in Table 2. In our example, the answer is "no" until we reach Language;

&sup2;        如果有,則跳轉到步驟4。

4.         排除不包含這個限定符的資源目錄,在我們的例子中,我們排除所有不包含語言的目錄。

MyApp/res/drawable/

MyApp/res/drawable-en/

MyApp/res/drawable-en-port/

MyApp/res/drawable-en-notouch-12key/

MyApp/res/drawable-port-92dpi/

MyApp/res/drawable-port-notouch-12key

例外:如果詢問中的限定符是螢幕像素密度,Android會選擇最接近比對于裝置的選項,而且選擇過程将會完成。一般而言,Android會傾向于縮小一個大圖檔而不是放大一個小圖檔。

5.         回頭重複步驟2,3,4直到隻剩下一個選擇。在本例中,螢幕方向是下一個要比較的限定符,我們排除沒有指定螢幕方向的資源。現在隻剩下一個選擇,那就是它了。當drawables被這個應用程式調用時,Android系統會從下面這個目錄中加載資源:MyApp/res/drawable-en-port/

提示Tip:限定符的優先權比比對的數目要重要得多。比如,在上面的步驟4中,清單中最後的選項包含三個限定符和裝置比對(方向,觸摸屏類型,和輸入法),而drawable-en隻有一個參數比對(語言)。但是,語言擁有更高的優先權,是以drawable-port-notouch-12key 被排除出局。

下面的流程圖總結了Android如何選擇資源目錄來加載的過程:

術語Terminology

資源系統将一系列分散内容集合在一起形成最終的完整的資源功能,去幫助我們了解整個系統。這裡有一些核心概念以及元件的概要說明,你在開發中将可能使用到:

資産Asset:應用程式的獨立的資料塊。這包含所有從java程式編譯成的目标檔案,圖像 (例如PNG圖檔), XML檔案等等。這些檔案以一種特定的方式組織在一起,在程式最後打包時,它們被捆綁進一個單獨的ZIP檔案裡。

aapt::Android最終檔案打包工具。這個工具産生最終程式的ZIP檔案。除了将資産中繼資料檔案收集在一起,它也把資源定義解析到最終的二進制資料裡。

資源表Resource Table: aapt工具産生的特殊的檔案,描述了所有在程式/包裡的資源。這個檔案可以通過資源類來通路;它不能直接和應用程式接觸。

資源Resource: 資源表裡一條記錄描述的是單一的命名值。大體上, 資源分成兩種:元資源和包資源.

資源辨別符Resource Identifier: 在資源表裡所有的資源都被唯一的整數辨別着。所有的代碼中(資源描述,XML 檔案,Java源代碼)你可以直接使用符号名代替真實的整數數值。

元資源Primitive Resource: 所有元資源都可以被寫成一個簡單的字串,使用一定的格式可以描述資源系統裡各種不同的基本類型:整數,顔色,字串,其他資源的引用,等等。像圖檔以及XML描述檔案這些複雜資源,被以元字串資源儲存,它們的值就是相關最終資料檔案的路徑。

包資源Bag Resource: 一種特殊類型的資源,不是簡單的字元串,而是一個容納名字/數值對的任意清單。每個名字本身就是資源辨別,每個值可以容納相同類型的字元串格式的資料作為一個普通資源。包資源支援繼承:一個包裡的資料能從其他包裡繼承,有選擇地替換或者擴充能産生它自己的内容。

種類Kind: 資源種類是對于不同需求的資源辨別符而言的。例如,繪制資源類常常執行個體化繪制類的對象,是以這些包含顔色以及指向圖檔或XML檔案的字元串路徑資料是原始資料。其它常見資源類型是字元串(本地化字元串),顔色(基本顔色),布局(一個指向XML檔案的字串路徑,它描述的是一個使用者界面)以及風格(一個描述使用者接口屬性的包裝資源)。還有一個标準的“attr”資源類型,它定義了命名包裝資料以及XML屬性的資源辨別符。

風格Style: 包含包裝資源類型的名字常常用來描述一系列使用者接口屬性。例如,一個TextView的類可能會有一個描述界面風格的類來定義文本大小,顔色以及對齊方式。在一個界面布局的XML檔案中,可以使用“風格” 屬性來确定整體界面風格,它的值就是風格資源的名字。

風格類Style Class: 這裡将詳述一些屬性資源類。其實資料不會被放在資源表本身,通常在源代碼裡它以常量的形式出現,這也可以使你在風格類或者XML的标簽屬性裡友善找到它的值。例如,Android平台裡定義了一個“視圖”的風格類,它包含所有标準視圖的屬性:畫圖區域,可視區域,背景等。這個視圖被使用時,它就會借助風格類去從XML檔案取得資料并将其載入到執行個體中。

配置Configuration: 對許多特殊的資源辨別符,根據目前的配置,可以有多種不同的值。配置包括地區(語言和國家),螢幕方向,螢幕分辨率,等等。目前的配置用來選擇當資源表載入時哪個資源值生效。

主題Theme: 一個标準類型的資源能為一個特殊的上下文提供全局的屬性值。例如,當應用工程師寫一個活動時,他能選擇一個标準的主題去使用,白色的或者黑色的;這個類型能提供很多資訊,如螢幕背景圖檔/顔色,預設文本顔色,按鈕類型,文本編輯框類型,文本大小,等。當布置一個資源布局時,控件(文本顔色,選中後顔色,背景)的大部分設定值取自目前主題;如果需要,布局中的風格以及屬性也可以從主題的屬性中獲得。

覆寫層Overlay: 資源表不能定義新類型的資源,但是你可以在其他表裡替換資源值。就像配置值,這可以在裝載時候進行;它能加入新的配置值(例如,改變字串到新的位置),替換現有值(例如,将标準的白色背景替換成"Hello Kitty"的背景圖檔),修改資源包(例如修改主題的字型大小。白色主題字型大小為18pt)。這實際上允許使用者選擇裝置不同的外表,或者下載下傳新的外表檔案。

資源引用Resource Reference

可用資源Available Resources文檔提供了一個各種類型資源的詳細清單,并描述了如何在Java代碼中或其他引用中使用它們。

國際化和本地化Internationalization and Localization

即将完成:國際化和本地化是非常關鍵的,但現在的SDK還沒有完全準備好。當SDK成熟時,這個章節會包含Android平台國際化和本地化的相關資訊。在此期間,讓我們先從把資源外部化以及練習以好的結建構立和使用資源開始做起吧。

cuihai 2011-03-03 12:14

意圖和意圖過濾器Intents and Intent Filters

一個應用程式的三個核心元件-活動,服務和廣播接收器是通過消息即意圖(Intents)來激活的。Intent息傳送是相同或不同應用中元件運作時晚綁定的一種機制。意圖本身,一個意圖對象,是一個包含被執行操作抽象描述的被動的資料結構-或者,對于廣播而言,是某件已經發生并被聲明的事情的描述。存在不同的機制來傳送意圖到每種元件中:

    * 一個意圖對象是傳遞給Context.startActivity()或者Activity.startActivityForResult()來啟動一個活動或者讓一個存在的活動去做某些新的事情。

    * 一個意圖對象是傳遞給Context.startService()來發起一個服務或者遞交新的指令給運作中的服務。類似的,一個意圖能被傳遞給Context.bindService() 來在調用元件和一個目标服務之間建立連接配接。作為一個可選項,它可以發起這個服務如果還沒運作的話。

    * 傳遞給任意廣播方法(例如Context.sendBroadcast(),Context.sendOrderedBroadcast(), 或者Context.sendStickyBroadcast())的意圖對象被傳遞給所有感興趣的廣播接收者。許多種廣播産生于系統代碼。

在每個例子裡,Android系統找到合适的活動,服務,或者一組廣播接收者來回應這個意圖,必要時執行個體化它們。這些消息傳送系統沒有重疊:廣播意圖僅被傳遞給廣播接收者,永遠不會給活動或者服務。一個傳送給startActivity()的意圖是隻會被傳遞給一個活動,永遠不會給一個服務或廣播接收者,如此類推。

這篇文檔以意圖對象的描述開始,然後描述Android映射意圖到元件的規則-如何解決哪個元件應該接收一個意圖消息。對于沒有顯式命名一個目标元件的意圖,這個過程包括對照與潛在目标相關聯的意圖過濾器來測試這個意圖對象。

意圖對象Intent Objects

一個意圖Intent對象是一堆資訊。它包含接收這個意圖的元件感興趣的資訊(例如将要采取的動作和操作的資料)再加上Android系統感興趣的資訊(例如應該處理這個意圖的元件類别和如何啟動一個目标活動的指令):

元件名稱Component name

應該處理這個意圖的元件名字. 這個字段是一個ComponentName對象- 一個組合物:目标元件的完全合格的類名 (比如"com.example.project.app.FreneticActivity") 以及應用程式描述檔案中設定的元件所在包的名字(比如, "com.example.project"). 這個元件名字的包部分和描述檔案中設定的包名字不一定要比對。

元件名字是可選的。如果被設定了,這個意圖對象将被傳遞到指定的類。如果沒有, Android使用另外的意圖對象中的資訊去定位一個合适的目标- 請看本文稍後描述的意圖解析Intent Resolution。

元件名字通過如下方法:setComponent(),setClass(), 或者setClassName()設定并通過getComponent()讀取。

動作Action

一個将被執行的動作的字元串命名-或者, 對于廣播意圖而言, 是發生并被報告的動作。這個意圖類定義了一些動作常量, 包含下面這些:

常量

目标元件

Action

ACTION_CALL

活動

開始一個電話呼叫

ACTION_EDIT

活動

顯示資料以給使用者編輯

ACTION_MAIN

活動

開始任務的初始活動,沒有輸入資料也沒有輸出傳回

ACTION_SYNC

活動

同步伺服器與移動裝置之間的資料

ACTION_BATTERY_LOW

廣播接收器

電池低電量警告

ACTION_HEADSET_PLUG

廣播接收器

耳機插拔

ACTION_SCREEN_ON

廣播接收器

螢幕開啟

ACTION_TIMEZONE_CHANGED

廣播接收器

時區變化

通過檢視Intent類描述可獲得一個通用動作的預定義常量清單。其他動作被定義在Android API的其他地方。你也可以自定義動作字元串來激活應用程式中的元件。那些你所建立的動作字元串應該以應用程式包名作為字首-例如:

"com.example.project.SHOW_COLOR".

動作很大程度上決定了意圖其他部分如何被組織-尤其是資料data和附加字段extras-很像一個方法名決定了一些參數和傳回值. 是以, 一個好的想法是使用盡可能具體的動作名并和意圖的其他字段緊密聯系起來。換句話說,為您的元件能處理的意圖對象定義一個整體的協定而不是定義一個孤立的動作。

一個意圖對象裡的動作可以通過setAction()方法設定和通過getAction()方法讀取.

資料Data

想要操作的資料統一資源辨別符(URI)和那種資料的多用途網際網路郵件擴充(MIME). 不同的動作伴随着不同種類的資料規格。例如,如果動作是ACTION_EDIT,資料字段會包含可編輯文檔的URI;如果動作是ACTION_CALL,資料字段會是一個電話号碼:含呼叫電話号碼的URI;類似的,如果動作是ACTION_VIEW而且資料字段是一個http:URI, 那麼接收到的活動将會是下載下傳并顯示URI所引用資料的請求。當比對一個意圖到一個能處理資料的元件時,除了它的URI外,通常需要知道資料類型(它的MIME類型)。

比如,一個能顯示圖檔的元件不應該被要求去播放一個聲音檔案。

在很多情況下,這個資料類型可以從URI裡推斷出來-尤其是content:URIs, 這意味着資料被存放在裝置上而且由一個内容提供者控制着。(參閱separate discussion on content providers). 但類型可以在意圖對象裡顯示的設定。setData()方法指定資料隻能為一個URI,setType()指定它隻能是一個MIME類型, 而setDataAndType()指定它同時為URI和MIME類型。URI通過getData()讀取,類型則通過getType().

目錄Category

一個包含關于應該處理這個意圖的元件的附加資訊的字元串。任意數目的類别描述可以被放到一個意圖對象裡。和動作一樣,意圖類定義若幹類别常量,包含如下這些:

常量

含義

CATEGORY_BROWSABLE

目标活動可以被浏覽器安全的喚起來顯示被一個連結所引用的資料-比如,一張圖檔或一條e-mail消息。

CATEGORY_GADGET

這個活動可以被嵌入到充當配件宿主的另外的活動裡面。

CATEGORY_HOME

這個活動将顯示桌面,也就是使用者開機後看到的第一個螢幕或者按HOME鍵時看到的螢幕。

CATEGORY_LAUNCHER

這個活動可以是一個任務的初始活動并被列在應用程式啟動器的頂層。

CATEGORY_PREFERENCE

目标活動是一個選擇面闆。

查閱Intent類描述可擷取類别的完整清單。

addCategory()方法在一個意圖對象中添加了一個目錄,removeCategory()删除之前添加的目錄,而getCategories()可以擷取目前對象的所有類别。

附加資訊Extras

應該遞交給意圖處理元件的附加資訊鍵-值對。就像一些動作伴随着特定的資料URIs類型,一些動作則伴随着特定的附加資訊。比如,一個ACTION_TIMEZONE_CHANGED意圖有一個“時區”附加資訊用來差別新的時區,而ACTION_HEADSET_PLUG有一個“狀态”附加字段表明耳機有沒有插着,以及一個“名字”附加資訊來表示耳機的類型。如果你想要建立一個SHOW_COLOR動作,顔色的值将被設定在一個附加的鍵-值對中。

意圖對象有一系列的put...()方法來插入各種不同的附加資料和一個類似的用來讀取資料的get...()方法系列。這些方法與Bundle對象的方法相似。事實上,附加資訊可以被當作一個Bundle通過使用putExtras()和getExtras()方法安裝和讀取。

标志Flags

各種類型的标志. 許多标志用來訓示Android系統如何去加載一個活動(例如,哪個是這個活動應該歸屬的任務)和啟動後如何對待它(比如,它是否屬于目前活動清單),所有這些清單都在意圖類中定義了。

Android系統以及這個平台上的應用程式利用意圖對象來發送源于系統的廣播以及激活系統定義的元件。要查閱如何組織一個意圖去激活一個系統元件,請咨詢引用中的意圖清單list of intents。

意圖解析Intent Resolution

意圖可以被分成兩組:

    * 顯式意圖 通過名字指明目标元件(這個元件名字字段component name field, 前面提到過, 有一個數值集)。既然元件名稱通常不為其他應用程式的開發者所了解,顯式意圖典型的被用作應用程式的内部消息-例如一個活動啟動一個附屬服務或姊妹活動。

    * 隐式意圖 不命名目标元件(元件名稱字段為空)。隐式意圖經常用來激活其他應用程式的元件。

Android遞交一個顯式的意圖給一個指定目标類的執行個體。意圖對象中的元件名稱唯一的确定哪個元件應該擷取這個意圖。隐式意圖需要一個不同的政策。在沒有指定目标的情況下,Android系統必須找到最合适的元件來處理這個意圖-單個活動或者服務來執行這個請求動作或者一系列的廣播接收器來應對廣播通告。

這是通過比較意圖對象的内容和意圖過濾器,有可能接收意圖的元件相關結構。過濾器公布一個元件具備的能力以及限定它能處理的意圖。他們使元件接收該公布類型的隐式意圖成為可能。如果一個元件沒有任何的意圖過濾器,那它隻能接收顯式意圖。一個帶過濾器的元件可以同時接收顯式和隐式意圖。

當一個意圖對象被一個意圖過濾器測試時,隻有三個方面會被參考到:

動作

資料(URI以及資料類型)

類别

附加資訊和标志并不參與解析哪個元件接收一個意圖。

意圖過濾器Intent filters

為了通知系統它們可以處理哪些意圖,活動、服務和廣播接收器可以有一個或多個意圖過濾器。每個過濾器描述元件的一個能力,一系列元件想要接收的意圖。它實際上按照一個期望的類型來進行意圖濾入,同時濾出不想要的意圖-但是隻有不想要的隐式意圖會被濾出(那些沒有命名目标的對象類)。一個顯式意圖總能夠被遞交給它的目标,而無論它包含什麼。這種情況下過濾器不起作用。但是一個顯式意圖僅當它能通過元件的一個過濾器時才可以被遞交到這個元件。

元件為它能做的每項工作,每個呈現給使用者的不同方面分有不同的過濾器。比如,範例記事本應用程式中的主要活動有三個過濾器-一個是空白闆,另一個是使用者可以檢視、編輯、或選擇的一個指定的記事目錄,第三是在沒有初始目錄說明的情況下查找一個特定的記錄。一個意圖過濾器是IntentFilter類的一個執行個體。但是,由于Android系統在啟動一個元件前必須知道這個元件的能力,意圖過濾器通常不會用Java代碼來設定,而是在應用程式清單檔案(AndroidManifest.xml)中設定<intent-filter>元素。(有一個例外,通過調用Context.registerReceiver()來注冊的廣播接收器的過濾器;它們是作為意圖過濾器對象而被直接建立的。

過濾器與安全Filters and security

不能信賴一個意圖過濾器的安全性。當它打開一個元件來接收某些特定類型的隐式意圖,它并不能阻止以這個元件為目标的顯式意圖。即使過濾器對元件要處理的意圖限制某些動作和資料源,總有人能把一個顯式意圖和一個不同的動作及資料源組合在一起,然後命名該元件為目标。

一個過濾器和意圖對象有同樣的動作、資料以及類别字段。一個隐式意圖在過濾器的所有三個方面都被測試。為了遞交到擁有這個過濾器的元件,它必須通過所有這三項測試。即便隻有一個不通過,Android系統都不會把它遞交給這個元件-至少以那個過濾器的标準而言。不過,由于一個元件可以包含多個意圖過濾器,一個不能通過其中一個元件過濾器的意圖可能在另外的過濾器上獲得通過。

三個測試較長的描述如下:

動作測試Action test

清單檔案中的意圖過濾器元素裡列舉了動作元素,比如:

<intent-filter . . . >

     <action android:name="com.example.project.SHOW_CURRENT" />

     <action android:name="com.example.project.SHOW_RECENT" />

     <action android:name="com.example.project.SHOW_PENDING" />

     . . .

</intent-filter>

如同例子所示,一個意圖對象隻對單個動作命名,而一個過濾器可能列舉多個。清單不能為空;一個過濾器必須包含至少一個動作元素,否則它将阻塞所有的意圖。

為了通過這個測試,在意圖對象中指定的動作必須比對過濾器中所列舉的動作之一。如果意圖對象或過濾器不指定一個動作,結果将如下:

·        如果這個過濾器沒有列出任何動作,那意圖就沒有什麼可比對的,是以所有的意圖都會測試失敗。沒有意圖能夠通過這個過濾器。

·         另一方面,一個未指定動作的意圖對象自動通過這個測試-隻要過濾器包含至少一個動作。

類别測試Category test

一個意圖過濾器<intent-filter>元素也列舉了類别作為子元素。比如:

<intent-filter . . . >

     <category android:name="android.intent.category.DEFAULT" />

     <category android:name="android.intent.category.BROWSABLE" />

     . . .

</intent-filter>

注意前面描述的動作和類别常量沒有在清單檔案中使用。相反使用了完整的字元串。比如,對應于前述CATEGORY_BROWSABLE常量,上面的例子裡使用了"android.intent.category.BROWSABLE"字元串。類似的,字元串"android.intent.action.EDIT" 對應于ACTION_EDIT常量。

對一個通過類别測試的意圖,每個意圖對象中的類别必須比對一個過濾器中的類别。這個過濾器可以列舉另外的類别,但它不能遺漏任何在這個意圖中的類别。

是以,原則上一個沒有類别的意圖對象應該總能夠通過測試,而不管過濾器裡有什麼。絕大部分情況下這個是對的。但有一個例外,Android把所有傳給startActivity()的隐式意圖當作他們包含至少一個類别:"android.intent.category.DEFAULT" (CATEGORY_DEFAULT常量)。是以,想要接收隐式意圖的活動必須在它們的意圖過濾器中包含"android.intent.category.DEFAULT"。(帶"android.intent.action.MAIN"和"android.intent.category.LAUNCHER"設定的過濾器是例外)。它們标記那些啟動新任務和呈現在啟動螢幕的活動。它們可以在類别清單中包含"android.intent.category.DEFAULT",但不是必要的。) 可查閱後面的使用意圖比對(Using intent matching)以獲得更多關于過濾器的資訊。

資料測試Data test

就像動作和類别,一個意圖過濾器的資料規格被包含在一個子元素中。而且這個子元素可以出現多次或一次都不出現。例如:

<intent-filter . . . >

     <data android:type="video/mpeg" android:scheme="http" . . . />

     <data android:type="audio/mpeg" android:scheme="http" . . . />

     . . .

</intent-filter>

每個資料<data>元素可以指定一個URI和一個資料類型(MIME媒體類型)。有一些單獨的屬性-模式,主機,端口和路徑-URI的每個部分:

scheme://host:port/path

比如,在下面的URI裡面,

content://com.example.project:200/folder/subfolder/etc

模式是"内容",主機是"com.example.project",端口是"200",路經是"folder/subfolder/etc"。主機和端口一起組成URI鑒權(authority);如果未指定主機,端口會被忽略。

這些屬性都是可選的,但彼此有依賴關系:一個授權要有意義,必須指定一個模式。一個路經要有意義,必須同時指定模式和鑒權。

當一個意圖對象中的URI被用來和一個過濾器中的URI規格比較時,它實際上比較的是上面提到的URI的各個部分。比如,如果過濾器僅指定了一個模式,所有那個模式的URIs和這個過濾器相比對;如果過濾器指定了一個模式、鑒權但沒有路經,所有相同模式和鑒權的URIs可以比對上,而不管它們的路經;如果過濾器指定了一個模式、鑒權和路經,隻有相同模式、鑒權和路經的URIs可以比對上。當然,一個過濾器中的路徑規格可以包含通配符,這樣隻需要部分比對即可。

資料<data>元素的類型屬性指定了資料的MIME類型。這在過濾器裡比在URI裡更為常見。意圖對象和過濾器都可以使用一個"*"通配符指定子類型字段-比如,"text/*"或者"audio/*"-訓示任何比對的子類型。

資料測試同時比較意圖對象和過濾器中指定的URI和資料類型。規則如下:

a.     一個既不包含URI也不包含資料類型的意圖對象僅在過濾器也同樣沒有指定任何URIs和資料類型的情況下才能通過測試。

b.    一個包含URI但沒有資料類型的意圖對象僅在它的URI和一個同樣沒有指定資料類型的過濾器裡的URI比對時才能通過測試。這通常發生在類似于mailto:和tel:這樣的URIs上:它們并不引用實際資料。

c.     一個包含資料類型但不包含URI的意圖對象僅在這個過濾器列舉了同樣的資料類型而且也沒有指定一個URI的情況下才能通過測試。

d.    一個同時包含URI和資料類型(或者可從URI推斷出資料類型)的意圖對象可以通過測試,如果它的類型和過濾器中列舉的類型相比對的話。如果它的URI和這個過濾器中的一個URI相比對或者它有一個内容content:或者檔案file: URI而且這個過濾器沒有指定一個URI,那麼它也能通過測試。換句話說,一個元件被假定為支援content:和file: 資料如果它的過濾器僅列舉了一個資料類型。

如果一個意圖可以通過不止一個活動或服務的過濾器,使用者可能會被詢問要激活那個元件。如果沒有發現目标對象将會出現異常。

通常情況Common cases

上面描述的資料測試的最後一個規則(d),表達了這樣一個期望即元件能夠從檔案或内容提供者中擷取本地資料。是以,它們的過濾器可以隻列舉一個資料類型而不需要顯式的命名content:和file:模式。這是一個典型情況。比如,一個如下的資料<data>元素,告訴Android這個元件能從内容提供者擷取圖檔資料并顯示:

<data android:type="image/*" />

既然大多數可用資料是通過内容提供者來分發,那麼過濾器最通常的配置就是指定一個資料類型而不指定URI。另外一個通用的配置是帶有一個模式和資料類型的過濾器。比如,一個如下的資料<data>元素告訴Android可以從網絡擷取視訊資料并顯示:

<data android:scheme="http" android:type="video/*" />

比如,想一下,當使用者點選網頁上的一個連結時浏覽器做了什麼。它首先試圖去顯示這個資料(如果這個連結指向一個HTML頁面)。如果它不能顯示這個資料,它會把一個顯式意圖和一個模式、資料類型組成整體然後嘗試啟動一個可以處理這個工作的活動。如果沒有接受者,它将要求下載下傳管理器來下載下傳資料。這讓它處于内容提供者的控制下,以便一個潛在的更大的活動池可以做出反應。

大多數應用程式同樣有一個方法去啟動重新整理,而不包含任何特定資料的引用。能初始化應用程式的活動擁有指定動作為"android.intent.action.MAIN"的過濾器。如果它們表述在應用程式啟動器中,那它們同樣指定了"android.intent.category.LAUNCHER"類别:

<intent-filter . . . >

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

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

</intent-filter>

使用意圖比對Using intent matching

通過意圖過濾器比對的意圖不僅是為了發現要激活的目标元件,而且為了發現這個裝置上的一系列元件的某些東西。比如,Android系統通過查找符合條件的所有活動(需要包含指定了動作"android.intent.action.MAIN"和"android.intent.category.LAUNCHER"類别的意圖過濾器,如前面章節所述)來産生應用程式啟動器,也就是使用者可用程式的前置螢幕。然後它顯示在這個啟動器裡的這些活動的圖示和标簽。類似的,它通過查找其過濾器配有"android.intent.category.HOME"元素的活動來發現桌面。

你的應用程式可以用類似的方式使用意圖比對。PackageManager有一系列的查詢query…()方法可以接收一個特定的意圖,以及相似的一個解析resolve…()方法系列可以确定應答意圖的最佳元件。比如,queryIntentActivities()傳回一個所有活動的清單,而queryIntentServices()傳回一個類似的服務清單。兩個方法都不會激活元件;它們僅僅列舉能應答的。對于廣播接收者,有一個類似的方法queryBroadcastReceivers()。

資料存儲Data Storage

概覽Storage quickview

&sup2; 系統偏好:快速,輕量級存儲

&sup2; 檔案:存儲到裝置内部或可移動閃存

&sup2; 資料庫:任意的結構化存儲

&sup2; 支援基于網絡的存儲

一個典型的桌面作業系統提供了一個通用檔案系統使得任何應用程式能夠使用它來存儲檔案,這些檔案可以被其它應用程式讀取(可能有通路權限的設定)。Android使用一個不同的系統:在Android上,所有應用程式資料(包括檔案)都是該應用程式私有的。

不過,Android同樣提供了一個應用程式向其它應用程式暴露其私有資料的基本方式-通過内容提供器。内容提供器是應用程式的可選元件,用來暴露該應用程式資料的讀寫接口,且遵循任何可能引入的約定。内容提供器實作了一個用來請求和修改資料的基本文法,一個讀取傳回資料的基本機制。Android為基礎資料類型如圖像,音頻和視訊檔案以及個人聯系人資訊提供了許多内容提供器。想要了解更多如何使用内容提供器的資訊,請參見一篇單獨的文章:内容提供器(Content Providers)。

無論你是否想把應用程式資料輸出給别人,你總需要有一個方法來儲存它。Android提供了下面4種機制來儲存和擷取資料:系統偏好Preferences,檔案Files,資料庫Databases和網絡Network。

系統偏好Preferences

系統偏好是一個用來存放和提取中繼資料類型鍵-值對的輕量級機制。它通常用來存放應用程式偏好,例如一個應用程式啟動時所使用的預設問候或文本字型。通過調用Context.getSharedPreferences()來讀寫數值。如果你想分享給應用程式中的其它元件,可以為你的偏好集配置設定一個名字,或者使用沒有名字的Activity.getPreferences()方法來保持對于該調用程式的私有性。你不能跨應用程式共享偏好(除了使用一個内容提供器)。

下面是一個為電腦設定按鍵靜音模式的例子:

import android.app.Activity;

import android.content.SharedPreferences;

public class Calc extends Activity {

public static final String PREFS_NAME = "MyPrefsFile";

    . . .    

    @Override

    protected void onCreate(Bundle state){        

       super.onCreate(state);

    . . .

       // Restore preferences

       SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);

       boolean silent = settings.getBoolean("silentMode", false);

       setSilent(silent);

    }

    @Override

    protected void onStop(){

       super.onStop();

      // Save user preferences. We need an Editor object to

      // make changes. All objects are from android.context.Context

      SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);

      SharedPreferences.Editor editor = settings.edit();

      editor.putBoolean("silentMode", mSilentMode);

      // Don't forget to commit your edits!!!

      editor.commit();

    }

}

檔案Files

你可以直接在移動裝置或可移動存儲媒介裡存放檔案。預設情況下,其它應用程式不能通路這些檔案。

為了從檔案中讀取資料,可調用Context.openFileInput()方法并傳遞本地檔案名和檔案路徑給它。該方法傳回一個标準的Java FileInputStream對象。為了寫一個檔案,可調用Context.openFileOutput()并傳遞檔案名和路徑,這個方法也傳回FileOutputStream對象。從另外的應用程式中調用這些方法将不起作用,你隻能通路本地檔案。

如果你有一個靜态檔案需要在編譯時打包進應用程式,你可以儲存該檔案在你項目中res/raw/myDataFile,然後使用Resources.openRawResource (R.raw.myDataFile)打開它。該方法傳回一個InputStream對象,你可以使用它讀取檔案資料。

資料庫Databases

Android API包含對建立和使用SQLite資料庫的支援。每個資料庫都是建立它的應用程式所私有的。

這個SQLiteDatabase對象代表了一個資料庫并包含與之互動的方法-生成查詢和管理資料。為了建立資料庫,調用SQLiteDatabase.create()并同時子類化SQLiteOpenHelper。

作為支援SQLite資料庫的一部分,Android暴露了資料庫管理函數,這讓你可以存儲複雜的資料集合,這些資料被包裝到有用的對象裡。比如,Android為聯系人資訊定義了一個資料類型;它由很多字段組成,其中包括姓,名(字元串),位址資訊和電話号碼(也是字元串),照片(位圖圖像),以及更多其它個人資訊。

Android裝載了sqlite3資料工具,利用這些工具你可以浏覽表内容,運作SQL指令,并執行SQLite資料庫上的其它有用的函數。請查閱檢查資料庫(Examine databases (sqlite3))得知如何運作這個程式。

所有的資料庫,SQLite以及其它,都被儲存在裝置如下目錄裡:

/data/data/package_name/databases.

讨論建立多少表格,包含哪些字段以及它們之間如何連接配接超出了本文的範圍,不過Android并沒有引入任何在标準SQLite概念之外的限制。我們确實推薦包含一個自增長數值的關鍵域,作為一個唯一ID用來快速查找一個記錄。這對于私有資料并不必要,但如果你實作了一個内容提供器,你必須包含這樣一個唯一ID字段。請參見Content Providers文檔以擷取關于該字段的更多資訊,以及NotePadProvider類(在NotePad例子代碼裡)中建立群組裝一個新資料庫的方法。你建立的任何資料庫都将可以通過名字被應用程式中其它的類通路,但不能從應用程式外部通路。

網絡Network

你也可以使用網絡來存放和擷取資料(當它可用時)。要進行網絡操作,可使用如下程式包中的類:

·         java.net.*

·         android.net.*

内容提供器Content Providers

内容提供器用來存放和擷取資料并使這些資料可以被所有的應用程式通路。它們是應用程式之間共享資料的唯一方法;不存在所有Android軟體包都能通路的公共儲存區域。

Android為常見資料類型(音頻,視訊,圖像,個人聯系人資訊,等等)裝載了很多内容提供器。你可以看到在android.provider包裡列舉了一些。你還能查詢這些提供器包含了什麼資料(盡管,對某些提供器,你必須擷取合适的權限來讀取資料)。

如果你想公開你自己的資料,你有兩個選擇:你可以建立你自己的内容提供器(一個ContentProvider子類)或者你可以給已有的提供器添加資料-如果存在一個控制同樣類型資料的内容提供器且你擁有寫的權限。

這篇文檔是一篇關于如何使用内容提供器的簡介。先是一個簡短的基礎知識讨論,然後探究如何查詢一個内容提供器,如何修改内容提供器控制的資料,以及如何建立你自己的内容提供器。

内容提供器的基礎知識Content Provider Basics

内容提供器究竟如何在表層下儲存它的資料依賴于它的設計者。但是所有的内容提供器實作了一個公共的接口來查詢這個提供器和傳回結果-增加,替換,和删除資料也是一樣。

這是一個用戶端直接使用的接口,一般是通過ContentResolver對象。你可以通過getContentResolver()從一個活動或其它應用程式元件的實作裡擷取一個ContentResolver:

ContentResolver cr = getContentResolver();

然後你可以使用這個ContentResolver的方法來和你感興趣的任何内容提供器互動。

當初始化一個查詢時,Android系統識别查詢目标的内容提供器并確定它正在運作。系統執行個體化所有的ContentProvider對象;你從來不需要自己做。事實上,你從來不會直接處理ContentProvider對象。通常,對于每個類型的ContentProvider隻有一個簡單的執行個體。但它能夠和不同應用程式和程序中的多個ContentProvider對象通訊。程序間的互動通過ContentResolver和ContentProvider類處理。

資料模型The data model

内容提供器以資料庫模型上的一個簡單表格形式暴露它們的資料,這裡每一個行是一個記錄,每一列是特别類型和含義的資料。比如,關于個人資訊以及他們的電話号碼可能會以下面的方式展示:

_ID

NUMBER

NUMBER_KEY

LABEL

NAME

TYPE

13

(425) 555 6677

425 555 6677

Kirkland office

Bully Pulpit

TYPE_WORK

44

(212) 555-1234

212 555 1234

NY apartment

Alan Vain

TYPE_HOME

45

(212) 555-6657

212 555 6657

Downtown office

Alan Vain

TYPE_MOBILE

53

201.555.4433

201 555 4433

Love Nest

Rex Cars

TYPE_HOME

每個記錄包含一個數字的_ID字段用來唯一辨別這個表格裡的記錄。IDs可以用來比對相關表格中的記錄-比如,用來在一張表格中查找個人電話号碼并在另外一張表格中查找這個人的照片。

一個查詢傳回一個Cursor對象可在表格和列中移動來讀取每個字段的内容。它有特定的方法來讀取每個資料類型。是以,為了讀取一個字段,你必須了解這個字段包含了什麼資料類型。(後面會更多的讨論查詢結果和遊标Cursor對象)。

唯一資源辨別符URIs

每個内容提供器暴露一個公開的URI(以一個Uri對象包裝)來唯一的辨別它的資料集。一個控制多個資料集(多個表)的内容提供器為每一個資料集暴露一個單獨的URI。所有提供器的URIs以字元串"content://"開始。這個content:形式表明了這個資料正被一個内容提供器控制着。

如果你正準備定義一個内容提供器,為了簡化用戶端代碼和使将來的更新更清楚,最好也為它的URI定義一個常量。Android為這個平台所有的提供器定義了CONTENT_URI常量。比如,比對個人電話号碼的表的URI和包含個人照片的表的URI是:(均由聯系人Contacts内容提供器控制)

android.provider.Contacts.Phones.CONTENT_URI

android.provider.Contacts.Photos.CONTENT_URI

類似的,最近電話呼叫的表和日程表條目的URI如下:Similarly, the URIs for the table of recent phone calls and the table of calendar entries are:

android.provider.CallLog.Calls.CONTENT_URI

android.provider.Calendar.CONTENT_URI

這個URI常量被使用在和這個内容提供器所有的互動中。每個ContentResolver方法采用這個URI作為它的第一個參數。正是它辨別了ContentResolver應該和哪個内容提供器對話以及這個内容提供器的哪張表格是其目标。

查詢一個内容提供器Querying a Content Provider

你需要三方面的資訊來查詢一個内容提供器:

·         用來辨別内容提供器的URI

·         你想擷取的資料字段的名字

·         這些字段的資料類型

如果你想查詢某一條記錄,你同樣需要那條記錄的ID。

生成查詢Making the query

你可以使用ContentResolver.query()方法或者Activity.managedQuery()方法來查詢一個内容提供器。兩種方法使用相同的參數序列,而且都傳回一個Cursor對象。不過,managedQuery()使得活動需要管理這個遊标的生命周期。一個被管理的遊标處理所有的細節,比如當活動暫停時解除安裝自身,而活動重新啟動時重新查詢它自己。你可以讓一個活動開始管理一個尚未被管理的遊标對象,通過如下調用:Activity.startManagingCursor()。

無論query()還是managedQuery(),它們的第一個參數都是内容提供器的URI-CONTENT_URI常量用來辨別某個特定的ContentProvider和資料集(參見前面的URIs)。

為了限制隻對一個記錄進行查詢,你可以在URI後面擴充這個記錄的_ID值-也就是,在URI路徑部分的最後加上比對這個ID的字元串。比如,如果ID是23,那麼URI會是:

content://. . . ./23

有一些輔助方法,特别是ContentUris.withAppendedId()和Uri.withAppendedPath(),使得為URI擴充一個ID變得簡單。是以,比如,如果你想在聯系人資料庫中查找記錄23,你可能需要構造如下的查詢語句:

import android.provider.Contacts.People;

import android.content.ContentUris;

import android.net.Uri;

import android.database.Cursor;

// Use the ContentUris method to produce the base URI for the contact with _ID == 23.

Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);

// Alternatively, use the Uri method to produce the base URI.

// It takes a string rather than an integer.

Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");

// Then query for this specific record:

Cursor cur = managedQuery(myPerson, null, null, null, null);

query()和managedQuery()方法的其它參數限定了更多的查詢細節。如下:

·         應該傳回的資料列的名字。null值傳回所有列。否則隻有列出名字的列被傳回。所有這個平台的内容提供器為它們的列定義了常量。比如,android.provider.Contacts.Phones類對前面說明過的通訊錄中各個列的名字定義了常量ID, NUMBER, NUMBER_KEY, NAME,等等。

·         指明傳回行的過濾器,以一個SQL WHERE語句格式化。null值傳回所有行。(除非這個URI限定隻查詢一個單獨的記錄)。

·         選擇參數

·         傳回行的排列順序,以一個SQL ORDER BY語句格式化(不包含ORDER BY本身)。null值表示以該表格的預設順序傳回,有可能是無序的。

讓我們看一個查詢的例子吧,這個查詢擷取一個聯系人名字和首選電話号碼清單:

import android.provider.Contacts.People;

import android.database.Cursor;

// Form an array specifying which columns to return.

String[] projection = new String[] {

                             People._ID,

                             People._COUNT,

                             People.NAME,

                             People.NUMBER

                          };

// Get the base URI for the People table in the Contacts content provider.

Uri contacts = People.CONTENT_URI;

// Make the query.

Cursor managedCursor = managedQuery(contacts,

                         projection, // Which columns to return

                         null,       // Which rows to return (all rows)

                         null,       // Selection arguments (none)

                         // Put the results in ascending order by name

                         People.NAME + " ASC");

這個查詢從聯系人内容提供器中擷取了資料。它得到名字,首選電話号碼,以及每個聯系人的唯一記錄ID。同時它在每個記錄的_COUNT字段告知傳回的記錄數目。

列名的常量被定義在不同的接口中-_ID和_COUNT定義在BaseColumns裡, NAME在PeopleColumns裡,NUMBER在PhoneColumns裡。Contacts.People類已經實作了這些接口,這就是為什麼上面的代碼執行個體隻需要使用類名就可以引用它們的原因。

查詢的傳回結果What a query returns

一個查詢傳回零個或更多資料庫記錄的集合。列名,預設順序,以及它們的資料類型是特定于每個内容提供器的。但所有提供器都有一個_ID列,包含了每個記錄的唯一ID。另外所有的提供器都可以通過傳回_COUNT列告知記錄數目。它的數值對于所有的行而言都是一樣的。

下面是前述查詢的傳回結果的一個例子:

_ID

_COUNT

NAME

NUMBER

44

3

Alan Vain

212 555 1234

13

3

Bully Pulpit

425 555 6677

53

3

Rex Cars

201 555 4433

擷取到的資料通過一個遊标Cursor對象暴露出來,通過遊标你可以在結果集中前後浏覽。你隻能用這個對象來讀取資料。如果想增加,修改和删除資料,你必須使用一個ContentResolver對象。

讀取查詢所獲資料Reading retrieved data

查詢傳回的遊标對象可以用來通路結果記錄集。如果你通過指定的一個ID來查詢,這個集合将隻有一個值。否則,它可以包含多個數值。(如果沒有比對結果,那還可能是空的。)你可以從表格中的特定字段讀取資料,但你必須知道這個字段的資料類型,因為這個遊标對象對于每種資料類型都有一個單獨的讀取方法-比如getString(), getInt(), 和getFloat()。(不過,對于大多數類型,如果你調用讀取字元串的方法,遊标對象将傳回給你這個資料的字元串表示。)遊标可以讓你按列索引請求列名,或者按列名請求列索引。

下面的代碼片斷示範了如何從前述查詢結果中讀取名字和電話号碼:

import android.provider.Contacts.People;

private void getColumnData(Cursor cur){

    if (cur.moveToFirst()) {

        String name;

        String phoneNumber;

        int nameColumn = cur.getColumnIndex(People.NAME);

        int phoneColumn = cur.getColumnIndex(People.NUMBER);

        String imagePath;

        do {

            // Get the field values

            name = cur.getString(nameColumn);

            phoneNumber = cur.getString(phoneColumn);

            // Do something with the values.

            ...

        } while (cur.moveToNext());

    }

}

如果一個查詢可能傳回二進制資料,比如一個圖像或聲音,這個資料可能直接被輸入到表格或表格條目中也可能是一個content: URI的字元串可用來擷取這個資料,一般而言,較小的資料(例如,20到50K或更小)最可能被直接存放到表格中,可以通過調用Cursor.getBlob()來擷取。它傳回一個位元組數組。

如果這個表格條目是一個content: URI,你不該試圖直接打開和讀取該檔案(會因為權限問題而失敗)。相反,你應該調用ContentResolver.openInputStream()來得到一個InputStream對象,你可以使用它來讀取資料。

修改資料Modifying Data

儲存在内容提供器中的資料可以通過下面的方法修改:

·         增加新的記錄

·         為已有的記錄添加新的資料

·         批量更新已有記錄

·         删除記錄

所有的資料修改操作都通過使用ContentResolver方法來完成。一些内容提供器對寫資料需要一個比讀資料更強的權限限制。如果你沒有一個内容提供器的寫權限,這個ContentResolver方法會失敗。

增加記錄Adding records

想要給一個内容提供器增加一個新的記錄,第一步是在ContentValues對象裡建構一個鍵-值對映射,這裡每個鍵和内容提供器的一個列名比對而值是新記錄中那個列期望的值。然後調用ContentResolver.insert()并傳遞給它提供器的URI和這個ContentValues映射圖。這個方法傳回新記錄的URI全名-也就是,内容提供器的URI加上該新記錄的擴充ID。你可以使用這個URI來查詢并得到這個新記錄上的一個遊标,然後進一步修改這個記錄。下面是一個例子:

import android.provider.Contacts.People;

import android.content.ContentResolver;

import android.content.ContentValues;

ContentValues values = new ContentValues();

// Add Abraham Lincoln to contacts and make him a favorite.

values.put(People.NAME, "Abraham Lincoln");

// 1 = the new contact is added to favorites

// 0 = the new contact is not added to favorites

values.put(People.STARRED, 1);

Uri uri = getContentResolver().insert(People.CONTENT_URI, values);

cuihai 2011-03-03 12:14

增加新值Adding new values

一旦記錄已經存在,你就可以添加新的資訊或修改已有資訊。比如,上例中的下一步就是添加聯系人資訊-如一個電話号碼或一個即時通訊IM或電子郵箱位址-到新的條目中。

在聯系人資料庫中增加一條記錄的最佳途徑是在該記錄URI後擴充表名,然後使用這個修正的URI來添加新的資料值。為此,每個聯系人表暴露一個CONTENT_DIRECTORY常量的表名。下面的代碼繼續之前的例子,為上面剛剛建立的記錄添加一個電話号碼和電子郵件位址:

Uri phoneUri = null;

Uri emailUri = null;

// Add a phone number for Abraham Lincoln. Begin with the URI for

// the new record just returned by insert(); it ends with the _ID

// of the new record, so we don't have to add the ID ourselves.

// Then append the designation for the phone table to this URI,

// and use the resulting URI to insert the phone number.

phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);

values.clear();

values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);

values.put(People.Phones.NUMBER, "1233214567");

getContentResolver().insert(phoneUri, values);

// Now add an email address in the same way.

emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);

values.clear();

// ContactMethods.KIND is used to distinguish different kinds of

// contact methods, such as email, IM, etc.

values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);

values.put(People.ContactMethods.DATA, "[email protected]");

values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);

getContentResolver().insert(emailUri, values);  

你可以通過調用接收位元組流的ContentValues.put()版本來把少量的二進制資料放到一張表格裡去。這對于像小圖示或短小的音頻片斷這樣的資料是可行的。但是,如果你有大量二進制資料需要添加,比如一張相片或一首完整的歌曲,則需要把該資料的content:URI放到表裡然後以該檔案的URI調用ContentResolver.openOutputStream()方法。(這導緻内容提供器把資料儲存在一個檔案裡并且記錄檔案路徑在這個記錄的一個隐藏字段中。)

考慮到這一點,MediaStore内容提供器,這個用來分發圖像,音頻和視訊資料的主内容提供器,利用了一個特殊的約定:用來擷取關于這個二進制資料的元資訊的query()或managedQuery()方法使用的URI,同樣可以被openInputStream()方法用來資料本身。類似的,用來把元資訊放進一個MediaStore記錄裡的insert()方法使用的URI,同樣可以被openOutputStream()方法用來在那裡存放二進制資料。下面的代碼片斷說明了這個約定:

import android.provider.MediaStore.Images.Media;

import android.content.ContentValues;

import java.io.OutputStream;

// Save the name and description of an image in a ContentValues map.

ContentValues values = new ContentValues(3);

values.put(Media.DISPLAY_NAME, "road_trip_1");

values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");

values.put(Media.MIME_TYPE, "image/jpeg");

// Add a new record without the bitmap, but with the values just set.

// insert() returns the URI of the new record.

Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);

// Now get a handle to the file for that record, and save the data into it.

// Here, sourceBitmap is a Bitmap object representing the file to save to the database.

try {

    OutputStream outStream = getContentResolver().openOutputStream(uri);

    sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);

    outStream.close();

} catch (Exception e) {

    Log.e(TAG, "exception while writing image", e);

}

批量更新記錄Batch updating records

要批量更新一組記錄(例如,把所有字段中的"NY"改為"New York"),可以傳以需要改變的列和值參數來調用ContentResolver.update()方法。

删除一個記錄Deleting a record

要删除單個記錄,可以傳以一個特定行的URI參數來調用ContentResolver.delete()方法。

要删除多行記錄,可以傳以需要被删除的記錄類型的URI參數來調用ContentResolver.delete()方法(例如,android.provider.Contacts.People.CONTENT_URI)以及一個SQL WHERE語句來定義哪些行要被删除。(小心:如果你想删除一個通用類型,你得確定包含一個合法的WHERE語句,否則你可能删除比設想的多得多的記錄!)

建立一個内容提供器Creating a Content Provider

要建立一個内容提供器,你必須:

·         建立一個儲存資料的系統。大多數内容提供器使用Android的檔案儲存方法或SQLite資料庫來存放它們的資料,但是你可以用任何你想要的方式來存放資料。Android提供SQLiteOpenHelper類來幫助你建立一個資料庫以及SQLiteDatabase類來管理它。

·         擴充ContentProvider類來提供資料通路接口。

·         在清單manifest檔案中為你的應用程式聲明這個内容提供器(AndroidManifest.xml)。

下面的章節對後來兩項任務有一些标注。

擴充ContentProvider類Extending the ContentProvider class

你可以定義一個ContentProvider子類來暴露你的資料給其它使用符合ContentResolver和遊标Cursor對象約定的應用程式。理論上,這意味需要實作6個ContentProvider類的抽象方法:

query()

insert()

update()

delete()

getType()

onCreate()

query()方法必須傳回一個遊标Cursor對象可以用來周遊請求資料,遊标本身是一個接口,但Android提供了一些現成的Cursor對象給你使用。例如,SQLiteCursor可以用來周遊SQLite資料庫。你可以通過調用任意的SQLiteDatabase類的query()方法得到它。還有一些其它的遊标實作-比如MatrixCursor用來通路沒有存放在資料庫中的資料。-

因為這些内容提供器的方法可以從不同的程序和線程的各個ContentResolver對象中調用,是以它們必須以線程安全的方式來實作。

周到起見,當資料被修改時,你可能還需要調用ContentResolver.notifyChange()方法來通知偵聽者。

除了定義子類以外,你應該還需要采取其它一些步驟來簡化用戶端的工作和讓這個類更容易被通路:

·         定義一個public static final Uri命名為CONTENT_URI。這是你的内容提供器處理的整個content: URI的字元串。你必須為它定義一個唯一的字元串。最佳方案是使用這個内容提供器的全稱(fully qualified)類名(小寫)。是以,例如,一個TransportationProvider類可以定義如下:

public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transporationprovider");

如果這個内容提供器有子表,那麼為每個子表也都定義CONTENT_URI常量。這些URIs應該全部擁有相同的權限(既然這用來識别内容提供器),隻能通過它們的路徑加以區分。例如:

content://com.example.codelab.transporationprovider/train

content://com.example.codelab.transporationprovider/air/domestic

content://com.example.codelab.transporationprovider/air/international

請查閱本文最後部分的Content URI Summary以對content: URIs有一個總體的了解。

·         定義内容提供器傳回給用戶端的列名。如果你正在使用一個底層資料庫,這些列名通常和SQL資料庫列名一緻。同樣還需要定義公共的靜态字元串常量用來指定查詢語句以及其它指令中的列。

確定包含一個名為"_id"(常量_ID)的整數列來傳回記錄的IDs。你應該有這個字段而不管有沒有其它字段(比如URL),這個字段在所有的記錄中是唯一的。如果你在使用SQLite資料庫,這個_ID 字段應該是下面的類型:

INTEGER PRIMARY KEY AUTOINCREMENT

其中AUTOINCREMENT描述符是可選的。但是沒有它,SQLite的ID數值字段會在列中已存在的最大數值的基礎上增加到下一個數字。如果你删除了最後的行,那麼下一個新加的行會和這個删除的行有相同的ID。AUTOINCREMENT可以避免這種情況,它讓SQLite總是增加到下一個最大的值而不管有沒有删除。

·         在文檔中謹慎的描述每個列的資料類型。用戶端需要這些資訊來讀取資料。

·         如果你正在處理一個新的資料類型,你必須定義一個新的MIME類型在你的ContentProvider.getType()實作裡傳回。這個類型部分依賴于送出給getType()的content: URI參數是否對這個請求限制了特定的記錄。有一個MIME類型是給單個記錄用的,另外一個給多記錄用。使用Uri方法來幫助判斷哪個是正在被請求的。下面是每個類型的一般格式:

&sup2; 對于單個記錄:    vnd.android.cursor.item/vnd.yourcompanyname.contenttype

比如,一個火車記錄122的請求,URI如下

content://com.example.transportationprovider/trains/122

可能會傳回這個MIME類型:

vnd.android.cursor.item/vnd.example.rail

&sup2; 對于多個記錄:    vnd.android.cursor.dir/vnd.yourcompanyname.contenttype

比如, 一個所有火車記錄的請求,URI如下

content://com.example.transportationprovider/trains

可能會傳回這個MIME類型:

vnd.android.cursor.dir/vnd.example.rail

·         如果你想暴露過于龐大而無法放在表格裡的位元組資料-比如一個大的位圖檔案-這個給用戶端暴露資料的字段事實上應該包含一個content: URI字元串。這個字段給了用戶端資料通路接口。這個記錄應該有另外的一個字段,名為"_data",列出了這個檔案在裝置上的準确路徑。這個字段不能被用戶端讀取,而要通過ContentResolver。用戶端将在這個包含URI的使用者側字段上調用ContentResolver.openInputStream() 方法。ContentResolver會請求那個記錄的"_data"字段,而且因為它有比用戶端更高的許可權,它應該能夠直接通路那個檔案并傳回給用戶端一個包裝的檔案讀取接口。

自定義内容提供器的實作的一個例子,參見SDK附帶的Notepad例程中的NodePadProvider 類。

聲明内容提供器Declaring the content provider

為了讓Android系統知道你開發的内容提供器,可以用在應用程式的AndroidManifest.xml檔案中以<provider>元素聲明它。未經聲明的内容提供器對Android系統不可見。

名字屬性是ContentProvider子類的全稱名(fully qualified name)。權限屬性是辨別提供器的content: URI的權限認證部分。例如如果ContentProvider子類是AutoInfoProvider,那麼<provider>元素可能如下:

<provider name="com.example.autos.AutoInfoProvider"

          authorities="com.example.autos.autoinfoprovider"

          . . . />

</provider>

請注意到這個權限屬性忽略了content: URI的路徑部分。例如,如果AutoInfoProvider為各種不同的汽車或制造商控制着各個子表,Note that the authorities attribute omits the path part of a content: URI. For example, if AutoInfoProvider controlled subtables for different types of autos or different manufacturers,

content://com.example.autos.autoinfoprovider/honda

content://com.example.autos.autoinfoprovider/gm/compact

content://com.example.autos.autoinfoprovider/gm/suv

這些路徑将不會在manifest裡聲明。權限是用來識别提供器的,而不是路徑;你的提供器能以任何你選擇的方式來解釋URI中的路徑部分。

其它<provider>屬性可以設定資料讀寫許可,提供可以顯示給使用者的圖示和文本,啟用或禁用這個提供器,等等。如果資料不需要在多個内容提供器的運作版本中同步則可以把multiprocess屬性設定成"true"。這使得在每個客戶程序中都有一個提供器執行個體被建立,而無需執行IPC調用。

Content URI 總結

這裡回顧一下content URI的重要内容:

A.      标準字首表明這個資料被一個内容提供器所控制。它不會被修改。

B.      URI的權限部分;它辨別這個内容提供器。對于第三方應用程式,這應該是一個全稱類名(小寫)以確定唯一性。權限在<provider>元素的權限屬性中進行聲明:

<provider name=".TransportationProvider"

          authorities="com.example.transportationprovider"

          . . . >

C.      用來判斷請求資料類型的路徑。這可以是0或多個段長。如果内容提供器隻暴露了一種資料類型(比如,隻有火車),這個分段可以沒有。如果提供器暴露若幹類型,包括子類型,那它可以是多個分段長-例如,提供"land/bus", "land/train", "sea/ship", 和"sea/submarine"這4個可能的值。

D.      被請求的特定記錄的ID,如果有的話。這是被請求記錄的_ID數值。如果這個請求不局限于單個記錄,這個分段和尾部的斜線會被忽略:

content://com.example.transportationprovider/trains

清單檔案The AndroidManifest.xml File

每個應用程式都有一個AndroidManifest.xml檔案(一定是這個名字)在它的根目錄裡。這個清單檔案給Android系統提供了關于這個應用程式的基本資訊,系統在能運作任何程式代碼之前必須知道這些資訊。AndroidManifest.xml主要包含以下功能:

    * 命名應用程式的Java包,這個包名用來唯一辨別應用程式;

    * 描述應用程式的元件-活動,服務,廣播接收者,以及組成應用程式的内容提供器;對實作每個元件和公布其能力(比如,能處理哪些意圖消息)的類進行命名。這些聲明使得Android系統了解這些元件以及在什麼條件下可以被啟動;

    * 決定應用程式元件運作在哪個程序裡面;

    * 聲明應用程式所必須具備的權限,用以通路受保護的部分API,以及和其它應用程式互動;

    * 聲明應用程式其他的必備權限,用以元件之間的互動;

    * 列舉測試裝置Instrumentation類,用來提供應用程式運作時所需的環境配置及其他資訊,這些聲明隻在程式開發和測試階段存在,釋出前将被删除;

    * 聲明應用程式所要求的Android API的最低版本級别;

    * 列舉application所需要連結的庫;

清單檔案結構Structure of the Manifest File

下面的圖表顯示了清單檔案的基本結構以及它能包含的所有元素。每個元素,和它所有的屬性,在一個單獨的檔案中完整描述。要檢視任何元素的細節資訊,可在圖表下方的以字元序排列的元素清單中點選其元素名稱。

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

<manifest>

    <uses-permission />

    <permission />

    <permission-tree />

    <permission-group />

    <instrumentation />

    <uses-sdk />

    <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 />

            <meta-data />

        </provider>

        <uses-library />

        <uses-configuration />

    </application>

</manifest>

所有清單檔案中可能出現的元素按字元序排列如下。隻有這些元素是合法的,你不能添加自己的元素或屬性:

<action>

<activity>

<activity-alias>

<application>

<category>

<data>

<grant-uri-permission>

<instrumentation>

<intent-filter>

<manifest>

<meta-data>

<permission>

<permission-group>

<permission-tree>

<provider>

<receiver>

<service>

<uses-configuration>

<uses-library>

<uses-permission>

<uses-sdk>

檔案約定File Conventions

下面是一些清單檔案中适用于所有元素和屬性的約定和規則:

元素Elements:

在所有的元素中隻有<manifest>和<application>是必需的,且隻能出現一次。很多其他元素可以出現多次甚或一次都沒有-盡管如果清單檔案想要完成一些有意義的工作,必須設定至少其中的一些。如果一個元素包含點什麼,那就是包含其他元素。所有的值必須通過屬性來設定,而不是元素中的字元資料。同一級别的元素一般是沒有順序的。比如,<activity>, <provider>, 和<service>元素可以以任意順序混合使用。(<activity-alias>元素是個例外:它必須跟在該别名所指的<activity>後面。)

屬性Attributes:

正規意義上,所有的屬性都是可選的,但實際上有些屬性是必須為一個元素指定來完成其目标。把這篇文檔當作一個指南。對于那些真正可選的屬性,即使不存在一個規格,也會有預設的數值或狀态。

除了根元素<manifest>的一些屬性,所有其他元素屬性的名字都是以android:作為字首的-比如,android:alwaysRetainTaskState。因為這個字首是通用的,這篇文檔提及屬性名稱時一般會忽略它。

聲明類名Declaring class names:

很多對應于Java對象的元素,包括應用程式自己(<application>元素)以及它的基礎元件-活動(<activity>),服務(<service>),廣播接收器(<receiver>),以及内容提供器(<provider>)。

如果你定義一個子類,就像你将經常為元件類(Activity, Service, BroadcastReceiver, 和ContentProvider)所做的那樣,這個子類通過一個名字屬性來聲明。這個名字必須包含完整的包名稱。比如,一個服務Service子類可能會聲明如下:

<manifest . . . >

    <application . . . >

        <service android:name="com.example.project.SecretService" . . . >

            . . .

        </service>

        . . .

    </application>

</manifest>

不過,作為類名的簡寫,如果這個字元串的第一個字元是一個點号“.”,那麼這個字元串将被擴充到應用程式包名的後面(正如<manifest>元素的package屬性所指明的那樣)。下面這個指派和上面效果一樣:

<manifest package="com.example.project" . . . >

    <application . . . >

        <service android:name=".SecretService" . . . >

            . . .

        </service>

        . . .

    </application>

</manifest>

當啟動一個元件時,Android建立了一個命名子類的執行個體。如果沒有指定一個子類,它建立基類的一個執行個體。

多數值項Multiple values:

如果某個元素有超過一個數值,這個元素幾乎總是需要被重複聲明,而不能将多個數值項列舉在一個屬性中。比如,一個意圖過濾器可以列舉多個動作:

<intent-filter . . . >

    <action android:name="android.intent.action.EDIT" />

    <action android:name="android.intent.action.INSERT" />

    <action android:name="android.intent.action.DELETE" />

    . . .

</intent-filter>

資源項Resource values:

一些屬性有能顯示給使用者的數值-比如,活動(activity)的一個标簽和圖示。這些屬性的值應該被本地化,從一個資源或主題中設定。當需要引用某個資源時,采用如下的表述格式:

@[package:]type:name

這裡package名稱可以被忽略,要是資源和應用程式在同一個包裡的話;type是資源的類型-如"string"或"drawable"-而且name是用來辨別特定資源的名字。例如

<activity android:icon="@drawable/smallPic" . . . >

從主題擷取的資料以類似的方式表述,不過以'?'而不是'@'開頭。

?[package:]type:name

字元串值String values:

如果屬性值是一個字元串,則必須使用雙反斜杠('\\')來表示escape('\')字元;第一個反斜杠起轉義字元的作用)。比如,'\\n'表示換行或'\\uxxxx'表示一個Unicode字元。

檔案特性File Features

下面的章節描述了一些Android特性如何被映射到清單(manifest)檔案中。

意圖過濾器Intent Filters

應用程式的核心元件(活動,服務和廣播接收器)通過意圖被激活。意圖是描述期望動作的資訊包(一個Intent對象)-包括要操作的資料,執行該動作的元件類别,以及其他有關指令。Android尋找一個合适的元件來響應這個意圖,如果需要會啟動這個元件一個新的執行個體,并傳遞給這個意圖對象。

元件通過意圖過濾器(intent filters)通告它們所具備的能力-能響應的意圖類型。由于Android系統在啟動一個元件前必須知道該元件能夠處理哪些意圖,那麼意圖過濾器需要在manifest中以<intent-filter>元素指定。一個元件可以擁有多個過濾器,每一個描述不同的能力。

一個顯式命名目标元件的意圖将會激活那個元件;過濾器不起作用。但是一個沒有指定目标的意圖隻在它能夠通過元件過濾器任一過濾器時才能激活該元件。

請檢視關于意圖和意圖過濾器的文檔以擷取更多資訊:Intents and Intent Filters.

圖示和标簽Icons and Labels

許多元素有圖示(icon)和标簽(label)屬性。其中一些還有一個描述(description)屬性,可以用更長的解釋性文字呈現給使用者。比如,<permission>元素有所有這三個屬性,是以當使用者被詢問是否授予一個應用程式請求的權限許可時,一個代表權限的圖示,權限的名稱和必定伴有的權限描述會全部被顯示給使用者。

所有的情況中,設定在一個包含元素裡的圖示和标簽會成為該容器所有子元素的預設設定。這樣,在<application>元素中設定的圖示和标簽就是該應用程式每個元件的預設圖示和标簽。類似的,為一個元件設定的圖示和标簽-比如,一個<activity>元素-是這個元件<intent-filter>元素的預設值。如果一個<application>元素設定了一個圖示,但活動及其意圖過濾器沒有,那麼程式标簽被當作活動和意圖過濾器的标簽。

當呈現給使用者的元件實作一個意圖過濾器公告的函數時,為這個過濾器設定的圖示和标簽将被用來代表這個元件。比如,一個設定了"android.intent.action.MAIN"和"android.intent.category.LAUNCHER"的過濾器公告了一個活動來初始化應用程式-也就是,會被顯示在應用程式啟動器中。是以設定在過濾器中的圖示和标簽也就是顯示在啟動器裡的那些圖示和标簽。

許可Permissions

一個許可(permission)是代碼對裝置上資料的通路限制。這個限制被引入來保護可能會被誤用而曲解或破壞使用者體驗的關鍵資料和代碼。

每個許可被一個唯一的标簽所辨別。這個标簽常常指出了受限的動作。例如,下面是一些Android定義的許可:

android.permission.CALL_EMERGENCY_NUMBERS

android.permission.READ_OWNER_DATA

android.permission.SET_WALLPAPER

android.permission.DEVICE_POWER

一個功能(feature)最多隻能被一個權限許可保護。

如果一個應用程式需要通路一個需要特定權限的功能,它必須在manifest檔案中使用<uses-permission>元素來聲明這一點。這樣,當應用程式安裝到裝置上時,安裝器可以通過檢查簽署應用程式認證的機構來決定是否授予請求的權限,在某些情況下,會詢問使用者。如果權限已被授予,那應用程式就能夠通路受保護的功能特性。如果沒有,通路将失敗,但不會給使用者任何通知。

應用程式還可以通過權限許可來保護它自己的元件(活動,服務,廣播接收器,和内容提供器)。它可以利用Android已經定義(列在android.Manifest.permission裡)或其他應用程式已聲明的權限許可。或者定義自己的許可。一個新的許可通過<permission>元素聲明。比如,一個活動可以用下面的方式保護:

<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>元素裡。為了應用程式的其他元件可以啟動這個受保護的活動,必須請求它的使用(use),即使這個保護是應用程式自己引入的。

如果,就在這個例子裡,這個permission屬性被設定為在其他地方聲明的權限許可(例如android.permission.CALL_EMERGENCY_NUMBERS,它将不需要再次聲明它,但是,它仍然需要通過<uses-permission>來請求它的使用。

這個<permission-tree>元素為一組想在代碼中定義的權限許可聲明了一個命名空間。而<permission-group>元素為一系列許可定義了一個标簽(用<permission>元素定義在manifest中的以及其他地方聲明的)。它僅僅影響這些權限許可在顯示給使用者時如何分組。<permission-group>元素并不指明哪個權限屬于這個分組;它隻是給這個組命名。一個權限許可通過給<permission>元素的permissionGroup屬性賦予這個組名來放置到這個權限組中。

庫Libraries

每個應用程式都連結到預設的Android庫,這個庫包含了基礎應用程式開發包(實作了基礎類如活動,服務,意圖,視圖,按鈕,應用程式,内容提供器,等等)

然而,一些包處于它們自己的庫中。如果你的應用程式使用了其他開發包中的代碼,它必須顯式的請求連結到它們。這個manifest必須包含一個單獨的<uses-library>元素來命名每一個庫。

什麼是 Android?

Android 是一個專門針對移動裝置的軟體集,它包括一個作業系統,中間件和一些重要的應用程式。Beta 版的 Android SDK 提供了在Android 平台上使用JaVa 語言進行Android 應用開發必須的工具和API 接口。

特性

· 應用程式架構 支援元件的重用與替換

· Dalvik 虛拟機專為移動裝置優化

· 內建的浏覽器 基于開源的WebKit 引擎

· 優化的圖形庫 包括定制的2D 圖形庫,3D 圖形庫基于OpenGL ES 1.0 (硬體加速可選)

· SQLite 用作結構化的資料存儲

· 多媒體支援 包括常見的音頻、視訊和靜态圖像格式(如 MPEG4, H.264, MP3,AAC, AMR, JPG, PNG, GIF)

· GSM 電話技術(依賴于硬體)

· 藍牙Bluetooth, EDGE, 3G, 和 WiFi (依賴于硬體)

· 照相機,GPS,指南針,和加速度計(accelerometer) (依賴于硬體)

· 豐富的開發環境 包括裝置模拟器,調試工具,記憶體及性能分析圖表,和Eclipse

內建開發環境插件

應用程式

Android 會同一系列核心應用程式包一起釋出,該應用程式包包括email 用戶端,SMS短消息程式,月曆,地圖,浏覽器,聯系人管理程式等。所有的應用程式都是使用JAVA語言編寫的。

應用程式架構

開發人員也可以完全通路核心應用程式所使用的API 架構。該應用程式的架構設計簡化了元件的重用;任何一個應用程式都可以釋出它的功能塊并且任何其它的應用程式都可以使用其所釋出的功能塊(不過得遵循架構的安全性限制)。同樣,該應用程式重用機制也使使用者可以友善的替換程式元件。

隐藏在每個應用後面的是一系列的服務和系統, 其中包括;

· 豐富而又可擴充的視圖(Views),可以用來建構應用程式, 它包括清單(lists),

網格(grids),文本框(text boxes),按鈕(buttons), 甚至可嵌入的web

浏覽器。

· 内容提供器(Content Providers)使得應用程式可以通路另一個應用程式的資料(如聯系人資料庫), 或者共享它們自己的資料

· 資料總管(Resource Manager)提供 非代碼資源的通路,如本地字元串,圖形,和布局檔案( layout files )。

· 通知管理器 (Notification Manager) 使得應用程式可以在狀态欄中顯示自定義的提示資訊。

· 活動管理器( Activity Manager) 用來管理應用程式生命周期并提供常用的導

航回退功能。

程式庫

Android 包含一些C/C++庫,這些庫能被Android 系統中不同的元件使用。它們通過Android 應用程式架構為開發者提供服務。以下是一些核心庫:

· 系統 C 庫 - 一個從 BSD 繼承來的标準 C 系統函數庫( libc ), 它是專門為基于 embedded linux 的裝置定制的。

· 媒體庫 - 基于 PacketVideo OpenCORE;該庫支援多種常用的音頻、視訊格式回放和錄制,同時支援靜态圖像檔案。編碼格式包括MPEG4, H.264, MP3, AAC,AMR, JPG, PNG 。

· Surface Manager - 對顯示子系統的管理,并且為多個應用程式提 供了2D 和

3D 圖層的無縫融合。

· LibWebCore - 一個最新的web 浏覽器引擎用,支援Android 浏覽器和一個可嵌入的web 視圖。

· SGL - 底層的2D 圖形引擎

· 3D libraries - 基于OpenGL ES 1.0 APIs 實作;該庫可以使用硬體 3D 加速(如果可用)或者使用高度優化的3D 軟加速。

· FreeType -位圖(bitmap)和矢量(vector)字型顯示。

· SQLite - 一個對于所有應用程式可用,功能強勁的輕型關系型資料庫引擎。

Android 運作庫

Android 包括了一個核心庫,該核心庫提供了JAVA 程式設計語言核心庫的大多數功能。每一個Android 應用程式都在它自己的程序中運作,都擁有一個獨立的Dalvik 虛拟 機執行個體。Dalvik 被設計成一個裝置可以同時高效地運作多個虛拟系統。 Dalvik 虛拟機執行(.dex)的Dalvik 可執行檔案,該格式檔案針對小記憶體使用做了優化。同時虛拟機是基于寄存器的,所有的類都經由JAVA 編譯器編譯,然後通過SDK 中 的 "dx" 工具轉化成.dex 格式由虛拟機執行。

Dalvik 虛拟機依賴于linux 核心的一些功能,比如線程機制和底層記憶體管理機制。

Linux 核心

Android 的核心系統服務依賴于 Linux 2.6 核心,如安全性,記憶體管理,程序管理,網絡協定棧和驅動模型。 Linux 核心也同時作為硬體和軟體棧之間的抽象層。

建立應用程式簽名

所有應用程式在安裝它們之前都必須被簽名。ADT 插件和ant 為基礎的開發工具都支援這一要求,它們通過帶一個調試KEY 瘿apk 檔案來簽發編譯。為了做到這一點,編譯工具使用包括在JDK 的Keytool 去創造一個keystore 和帶着一個已知的别名和密碼一個key 帶着一個已知的别名和密碼。如需詳細資訊,請查閱 簽名你的應用程式.

為了支援簽簽名,你應該首先确認Keytool 對于SDK 的編譯工具是有效的。在大多數情況下,你可以告訴的SDK 編譯工具如何找到Keytool,通過設定你的J AVA_HOME環境變量設定和一個合适的JDK。另外,您也可以添加keytool 的JDK 版本到您的PATH變量如果你正在開發Linux 的一個版本,那原本使用的是GNU 的JAVA 編譯嗿,請確定該系統正在使用的Keytool 的JDK 版本,而不是使用gcj,如果keyt ool 已經在您的路徑,它可能是指向在一個符号連結是/usr/bin/keytool 。在這種情況下,檢查符号連結的目标,以確定它指向正确的Keytool.如果您使用的ant 編譯你的.apk 檔案ض而不是ADT,你必須重新産生你的build.xml檔案。為了做到這一點,請執行下列步驟:

1. 在您的android 應用程式工程目錄中,找到并删除目前的build.xml 檔案

2. 2. 運作activitycreator ,直接輸出到包含您的應用程式項目的檔案夾

3. - exec activitycreator

--out your.activity.YourActivity

運作這種方式ضactivityCreator不會擦出或建立新的Java 檔案(或manifest 檔案ض,對于那些已經存在的activity 和package。重要的是,package 和activity 是真實存在的。該工具建立一個新的build.xml 檔案,以及一個新的目錄稱libs"中,這個目錄将放置第三方jar 檔案,這是你就能夠使用ant 腳本自動處理。

移植您的應用程式

更新過您的SDK 以後 ,您可能會遇到破損的代碼,由于架構和API 的變化。您需要更新您的代碼以比對變化的Andriod 的API。

一種方法是用Eclipse 打開您的項目和檢視你的應用程式中ADT 的标記錯誤。從這裡,你可以查找對應的變勢 變化預覽 and API 變化報告.

如果您更新您的代碼有其他麻煩,請通路 android 小組讨論 尋求幫助或者求助于其他android 開發人員.如果已經修改了一個ApiDemos 應用程式,并希望移植到新的SDK 的,請注意您将需要解除安裝模拟器中預裝的ApiDemos 版本。了解更多資訊,或(運作或安裝A piDemos)遇到一個重新安裝"的錯誤,見疑難解答論頿 因為簽名錯誤,我不能在我的IDE 中安裝ApiDemos 應用程式 來獲得解決這個問題的資訊。

為程式附加調試器

這一節我們介紹如何在螢幕上顯示調試資訊(例如CPU 使用率),以及如何将IDE 和模拟器上運作的程式關聯起來。

使用eclipse 插件可以自動的生成調試器。但你也可以通過配置IDES 來監聽調試端口得到調試資訊。

1. 啟動Dalvik Debug Monitor Server (DDMS) 工具 ,它在IDE 和模拟器之間扮演着端口轉換服務的角色。?

2. 設定模拟器調試配置選項。例如,等到調試資訊被加載後才啟動應用程式。注意,很多調試選項無需DDMS 也可以使用,例如模拟器上顯示CPU 的使用效率,或者螢幕的重新整理頻率。

3. 配置IDE,使得調試時IDE 與8700 端口關聯 .how to set up Eclipse to debug your project. 包含以下資訊。

配置IDE 附加調試端口

DDMS 将為每一個虛拟機配置設定一個特殊的調試端口,這個端口在模拟器上可以找到。你必須将你的IDE 與此端口(虛拟機上資訊欄中有列出這些端口)關聯或者是預設的端口8700。這樣可以使IDE 連接配接到模拟器上程式清單中的任一個程式。你的IDE 需要能夠關聯模拟器上正在運作的程式,顯示它的線程,并允許你挂起它,檢查它的狀态,設定斷點。如果你在開發設定面闆選擇了“等待調試”,應用程式将等到Eclipse 連接配接後才運作,是以你需要在連接配接之前設定斷點。

修改正在調試的程式,或者在目前程式運作時選擇“等待調試”将引起系統殺死這個應用程式。如果你的程式處于一種壞的狀态,你可以使用方式殺死它,方法很簡單,隻需要設定和鈎掉複選框。

應用程式簽名

Android 系統要求所有的程式經過數字簽名才能安裝,如果沒有可用的數字簽名,系統将不許安裝運作此程式。不管是模拟器還是真實裝置,隻要是android 系統,這都适用。鑒于此原因,在裝置或者是模拟器上運作調試程式之前,你必須為你的應用程式設定數字簽名。

了解android 程式簽名的重要幾點::

· 所有的程式都必須簽名,沒有被簽名的程式,系統将不能安裝。

· 你可使用自簽署證書簽署你的應用程式,必須是無憑證授權是的。

· 系統僅僅會在安裝的時候測試簽名證書的有效期,如果應用程式的簽名是在安裝之後才到期,那麼應用程式仍然可以正常啟用。

· 你可以使用标準工具-Keytool and Jarsigner-生成密鑰,來簽名應用程式的.apk

檔案。

Android SDK 工具可以幫助你在調試時給應用程式簽名。ADT 插件和Ant 編譯工具都提供了兩種簽名模式-debug 模式和release 模式

· debug 模式下,編譯工具使用JDK 中的通用程式Keytool 通過已知方法和密碼建立秘鎖和密鑰。每次編譯的時候,工具使用debug 密鑰簽名應用程式的.apk 檔案。因為密碼是已知的,工具不需要在每次編譯的時候提示你輸入密鎖和密鑰。

· 當你的應用程式已經準備release 了,你可以在release 模式下編譯。release

模式下,工具編譯時不會将.apk 檔案簽名。你需要用Keytool 生成密鑰和密鎖,

再用JDK 中的Jarsigner 工具給.apk 檔案簽名。

簽名基本設定

為了支援生成密鎖和密鑰,你首先要确定Keytool 在SDK 編譯工具中是有效的。在很多情況下,你可以設定JAVA_HOME 環境變量,告訴SDK 如何找到Keytool,或者你可以在PATH 變量中添加Keytool 的JDK 版本。

如果你是在linux 版本中開發,原本是來自Java Gnu 編譯器,請确定系統用的是Keytool版本的JDK,而不是gcj 版本的。如果Keytool 已經在PATH 中,它将指向符号連接配接/usr/bin/keytool。這種情況下,核實符号連接配接的目标是指向JDK 下的Keytool

Eclipse/ADT 中的簽名

如果你是在Eclipse 下開發,并已經按照上面所介紹的安裝了Keytool,預設情況下是可以在debug 模式下簽名的。當你運作調試程式的時候ADK 将給.apk 檔案簽名,并安裝到模拟器上。這部分不需要特殊的動作,ADT 已經進入Keytool

在release 模式下編譯程式,在Package 面版上按project 右鍵,選擇Android

Tools>Export Application Package.或者你可以點選Manifest Editor, overview 頁面上的“Exporting the unsigned .apk”連接配接 ,導出未簽名apk 檔案。儲存.apk 檔案之後,用Jarsigner 及你自己的密鑰給apk 檔案簽名,如果沒有密鑰, 你可以用Keystore 建立密鑰和密鎖。如果已經有一個密鑰了,如公共密鑰,就可以給.apk 檔案簽名了。

Ant 簽名

如果用Ant 編譯.apk 檔案,假設你使用最新版的SDK 中包含的activitycreator 工具生成build.xml 檔案,預設情況下可以使用debug 簽名模式。當你運作Ant 對build.xml編譯程式,build 腳本将生成密鎖和密鑰并簽名.apk 檔案。這部分不需要做其它特殊的動作。

release 模式下編譯程式,你需要做的是在Ant 指令中指定編譯目标“release”。例如,如果是在bulid.xml 所在目錄下運作ant,輸入以下指令:

ant release build 腳本編譯程式時并沒有簽名。編譯完.apk 檔案後,你需要用Jarsigner 和你自己的密鑰給.apk 檔案簽名。如果沒有密鑰,你可以用Keystore 建立密鑰和密鎖。如果已經有一個密鑰了,如公共密鑰,你就可以給.apk 檔案簽名了。

調試證書期限

自簽名證書用于程式的debug 模式下(預設情況下是Eclipse/ADT 和Ant builds),自它建立時間起有一年的期限。

當證書到期時,将會有編譯錯誤。 And 下錯誤顯示如下:

debug:

[echo] Packaging bin/samples-debug.apk, and signing it with a debug key...

[exec] Debug Certificate expired on 8/4/08 3:43 PM

在Eclipse/ADT 下,你可以看到類似的錯誤。

解決這個問題的簡單方法是删除debug.keystore 檔案。Linux/Mac OSX 下這個檔案儲存在~/.android 下,windows XP 下,檔案儲存在 C:\Documents and

Settings\<user>\Local Settings\Application Data\Android。windows

Vista 下檔案儲存在 C:\Users\<user>\AppData\Local\Android。

下次編譯時,編譯工具将生成新的密鎖和密鑰。

使用ApiDemo 示例應用程式

Android SDK 包含了一套示例程式,他們驗證了許多功能以及API 的用法。ApiDemos軟體包被提前安裝在模拟器中,是以你可以啟動模拟器,在主畫面的應用程式抽屜裡打開它。你也可以在<SDK>/samples/ApiDemos 中找到源碼,可用看看它,學習Demo 的實作方法。

如果你願意,你還可以将ApiDemo 的示例程式作為一個工程加載進來,修改并在模拟器上運作。然而,在這之前你首先要解除安裝之前已經安裝的ApiDemos。如果你沒有移除之前安裝的版本而直接在開發環境中運作或修改ApiDemos,将會有安裝錯誤。

關于如何解除安裝和重裝ApiDemo,可以參考I can't install ApiDemos apps in my IDE because of a signing error.這樣你就可以在你的開發環境中工作了。

調試

Android 有相當廣泛的一套工具幫助你調試你的應用程式:

· DDMS -一個生動的程式,它支援端口轉換(是以你可以在IDE 中給你的代碼下端點),支援抓取模拟器螢幕,線程和堆棧資訊,以及許多其他功能。你還可以運作logcat 重新獲得Log 資訊。點選此連接配接檢視更多資訊。

· logcat- 轉儲系統資訊,這些資訊包括,模拟器抛出錯誤時堆棧的運作過程以及日志資訊。運作logcat,點選此連接配接。

· ...

· I/MemoryDealer( 763): MemoryDealer (this=0x54bda0):

Creating 2621440 bytes heap at 0x438db000

· I/Logger( 1858): getView() requesting item number 0

· I/Logger( 1858): getView() requesting item number 1

· I/Logger( 1858): getView() requesting item number 2

· D/ActivityManager( 763): Stopping: HistoryRecord{409dbb20

com.android.home.AllApps}

...

· Android Log - 輸出模拟器上log 檔案資訊日志類。如果你在DDMS 上運作了

logcat,你可以實時閱讀這些資訊。在你的代碼中添加logging 方法的調用。使

用log 類,你可以根據你想獲得資訊的重要程度不同調用

Log.v(verbose),Log.d()(debug),Log.i()(information),Log.w()(warning)或者

Log.e(error).來分派log 資訊Log.i("MyActivity", "MyClass.getView()

— Requesting item number " + position)

你可以用logcat 閱讀這些資訊。

· Traceview - Android 可以将函數的調用情況以及調用時間儲存到一個log 檔案中,你可以用圖形閱讀器Traceview 檢視詳細内容。更多資訊檢視這個連接配接下的主題

· Eclipse plugin -Eclipse 插件整合了相當數量的工具(ADB,DDMS,logcat

output, 以及其它功能),點選此連接配接檢視更多資訊。

· Debug and Test Device Settings -Android 揭示了很多有用的設定,例如CPU使用率和幀速率,參看下面的 Debug and Test Settings on the Emulator

Also, see the Troubleshooting section of the doc to figure out why your application isn't appearing on the emulator, or why it's not starting.

此外,參看疑難解答這一節文檔,以找出您的應用程式為什麼沒有出現在模拟器上,或為什麼不開始。

裝置上的調試和測試設定

Android 允許你設定多個設定以便你測試和調試程式。獲得模拟器的開發設定,可以選擇Dev Tools>Development Settings。 按照以下選項将打開開發設定頁(或其中之一):

· Debug app 選擇要被調試的程式,你不需要設定這個來關聯調試器,但是這個變量有兩個作用:

o 防止Android 在調試的斷點處長時間停留時抛出錯誤。

o 允許你選擇Wait for Debugger 選項來暫停程式啟動,直到調試器被關聯

上(如下介紹)

· Wait for debugger 阻塞程式加載直到關聯上調試器。這樣你可以在

onCreate()中設定端點,這在調試Activity 的啟動程序時很重要。當你改變這個

選項,任何目前運作的程式執行個體将被殺死。為選中此框,你必須如上面介紹的選

擇一個調試程式。這和在代碼中添加waitForDebugger()是一樣的。

· Immediately destroy activities 告訴系統隻要activity 停止了就銷毀它。 (猶

如 Android 必須回收記憶體). 這個在測試 onSaveInstanceState(Bundle) /

onCreate(android.os.Bundle)代碼路徑 時非常有用, 否則将難以生效.選擇這個

選項可能帶來很多問題,因為他們沒有儲存程式的狀态。

· Show screen updates 選中這個選項時,螢幕上任何被重繪的矩形區域會閃現粉紅色。這對于發現螢幕不必要的繪圖很有用。

· Show CPU usage 在螢幕頂端顯示一個CPU 進度,顯示CPU 的使用情況。上面紅色欄顯示總的CPU 使用率,下方綠色欄顯示目前畫面的CPU 使用時間。注意:一旦打開次功能就不能關掉,除非重新啟動模拟器。

· Show background 沒有activity 螢幕顯示時顯示背景面闆,這個通常在調試的時候才會發生。

模拟器重起後這些設定仍被記憶。

頂端調試技巧

快速堆棧轉儲從模拟器上獲得堆轉儲,你可以登入adb shell,用"ps"指令找到你想要的程序,然後用"kill-3",堆棧使用軌迹将顯示在log 檔案中。

在模拟器螢幕上顯示有用資訊裝置可以顯示一些有用資訊,例如CPU 使用率,以及高亮顯示重繪區域。可以在開發設定視窗打開和關閉這些功能。Setting debug and test configurations onthe emulator.中有詳細介紹。

你可以通過Dalvik Debug Monitor Service 工具獲得轉儲狀态資訊。請參考adb

中介紹的dumpsys and dumpstate獲得模拟器中應用程式狀态資訊(dumpsys)

你可以通過Dalvik Debug Monitor Service 工具獲得dumpsys 資訊。參考adb

中介紹的dumpsys and dumpstate 。

獲得無線連接配接資訊你可以通過Dalvik Debug Monitor Service 工具獲得無線連接配接資訊。在Device菜單中選擇"Dump radio state"記錄跟蹤資料你可以在activity 中通過調用android.os.Debug.startMethodTracing()來記錄函數的調用以及其它跟蹤資料。詳細的參考Running the Traceview DebuggingProgram 。

記錄無線資料預設情況下系統不記錄無線資料(資料很多)。然而,你可以用下面的指令記錄無線資料:adb shell logcat -b radio

運作adb Android 有adb 工具,他提供了許多功能,包括移動和同步檔案到模拟器上,改變端口,在模拟器上運作 UNIX shell。獲得模拟器螢幕截圖Dalvik Debug Monitor Server (DDMS)可以抓取模拟器螢幕截圖。使用調試幫助類Android 為友善使用提供了調試幫助類,例如util.Log 和Debug

編譯安裝Anroid 應用程式

Android 要求專門的編譯工具可以正确的編譯資源檔案和應用程式的其他部分,是以,你必須為你的應用程式建立一個專門的編譯環境。專門Android 編譯器編譯步驟包括,編譯XML 和其他資源檔案并建立合适的輸出格式。編譯好的Android 應用程式是一個.apk 壓縮檔案,它含有.dex 檔案,資源檔案,原data檔案,以及其他檔案。你可以通過scratch,或者源檔案構造一個合适的Android 工程。Android 目前不支援的在本地代碼上開發第三方應用程式。比較推薦的Andriod 應用程式開發方法是use Eclipse with the Android plugin,它支援編譯,運作,調試Android 應用程式。如果你還有其他IDE,Android provides tools for other IDEs 可以編譯運作Android 應用程式,但是他們不是很完整。

移出一個Android 應用程式

移出一個安裝在模拟器上的應用程式,你需要執行adbrun adb 删除.apk 檔案。.apk 檔案是在安裝的時候發送到模拟器上的。使用adb shell 進入裝置的shell,切換到data/app目錄下,用rm 指令删除apk 檔案 :rm your_app.apk。用法在連接配接中介紹。

Eclipse 技巧

在Eclipse 上執行任意java 代碼

在Eclipse 上,當程式停在斷點處時你可以執行任意代碼。例如,在一個含有“zip”字元串參數的函數中,你可以獲得軟體包資訊,調用類方法。你也可以執行任意靜态方法:如,輸入 android.os.Debug.startMethodTracing() ,啟動 dmTrace。

打開代碼執行視窗,主菜單中選擇Window>Show View>Display,打開顯示視窗,一個簡單的文本編輯器。輸入你的代碼,高亮顯示文字,單擊'J'圖示(或者CTRL + SHIFT+ D)運作代碼。代碼在被選線程的上下文中運作, 而這個線程必須是停在斷點處或者單步停止點。(如果你手動挂去線程,你必須單步執行。線程停在Object.wait()是沒有用的)。

如果你目前是停在斷點,你可以簡單的按下(CTRL + SHIFT + D)高亮并執行一段代碼。

你可以高亮同一選中區域的文字,通過按下 ALT +SHIFT + 向上/向下箭頭來改變所選區域的大小下面是一些例子,輸入内容和eclipse 顯示視窗的回應資訊。

手動運作DDMS

雖然推薦用ADT 插件調試程式,但是你也可以手動運作DDMS,配置Eclipse 以便在8700 端口上調試程式(注意:首先确定你啟動了DDMS)。

cuihai 2011-03-03 12:15

小提示: 如果你忘記引入 TextView 的包,可以嘗試 Ctrl-Shift-O (如果是Mac 系統Cmd-Shift-O)。 這是Eclipse 管理應用的快捷方式-它會顯示沒有找到的包然後自動為你加上。在Android 裡,使用者接口由一些稱之為視圖的不同層次的類組成。一個視圖就是一個簡單的對象。如單選框,動畫控件,一個文本框(我們的例子裡的),我們稱處理文本的這樣一個子視圖就叫TextView。這裡教你如何建立 TextView。

這裡教你如何建立TextView:

TextView tv = new TextView(this);

TextView 構造器就是Android 上下文的執行個體,這個上下文僅僅是指向系統的一個句柄,它提供像資源處理之類的服務。包含一些進入資料庫以及參數選擇的入口。這個活動也是繼承上下文。 HelloAndroid 類是活動的一個子類,它也是一個上下文,我們能通過this 操作TextView。

建立TextView 後,加入需要顯示的内容:

tv.setText("Hello, Android");

這裡很正常。我們建立一個TextView,然後告訴它顯示的内容。最後一步就是讓TextView 在螢幕上顯示出來,像這樣:

setContentView(tv);

活動裡setContentView()的方法表明哪個視圖需要在目前UI 上被操作。如果一個活動不能調用這個方法,那麼目前就沒有界面系統顯示為一個空白螢幕。我們僅僅想顯示一些文本,是以我們将剛才建立的TextView 連接配接上

這就是Android 平台裡的“Hello,World”,當然,我們可以看下運作情況。

不使用Eclipse 建立工程

如果你不使用Eclipse(比如你喜歡其他IDE 工具,或者僅僅使用文本編輯器或者指令行工具),那麼Eclipse 的插件對你沒有作用。别擔心-它不會因為你不使用Eclipse 而使用失去任何功能。

Android 對Eclipse 的插件僅僅是Android SDK 外圍的一個工具。(這些工具如:模拟器,aapt, adb, ddms 等等都在 其他文檔) 是以,将這些工具和其他工具如“ant”編譯檔案結合也是有可能的。

Android SDK裡包含一個Python 的腳本“activitycreator.py”,它可以為你的項目建立所有的代碼以及目錄。就像ant 中的“build.xml”檔案。這允許你使用指令行編譯你的工程或者使用你自己的IDE 工具內建。

例如,就像我們剛使用Eclipse 建立的HelloAndroid 項目,你可以使用指令:

activitycreator.py --out HelloAndroid

com.android.hello.HelloAndroid

編譯工程的時候,你運作“ant”指令,當指令成功執行後,将在“bin”目錄裡産生一個“HelloAndroid.apk”的檔案,“apk”檔案是Android 的一個包,你可以使用“adb”工具安裝或者執行。

如果想獲得更多資訊,請閱讀網站提供的替他文檔。

Android 應用程式構成

一般情況Android 應用程式是由以下四種元件構造而成的:

· 活動

· 廣播接收器

· 服務

· 内容提供器

需要注意的是,并不是每個Andorid 應用程式都必須建構這4 個元件,有些可能由這些元件的組合而成。

一旦你确定了你的應用程式中需要的元件,那麼你就應該在AndroidManifest.xml 中列出他們。 這是一個XML 配置檔案,它用于定義應用程式中需要的元件、元件的功能及必要條件等。這個檔案是必須的。詳情參見Android manifest file documentation

四種元件說明如下:

活動

活動是最基本的Andorid 應用程式元件,應用程式中,一個活動通常就是一個單獨的螢幕。每一個活動都被實作為一個獨立的類,并且從活動基類中繼承而來, 活動類将會顯示由視圖控件組成的使用者接口,并對事件做出響應。 大多數的應用是由多螢幕顯示組成。例如,一個文本資訊的應用也許有一個顯示發送消息的聯系人清單螢幕,第二個螢幕用來寫文本消息和選擇收件人, 再來一個螢幕檢視消息曆史或者消息設定操作等。

這裡每一個這樣的螢幕就是一個活動,很容易實作從一個螢幕到一個新的螢幕并且完成新的活動。 在某些情況下目前的螢幕也許需要向上一個螢幕動提供傳回值--比如讓使用者從手機中挑選一張照片傳回通訊錄做為電話撥入者的頭像。

當打開一個新的螢幕時,之前一個螢幕會被置為暫停狀态并且壓入曆史堆棧中。使用者可以通過回退操回到以前打開過的螢幕。我們可以選擇性的移除一些沒有必要保留的螢幕,因為Android 會把每個從桌面打開的程式保留在堆棧中。

Intent 和 Intent Filters

調用Android 專有類 Intent 進行構螢幕之間的切換。 Intent 是描述應用想要做什麼。Intent 資料結構兩最重要的部分是動作和動作對應的資料。典型的動作類型有:MAIN(活動的門戶)、VIEW、PICK、EDIT 等。而動作對應的資料則以URI 的形式進行表示。例如:要檢視某一個人的聯系方式,你需要建立一個動作類型為VIEW 的intent,以及一個表示這個人的URI。

與之有關系的一個類叫IntentFilter。當intent 被要求做某事的時候,intent filter 用于描述一個活動(或者BroadcastReceiver,看下面)能夠操作哪些intent。一個活動如果要顯示一個人的聯系方式時,需要聲明一個IntentFilter,這個IntentFilter 要知道怎麼去處理VIEW 動作和表示一個人的URI。 IntentFilter 需要在AndroidManifest.xml 中定義。

通過解析各種intent,從一個螢幕切換到另一個螢幕是很簡單的。當向前導航時,活動将會調用startActivity(myIntent)方法。然後,系統會在所有安裝的應用程式定義的IntentFilter 中查找,找到最比對myIntent 的Intent 對應的活動。新的活動接收到myIntent 的通知後,開始運作。當start 活動方法被調用将觸發解析myIntent 的動作,這個機制提供了兩個關鍵好處:

· 活動能夠重複利用從其它元件中以Intent 的形式産生的一個請求

· 活動可以在任何時候被一個具有相同IntentFilter 的新的活動取代

廣播接收器

你可以使用BroadcastReceiver 來讓你的應用對一個外部的事件做出響應。比如:當電話呼入時,資料網絡可用時,或者到了晚上時。BroadcastReceivers 不能顯示UI,它隻能通過 NotificationManager 來通知使用者這些有趣的事情發生了。

BroadcastReceivers 既可以在AndroidManifest.xml 中注冊,也可以在代碼中使用Context.registerReceiver()進行注冊。但這些有趣的事情發生時,你的應用不必對請求調用BroadcastReceivers,系統會在需要的時候啟動你的應用,并在必要情況下觸發BroadcastReceivers。各種應用還可以通過使用Context.sendBroadcast()将它們自己的intent broadcasts 廣播給其它應用程式。

服務

一個服務是具有一段較長生命周期且沒有使用者界面的程式。比較好的一個例子就是一個正在從播放清單中播放歌曲的媒體播放器。在一個媒體播放器的應用中,應該會有多個活動,讓使用者可以選擇歌曲并播放歌曲。然而,音樂重放這個功能并沒有對應的活動,因為使用者當然會認為在導航到其它螢幕時音樂應該還在播放的。在這個例子中,媒體播放器這個活動會使用Context.startService() 來啟動一個服務,進而可以在背景保持音樂的播放。同時,系統也将保持這個服務一直執行,直到這個service 運作結束。(你可以通過閱讀Life Cycle of an Android Application 擷取更多關于服務的介紹). 另外,我們還可以通過使用Context.bindService() 方法,連接配接到一個服務上(如果這個服務還沒有運作将啟動它)。當連接配接到一個服務之後,我們還可以通過服務提供的接口與它進行通訊。拿媒體播放器這個例子來說,我們還可以進行暫停、重播等操作。

教程:一個記事本應用程式範例

本教程通過手把手教你的方式,講解如何利用Android 架構和諸多工具建立自己的手機應用。從一個預先配置好的工程檔案開始,該教程通過一個簡單記事本應用程式完整的開發過程,并輔以貫穿始終的詳盡例子,指導你如何搭建工程、組織應用邏輯以及UI,乃至接下來的編譯及運作可執行程式等等。

該教程将這個記事本應用的開發過程視作一組練習(見如下),每一個練習都由若幹步驟組成。你可以亦步亦趨地完成每個練習步驟,逐漸建立并完善自己的應用程式。這些練習提供了你實作此應用所需的——細到每一步驟的——具體範例代碼。

當你完成此教程後,一個具有實際功能的Android 應用就從你手上誕生了,并且你對Android 應用開發中的一些極為重要的概念也會有更加深刻的了解。若你想為你這個簡單的記事本應用添加更多複雜功能的話,你可以用另一方式實作的記事本程式比照你的練習代碼,具體可參看 Sample Code 文檔部分。

本教程目标讀者

該教程主要是面向有一定經驗,尤其是那些具備一定Java 程式設計語言知識的開發者。如果你之前從未寫過一個Java 應用程式的話,仍可以使用此教程,隻是學習進度稍稍慢一點罷了。

本教程假定你已熟悉了一些基本的Android 應用概念和術語。如果你對這些還不夠熟稔的話,你得将 Overview of an Android Application 好好溫故一下,才能繼續下面的學習。

同時需注意的時,該教程的內建開發環境是預裝Android 插件的Eclipse。如果你不用Eclipse,仍可做下面的這些練習和建立應用,但你屆時将不得不面對一些涉及Eclipse的步驟在非Eclipse IDE 中如何實作的問題。

Android 應用程式子產品: 應用, 任務, 程序, 和線程

在大多數作業系統裡,存在獨立的一個1 對1 的可執行檔案(如Windows 裡的exe 檔案),它可以産生程序,并能和界面圖示、應用進行使用者互動。但在Android 裡,這是不固定的,了解将這些分散的部分如何進行組合是非常重要的。

由于Android 這種可靈活變通的,在實作一個應用不同部分時你需要了解一些基礎技術:

· 一個android 包 (簡稱 .apk ) ,裡面包含應用程式的代碼以及資源。這是一個應用釋出,使用者能下載下傳并安裝他們裝置上的檔案。

· 一個 任務 ,通常使用者能當它為一個“應用程式”來啟動:通常在桌面上會有一個圖示可以來啟動任務,這是一個上層的應用,可以将你的任務切換到前台來。

· 一個 程序 是一個底層的代碼運作級别的核心程序。通常.apk 包裡所有代碼運作在一個程序裡,一個程序對于一個.apk 包;然而, 程序 标簽常用來改變代碼運作的位置,可以是 全部的.apk 包 或者是獨立的 活動, 接收器, 服務, 或者 提供器元件。

任務

記住關鍵的一點:當使用者看到的“應用”,無論實際是如何處理的,它都是一個任務。如果你僅僅通過一些活動來建立一個.apk 包,其中有一個肯定是上層入口(通過動作的intent-filter 以及分android.intent.category.LAUNCHER),然後你的.apk 包就建立了一個單獨任務,無論你啟動哪個活動都會是這個任務的一部分。

一個任務,從使用者的觀點,他是一個應用程式;對開發者來講,它是貫穿活動着任務的一個或者多個視圖,或者一個活動棧。當設定Intent.FLAG_ACTIVITY_NEW_TASK标志啟動一個活動意圖時,任務就被建立了;這個意圖被用作任務的根用途,定義區分哪個任務。如果活動啟動時沒有這個标記将被運作在同一個任務裡(除非你的活動以特殊模式被啟動,這個後面會讨論)。如果你使用 FLAG_ACTIVITY_NEW_TASK 标記并且這個意圖的任務已經啟動,任務将被切換到前台而不是重新加載。

FLAG_ACTIVITY_NEW_TASK 必須小心使用:在使用者看來,一個新的應用程式由此啟動。如果這不是你期望的,你想要建立一個新的任務。另外,如果使用者需要從桌面退出到他原來的地方然後使用同樣的意圖打開一個新的任務,你需要使用新的任務标記。否則,如果使用者在你剛啟動的任務裡按桌面(HOME)鍵,而不是退出(BACK)鍵,你的任務以及任務的活動将被放在桌面程式的後面,沒有辦法再切換過去。

任務親和力(Affinities)

一些情況下Android 需要知道哪個任務的活動附屬于一個特殊的任務,即使該任務還沒有被啟動。這通過任務親和力來完成,它為任務中一個或多個可能要運作的活動提供一個獨一無二的靜态名字。預設為活動命名的任務親和力的名字,就是實作該活動.apk 包的名字。這提供一種通用的特性,對使用者來說,所有在.apk 包裡的活動都是單一應用的一部分。

當不帶 Intent.FLAG_ACTIVITY_NEW_TASK 标記啟動一個新的活動,任務親和力對新啟動的活動将沒有影響作用:它将一直運作在它啟動的那個任務裡。然而,如果使用NEW_TASK 标記,親和力會檢測已經存在的任務是否具有相同的親和力。如果是,該任務會被切換到前台,新的活動會在任務的最上面被啟動。

你可以在你的表現檔案裡的應用程式标簽裡為.apk 包裡所有的活動設定你自己的任務親和力,當然也可以為單獨的活動設定标簽。這裡有些例子示範如何使用:

· 如果你的.apk 包裡包含多個使用者可啟動的上層應用程式,那麼你可能想要為每個活動配置設定不同的親和力。這裡有一個不錯的協定,你可以将不同的名字字串加上冒号附加在.apk 包名字的後面。 例如,"com.android.contacts"的親和力命名可以是"com.android.contacts:Dialer"and"com.android.contacts:ContactsList"。

· 如果你想替換一個通知,快捷鍵,或者其它能從外部啟動的應用程式的内部活動,你需要在你想替換的活動裡明确的設定任務親和力(taskAffinity)。例如,如果你想替換聯系人詳細資訊浏覽界面(使用者可以直接操作或者通過快捷方式調用),你需要設定任務親和力(taskAffinity)為“com.android.contacts”。

啟動模式以及啟動标記

你控制活動和任務通信的最主要的方法是通過設定啟動模式的屬性以及意圖相應的标記。這兩個參數能以不同的組合來共同控制活動的啟動結果,這在相應的文檔裡有描述。

這裡我們隻描述一些通用的用法以及幾種不同的組合方式。

你最通常使用的模式是singleTop(除了預設為standard 模式)。這不會對任務産生什麼影響;僅僅是防止在棧頂多次啟動同一個活動。

singleTask 模式對任務有一些影響:它能使得活動總是在新的任務裡被打開(或者将已經打開的任務切換到前台來)。使用這個模式需要加倍小心該程序是如何和系統其他部分互動的,它可能影響所有的活動。這個模式最好被用于應用程式入口活動的标記中。

(支援MAIN 活動和LAUNCHER 分類)。

singleInstance 啟動模式更加特殊,該模式隻能當整個應用隻有一個活動時使用。有一種情況你會經常遇到,其它實體(如搜尋管理器SearchManager 或者 通知管理器NotificationManager)會啟動你的活動。這種情況下,你需要使用Intent.FLAG_ACTIVITY_NEW_TASK 标記,因為活動在任務(這個應用/任務還沒有被啟動)之外被啟動。就像之前描述的一樣,這種情況下标準特性就是目前和任務和新的活動的親和性比對的任務将會切換到前台,然後在最頂端啟動一個新的活動。當然,你也可以實作其它類型的特性。

一個常用的做法就是将Intent.FLAG_ACTIVITY_CLEAR_TOP 和NEW_TASK 一起使用。這樣做,如果你的任務已經處于運作中,任務将會被切換到前台來, 在棧裡的所有的活動除了根活動,都将被清空,根活動的onNewIntent(Intent) 方法傳入意圖參數後被調用。當使用這種方法的時候 singleTop 或者 singleTask 啟動模式經常被使用,這樣目前執行個體會被置入一個新的意圖,而不是銷毀原先的任務然後啟動一個新的執行個體。

另外你可以使用的一個方法是設定活動的任務親和力為空字串(表示沒有親和力),然後設定finishOnBackground 屬性。 如果你想讓使用者給你提供一個單獨的活動描述的通知,倒不如傳回到應用的任務裡,這個比較管用。要指定這個屬性,不管使用者使用BACK還是HOME,活動都會結束;如果這個屬性沒有指定,按HOME 鍵将會導緻活動以及任務還留在系統裡,并且沒有辦法傳回到該任務裡。

請確定閱讀過文檔啟動模式屬性(launchMode attribute) 以及 意圖示記(Intent

flags) ,關注這些選項的詳細資訊。

程序

在Android 中,程序是應用程式的完整實作,而不是使用者通常了解的那樣。他們主要用途很簡單:

· 提高穩定性和安全性,将不信任或者不穩定的代碼移動到其他程序。

· 可将多個.apk 包運作在同一個程序裡減少系統開銷。

· 幫助系統管理資源,将重要的代碼放在一個單獨的程序裡,這樣就可以單獨銷毀應用程式的其他部分。

像前面描述的一樣,程序的屬性被用來控制那些有特殊應用元件運作的程序。注意這個屬性不能違反系統安全: 如果兩個.apk 包不能共享同一個使用者ID,卻試圖運作在通一個程序裡,這種情況是不被允許的,事實上系統将會建立兩個不同的程序。

請檢視安全相關文檔以擷取更多關于安全限制方面的資訊。

線程

每個程序包含一個或多個線程。多數情況下,Android 避免在程序裡建立多餘的線程,除非它建立它自己的線程,我們應保持應用程式的單線程性。一個重要的結論就是所有呼叫執行個體, 廣播接收器, 以及 服務的執行個體都是由這個程序裡運作的主線程建立的。

注意新的線程不是為活動,廣播接收器,服務或者内容提供器執行個體建立:這些應用程式的元件在程序裡被執行個體化(除非另有說明,都在同一個程序處理),實際上是程序的主線程。這說明當系統調用時這些元件(包括服務)不需要程序遠距離或者封鎖操作(就像網絡呼叫或者計算循環),因為這将阻止程序中的所有其他元件。你可以使用标準的線程 類或者Android 的HandlerThread 類去對其它線程執行遠端操作。

這裡有一些關于建立線程規則的例外:

· 呼叫IBinder 或者IBinder 實作的接口,如果該呼叫來自其他程序,你可以通過

線程發送的IBinder 或者本地程序中的線程池呼叫它們,從程序的主線程呼叫是不可以的。特殊情況下,,呼叫一個服務 的IBinder 可以這樣處理。(雖然在服務裡呼叫方法在主線程裡已經完成。)這意味着IBinder 接口的實作必須要有一種線程安全的方法,這樣任意線程才能同時通路它。

· 呼叫由正在被調用的線程或者主線程以及IBinder 派發的内容提供器 的主方法。

被指定的方法在内容提供器的類裡有記錄。這意味着實作這些方法必須要有一種

線程安全的模式,這樣任意其它線程同時可以通路它。

· 呼叫視圖以及由視圖裡正在運作的線程組成的子類。通常情況下,這會被作為程序的主線程,如果你建立一個線程并顯示一個視窗,那麼繼承的視窗視圖将從那個線程裡啟動。

Android 應用程式的生命周期

在大多數情況下,每個Android 應用程式都運作在自己的Linux 程序中。當應用程式的某些代碼需要運作時,這個程序就被建立并一直運作下去,直到系統認為該程序不再有用為止。然後系統将回收程序占用的記憶體以便配置設定給其它的應用程式。應用程式的開發人員必須了解不同的應用程式元件(尤其是Activity, Service, 和BroadcastReceiver)是如何影響應用程式程序生命周期的,這是很重要的一件事情。不正确地使用這些元件可能會導緻系統殺死正在執行重要任務的應用程式程序。一個常見的程序生命周期bug 的例子是BroadcastReceiver, 當BroadcastReceiver在BroadcastReceiver.onReceive()方法中接收到一個Intent 時,它會啟動一個線程,然後傳回。一旦它傳回,系統将認為BroadcastReceiver 不再處于活動狀态,因而BroadcastReceiver 所在的程序也就不再有用了(除非該程序中還有其它的元件處于活動狀态)。是以,系統可能會在任意時刻殺死程序以回收記憶體。這樣做的話,程序中建立(spawned)出的那個線程也将被終止。對這個問題的解決方法是從BroadcastReceiver 啟動一個服務,讓系統知道程序中還有處于活動狀态的工作。為了決定在記憶體不足時讓系統殺死哪個程序,Android 根據每個程序中運作的元件以及元件的狀态把程序放入一個”重要性分級(importance hierarchy)”中。程序的類型包括(按重要程度排序):

1. 前台(foreground)程序,與使用者目前正在做的事情密切相關。不同的應用程式元件能夠通過不同的方法使它的宿主程序移到前台。當下面任何一個條件滿足時,可以考慮将程序移到前台:

1. 程序正在螢幕的最前端運作一個與使用者互動的Activity (它的onResume()

方法被調用)

2. 程序有一正在運作的BroadcastReceiver (它的

BroadcastReceiver.onReceive()方法正在執行)

3. 程序有一個Service,并且在Service 的某個回調函數(Service.onCreate(),

Service.onStart(), 或 Service.onDestroy())内有正在執行的代碼。

1. 可見(visible)程序,它有一個可以被使用者從螢幕上看到的Activity,但不在前台(它的onPause()方法被調用)。舉例來說,如果前台的Activity 是一個對話框,以前的Activity 隐藏在對話框之後,就可能出現這種程序。這樣的程序特别重要,一般不允許被殺死,除非為了保證前台程序的運作不得不這樣做。

2. 服務(service)程序,有一個已經用startService() 方法啟動的Service。雖然這些程序使用者無法直接看到,但它們做的事情卻是使用者所關心的(例如背景MP3回放或背景網絡資料的上傳下載下傳)。是以,系統将一直運作這些程序除非記憶體不足以維持所有的前台程序和可見程序。

3. 背景(background)程序, 擁有一個目前使用者看不到的Activity(它的onStop()

方法被調用)。這些程序對使用者體驗沒有直接的影響。如果它們正确執行了Activity生命期(詳細資訊可參考Activity),系統可以在任意時刻殺死程序來回收記憶體,并提供給前面三種類型的程序使用。系統中通常有很多個這樣的程序在運作,是以要将這些程序儲存在LRU 清單中,以確定當記憶體不足時使用者最近看到的程序最後一個被殺掉。

4. 空(empty)程序,不包含任何處于活動狀态的應用程式元件。保留這種程序的唯一原因是,當下次應用程式的某個元件需要運作時,不需要重新建立程序,這樣可以提高啟動速度。

系統将以程序中目前處于活動狀态元件的重要程度為基礎對程序進行分類。請參考Activity, Service 和 BroadcastReceiver 文檔來獲得有關這些元件在程序整個生命期中是如何起作用的詳細資訊。每個程序類别的文檔較長的描述了它們是怎樣影響應用程式整個生命周期的。程序的優先級可能也會根據該程序與其它程序的依賴關系而增長。例如,如果程序A 通過在程序B 中設定Context.BIND_AUTO_CREATE 标記或使用ContentProvider 被綁定到一個服務(Service),那麼程序B 在分類時至少要被看成與程序A 同等重要。

二、開發應用程式

Android 應用構成

Android 應用是由各種各樣的元件來構成。 這些元件大部分都是松散連接配接的,準确 的說你可以把它們看成元件的聯合而非是一個單一的應用。

通常,這些元件運作在同一個系統程序裡面。你也可以在這個程序裡面建立多個線程(這是很常見的),如果必要你也可以建立獨立的子程序。不過這種情況是非常少見的,因為Android 盡力使代碼程序間透明。

以下部分是很重要的Android APIs:

AndroidManifest.xml

AndroidManifest.xml 是系統的控制檔案,它告訴系統如何處理你所建立的所有

頂層元件(尤其是activities,服務,Intent 接收器和後面描述的内容管理器)。舉例

來說,控制檔案就是把你的活動(Activities)要接收的Intents 連接配接在一起的“膠

水”。

活動(Activities)

活動(Activity)就是一個有生命周期的對象。 一個Activity 就是完成某些工作

的代碼塊, 如必要的話,這部分工作還可能包括對使用者UI 界面的顯示。不過

這不是必須的,有些活 動從不顯示UI 界面。典型地,你将會指定你的應用程

序中的一個活動為整個程式的入口點。

視圖(Views)

視圖(Views)可以将其自身繪制到螢幕上。Android 的使用者界面由一系列的視圖

樹 (trees of views)構成。接口都是由一組以樹的形式出現的視圖組成的。開

發者可 以通過建立一個新的視圖的方法來使用自定義的圖形處理技術(比如開

發遊戲,或者是 使用了不常用的使用者圖形(UI)視窗界面(widget))。

Intents

Intents 是一個簡單的消息對象,它表示程式想做某事的“意圖”(intention)。比

如如果你的應用程式想要顯示一個網頁,那麼它通過建立一個Intent 執行個體并将其

傳遞給 系統來表示意圖浏覽這個URI。系統将定位于知道如何能處理這一

Intent 的代碼(在當 前情況下就是浏覽器),并運作之。Intents 也可以用于廣

播系統範圍内的有效事件 (例如通知事件)。

服務(Services)

服務是運作在背景的一段代碼。它可以運作在它自己的程序,也可以運作在其他

應用程 序程序的上下文(context)裡面,這取決于自身的需要.。其它的元件

可以綁定到一個服 務(Service)上面,通過遠端過程調用(RPC)來調用這

個方法。例如媒體播放器的服務, 當使用者退出媒體選擇使用者界面,她仍然希望

音樂依然可以繼續播放,這時就是由服務 (service)來保證當使用者界面關閉時

音樂繼續播放的。

通知(Notifications)

通知将以小圖示的形式呈現在狀态欄裡,使用者通過與圖示的互動式操來接收消

息。最常見 的通知包括短資訊,通話記錄,語音郵件,但是應用程式也可以創

建它們自己的通知事件。 我們推薦采用通知事件實作提醒使用者的注意。

内容管理器(ContentProviders)

内容管理器(ContentProvider)提供對裝置上資料進行通路的資料倉庫。典型

的例子就 是使用内容管理器來通路聯系人清單。你的應用程式也可以使用其它

程式通過内容管理器提 供的資料,同時你也可以定義你自己的内容管理器來向

其它應用提供資料通路服務。

存、取、提供資料

典型的桌面作業系統一般能提供一種通用的檔案系統,所有應用程式都能儲存和讀檔案,并且其他應用程式也能通路該檔案(可能需要一些通路控制設定). Android 使用不同的方式:在平台上,所有應用程式的資料(包括檔案),對該應用程式是私有的。當然,Android 也提供一種标準方法将自己的私有資料提供給其他應用程式通路。這一章節講了很多方法,描述應用如何存取資料,以及将資料提供給其他程式通路,當然,你也可以向其他應用程式請求并獲得它的資料。

Android 提供下面的方式來存取資料:

參數選擇

使用一個輕量級機制來存取基本資料類型的資料對,這是典型應用程式參數的

存儲模式。

檔案

你可以将你的檔案存儲在裝置上或者其他移動媒介上,預設情況下,其他應用

程式是不能通路這些檔案的。

資料庫

Android 有直接SQLite 資料庫的API。應用程式可以建立以及使用SQLite 資料

庫。 每個包建立的資料庫都是私有的。

資料提供

資料提供是應用程式的一個可選元件,它可以提供讀/寫應用程式私有資料的方

法。内容提供元件實作了一種标準請求資料的文法,和一種标準處理傳回值的

機制。Android 提供很多标準資料的提供方式,例如私有聯系人。

網絡

不要忘記,我們還可以使用網絡存取資料。

Android 的安全與權限

Android 是一個多程序系統,每一個應用程式(和系統的組成部分)都運作在自己的程序中。在應用程式和系統間的安全通過标準的Linux 裝置在程序級被執行,例如被配置設定給應用程式的使用者群組ID。額外的細粒度安全特性通過“許可”機制來提供,該機制能夠對一個指定程序可實作的特定操作進行限制。

安全結構

Android 安全學中的一個重要的設計點是在預設情況下應用程式沒有權限執行對其它應用程式、作業系統或使用者有害的操作。這些操作包括讀/寫使用者的隐私資料(例如聯系方式或e-mail),讀/寫其它應用程式的檔案,執行網絡通路,保持裝置活動,等等。

應用程式的程序是一個安全的沙箱。它不能幹擾其它應用程式,除非在它需要添加原有沙箱不能提供的功能時明确聲明權限。這些權限請求能夠被不同方式的操作所處理,特别的要基于證書和使用者的提示被自動的允許或禁止。權限的請求在那個應用程式中通過一個應用程式被聲明為靜态的,是以在此之後在安裝時或沒有改變時它們會預先知道。

應用程式簽名

所有的Android 應用程式(.apk 檔案)必須通過一個證書的簽名,此證書的私鑰必須被開發者所掌握。這個證書的辨別是應用程式的作者。這個證書不需要通過證書組織的簽署:Android 應用程式對于使用自簽署的證書是完全允許的和特别的。這個證書僅僅被用于與應用程式建立信任關系,不是為了大規模的控制應用程式可否被安裝。最重要的方面是通過确定能夠通路原始簽名權限和能夠共享使用者ID 的簽名來影響安全。

使用者辨別和檔案通路

安裝在裝置中的每一個Android封包件(.apk)都會被配置設定給一個屬于自己的統一的Linux使用者ID,并且為它建立一個沙箱以防止影響其它應用程式(或者其它應用程式影響它)。

使用者ID 在應用程式安裝到裝置中時被配置設定,并且在這個裝置中保持它的永久性。

因為安全執行發生在程序級,是以一些不同包中的代碼在相同程序中不能正常的運作,自從他們需要以不同Linux 使用者身份運作時。你可以使用每一個包中的

AndroidManifest.xml 檔案中的manifest 标簽屬性sharedUserId 擁有它們配置設定的相同使用者ID。通過這樣做,兩個包被視為相同的應用程式的安全問題被解決了,注意為了保持安全,僅有相同簽名(和請求相同sharedUserId 标簽)的兩個應用程式簽名将會給相同的使用者ID。

應用建立的任何檔案都會被賦予應用的使用者辨別,并且,正常情況下不能被其它包通路。當你通過getSharedPreferences(String, int), openFileOutput(String, int) 或者openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)建立一個新檔案時, 你可以同時或分别使用 MODE_WORLD_READABLE 和

MODE_WORLD_WRITEABLE 标志允許其它包讀/寫此檔案。當設定了這些标志時,這個檔案仍然屬于你的應用程式,但是它的全局讀、寫和讀寫權限已經設定是以其它任何應用程式可以看到它。

權限命名

一個基本的Android 應用程式沒有與其相關聯的權限,意味着它不能做任何影響使用者體驗或裝置中的資料的有害操作。要利用這個裝置的保護特性,在你的應用程式需要時,你必須在AndroidManifest.xml 檔案中包含一個或更多的<uses-permission>

标簽來聲明此權限。

例如:需要監聽來自SMS 消息的應用程式将要指定如下内容:

<manifest

xmlns:android="http://schemas.android.com/apk/res/android"

package="com.android.app.myapp" >

<uses-permission

android:name="android.permission.RECEIVE_SMS" />

</manifest>

在安裝應用程式時,通過包安裝器應用程式要通過權限請求的許可,使建立在與應用程式簽名的核對下聲明對于使用者的那些權限和影響。在應用運作期間對使用者不做檢查:它要麼在安裝時被授予特定的許可,并且使用想用的特性;要麼不被授予許可,并且使得一切使用特性的嘗試失敗而不提示使用者。

例如,sendBroadcast(Intent) 方法就是當資料被發送給到每個接收器時檢查許可的,

在方法調用傳回之後,是以當許可失敗時你不會收到一個異常。然而,幾乎在所有例子中,許可失敗都會被列印到系統日志中。通常,多次的許可錯誤會産生抛回至應用程式的SecurityException 異常。

Android 系統提供的許可可以在Manifest.permission 中找到。每個引用也可以定義和Enforce 它自己的許可,是以這不是全面的所有可能的清單。

在程式操作期間,個别權限在一些地方可能被強制:

· 在系統接到呼叫的時候,預防一個應用程式去執行特定的函數。

· 在啟動Activity 時,防止一個應用啟動其它應用程式的Activities。

· 發送和接收Intent 廣播時,控制誰能接收你的廣播或者誰能發送廣播給你。

· 在一個内容提供器上通路和操作時。

· 綁定或開始一個服務時。

權限的聲明和支援

為了執行你自己的權限,你必須首先在你的AndroidManifest.xml 中使用一個或多個<permission> 标簽聲明它們。

<protectionLevel> 屬性是必需的,告訴系統使用者應如何處理應用程式接到請求此權限的通知,或者在這個文檔中對這個權限的許可的描述。

<permissionGroup> 屬性是可選的,僅僅用于幫助系統為使用者顯示權限。通常,你要設定這些,向一個标準的系統組(列在android.Manifest.permission_group 中),或者在更多的情況下要自定義。它更偏向于使用一個已經存在的組,做為簡化的權限使用者界面顯示給使用者。

注意:應該為每個權限提供标簽(label) 和描述(description)。當使用者浏覽權限清單時,它們可以為使用者展示字元資源,如(android:label) 或者一個許可的詳細資訊( android:description) 。标簽(label)比較短,用幾個詞來描述該權限保護的關鍵功能。描述(description)應該是一組句子,用于描述獲得權限的使用者可以做什麼。我們寫描述的習慣是兩句話,第一句聲明權限,第二句警告使用者如果應用許可該權限時,會發生什麼不好的事情。

你可以在系統中通過shell 指令 adb shell pm list permissions 檢視權限目前

在AndroidManifest.xml 檔案中支援權限

通過 AndroidManifest.xml 檔案可以設定進階權限,以限制通路系統的所有元件或者使用應用程式。所有的這些請求都包含在你所需要的元件中的 android:permission屬性,命名這個權限可以控制通路此元件。

Activity 權限 (使用 <activity> 标簽) 限制能夠啟動與 Activity 權限相關聯的元件或應用程式。此權限在 Context.startActivity() 和 Activity.startActivityForResult() 期間要經過檢查;如果調用者沒有請求權限,那麼會為調用抛出一個安全異常

( SecurityException )。

Service 權限(應用 <service> 标簽)限制啟動、綁定或啟動和綁定關聯服務的元件或應用程式。此權限在 Context.startService(), Context.stopService() 和

Context.bindService() 期間要經過檢查;如果調用者沒有請求權限,那麼會為調用抛出一個安全異常( SecurityException )。

BroadcastReceiver 權限(應用 <receiver> 标簽)限制能夠為相關聯的接收者發送廣播的元件或應用程式。在 Context.sendBroadcast() 傳回後此權限将被檢查,同時系統設法将廣播遞送至相關接收者。是以,權限失敗将會導緻抛回給調用者一個異常;它将不能遞送到目的地。在相同方式下,可以使 Context.registerReceiver() 支援一個權限,使其控制能夠遞送廣播至已登記節目接收者的元件或應用程式。其它的,當調用

Context.sendBroadcast() 以限制能夠被允許接收廣播的廣播接收者對象一個權限(見下文)。

ContentProvider 權限(使用 <provider> 标簽)用于限制能夠通路 ContentProvider中的資料的元件或應用程式。(Content providers 有一個重要的附加安全設施可用于它們調用被描述後的URI 權限。) 不同于其它元件,它有兩個不相連系的權限屬性要設定:android:readPermission 用于限制能夠讀取提供器的元件或應用程式,android:writePermission 用于限制能夠寫入提供器的元件或應用程式。注意,如果一個提供者的讀寫權限受保護,意思是你隻能從提供器中讀,而沒有寫權限。當你首次收回提供者(如果你沒有任何權限,将會抛出一個SecurityException 異常),那麼權限要被檢查,并且做為你在這個提供者上的執行操作。 使用 ContentResolver.query() 請求擷取讀權限; 使用 ContentResolver.insert(), ContentResolver.update() 和ContentResolver.delete() 請求擷取寫權限。在所有這些情況下,一個SecurityException異常從一個調用者那裡抛出時不會存儲請求權限結果。

發送廣播時支援權限

當發送一個廣播時你能總指定一個請求權限,此權限除了權限執行外,其它能發送Intent到一個已注冊的BroadcastReceiver 的權限均可以。 通過調用

Context.sendBroadcast() 及一些權限字元串, 為了接收你的廣播,你請求一個接收器應用程式必須持有那個權限。

注意,接收者和廣播者都能夠請求一個權限。當這樣的事發生了,對于Intent 來說,這兩個權限檢查都必須通過,為了傳遞到共同的目的地。

其它權限支援

任意一個好的粒度權限都能夠在一些調用者的一個服務中被執行。 和

Context.checkCallingPermission() method. 方法一起被完成。調用并産生一個需要的權限字元串,它将傳回一個整型,以确定目前調用程序是否被許可。注意,僅僅當你執行的調用進入到其它程序的時候這些才會被使用,通常,通過IDL 接口從一個服務釋出,或從一些其它方式通知其它程序。

這有許多其它有益的方式去檢查權限。如果你有一個其它程序的PID,你可以使用上下文的方法 Context.checkPermission(String, int, int) 檢查權限違反PID。 如果你有一個其它應用程式的包名, 你可以使用直接的包管理器方法

PackageManager.checkPermission(String, String) 去檢視特定的包是否被指定的權限所許可。

URI 權限

迄今為止,在與内容提供器共同使用時,标準權限系統描述通常是不充分的。一個内容提供器要保護它自己及讀和寫權限,當為了它們産生作用,它的直接用戶端總是需要手動的對其它應用程式指定URI。一個典型的例子是郵件應用程式中的附件。通路郵件的權限應該被保護,因為這是敏感使用者資料。可以,如果一個網址圖檔附件提供給一個圖檔檢視器,那個圖檔檢視器将沒有權限打開這個附件,因為它沒有原因去擁有一個權限進而不能通路所有的電子郵件。

對于這個問題的解決是通過網址權限:當開始一個活動或對一個活動傳回一個結果,調用者可通過設定Intent.FLAG_GRANT_READ_URI_PERMISSION 和

Intent.FLAG_GRANT_WRITE_URI_PERMISSION 中的一個或者兩個。允許接收活動權限通路在Intent 中特定的資料位址,不論它有權限通路資料在内容提供器相應的Intent 中。

這種機制允許一個公用的功能性模型使使用者互相互動(打開一個附件,從一個清單中選擇一個聯系人,等等)驅動ad-hoc 在優粒度權限的許可下。這可能是一個主要裝置,應用程式為了減少這個權限而需要,僅僅直接關系到它們的行為。

這優粒度URI 權限的許可工作,然而,請求一些協作和内容提供者保持那些URI。強烈推薦内容提供者實作這種裝置,并且通過android:grantUriPermissions 屬性或者<grant-uri-permissions> 标簽聲明支援它。

更多資訊可以在Context.grantUriPermission(), Context.revokeUriPermission(), 和Context.checkUriPermission() 方法中找到。

資源管理和多國版本

資源是外部檔案(不含代碼的檔案),它被代碼使用并在編譯時編入應用程式。Android支援不同類型的資源檔案,包括XML,PNG 以及JPEG 檔案XML 檔案根據描述的不同有不同格式。這份文檔描述可以支援什麼樣的檔案,文法,以及各種格式.源代碼以及XML 檔案将資源打包并編譯進二進制檔案,這種模式能使得資源更快得被加載。字元串也同樣被壓縮成更高效的模式。由于這些原因, Android 平台上存在不同的資源類型.

資源

Android 資源系統能跟蹤所有非代碼相關的應用程式。你可以使用 資源 類來通路應用程式的資源,資源的執行個體通常和應用程式聯系在一起,你可以通過Context.getResources()來通路。

應用程式的資源在編譯時就被編譯到應用程式二進制代碼裡。為了使用某個資源,你需要将它在代碼目錄結構裡放正确,然後編譯。作為編譯過程的一部分,産生的資源代号你可以在源代碼裡使用 -- 這允許編譯器驗證你的程式代碼和你定義的資源是否相符。

建立資源

Android 支援字元串,圖檔以及很多其他類型的資源。每個對象文法、格式以及它們存儲位置的支援,都是取決于不同類型的對象? 通常,你可以通過三種類型的檔案來建立資源:XML 檔案(除位圖以及原資料檔案),位圖檔案(對于圖檔)以及原始資料(其它類型,例如聲音檔案,等等。)。事實上,有兩種不同類型的XML 檔案,一種是編譯到包裡的,另外一種是通過aapt 來産生的資源檔案,這裡有一張包含所有資源類型,

檔案格式,檔案描述以及所有XML 檔案的詳細資訊的清單。

在項目裡,你可以在子目錄res/下建立和存儲資源檔案。Android 有一個資源編譯工具(aapt),它可以編譯在這個目錄下所有的子目錄中的資源,這裡有個各種資源的清單。你可以從 資源引用 這裡看到各種類型的對象,包含其文法以及格式。

路徑 資源類型res/anim/ XML 檔案被編譯進 逐幀動畫 或 補間動畫 的對象res/drawable/.png, .9.png, .jpg files 這些類型的檔案被編譯進下列這些圖表資源清單為了獲得這些資源的類型,使用Resource.getDrawable(id)

· 位圖檔案

· 9-patches (可改變尺寸的圖像)

res/layout/ 可編譯成螢幕布局的XML 檔案 (或者螢幕的一部分). 檢視 布局 res/values/ 可編譯成多種類型資源的檔案

注意: 不像其他 res/ 檔案夾,它能容納任何數量的檔案,但隻是描述其建立而不是資源本身. XML 的元素類型可以決定這些資源在R.class 裡什麼位置被替換 .檔案可以被命名為任何名字,檔案夾裡有一些典型的檔案(一般約定檔案以定義的元素類型後面部分為檔案名)::

· arrays.xml 定義數組

· colors.xml 定義 顔色 和 顔色字串數值. 你可以使用

Resources.getDrawable() 以及Resources.getColor(), respectively, 取得這些資源.· dimens.xml 定義 尺寸資料 . 使用Resources.getDimension() 取得這些資源。· strings.xml 定義字元串數值 (使用Resources.getString 或Resources.getText()取得資源,(後者更好一點)getText() 能取到在使用者界面上顯示的文本框裡的文本。· styles.xml 定義類型 對象。res/xml/ 任何XML 檔案可以進行編譯,并能在運作時調用Resources.getXML() 顯示XML 原檔案。

res/raw/ 這裡的任何檔案都将直接被複制到裝置上。編譯産品時,這些數

據不會被編譯,它們被直接加入到程式包裡。 為了在程式中使用這些資源,你可以調用Resources.openRawResource() , 參數為

ID: R.raw.somefilename.

資源最終會被編譯成APK 檔案,Android 建立一個包裝類,命名為R,這樣你能做你的代碼裡使用這些資源類。根據資源路徑和檔案名的不同,R 包含很多子類。

全局資源

· 一些資源類允許你定義顔色。它能接受多種網絡類型的值 -- 你可以寫成 #RGB,

#ARGB, #RRGGBB, #AARRGGBB 這樣16 進制常數都可以。

· 所有的顔色都可以設定一個阿爾法值,開始的兩個16 進制數指定為透明。 0 在阿爾法值裡意味着透明。當然,預設值是不透明的。

使用資源

編譯時,Android 産生一個叫R 的類,它指向你程式中所有的資源。這個類包含很多子類。每一種都是Android 支援的,同時,編譯後會産生一個資源檔案。每個類提供一個或多個編譯後資源的辨別符,你可以在代碼中使用。下面是個源代碼的檔案,裡面包含了字元串,布局檔案(全屏或者部分螢幕),以及圖像資源。

注意: 這個R 類是自動産生的,你不能手動編寫。當資源變化的時候它會自動更新。

在代碼中使用資源

隻要知道資源的ID 以及你編譯進目标檔案的資源類型就可以在代碼裡使用它來。下面是一些文法:

R.resource_type.resource_name

或者

android.R.resource_type.resource_name

resource_type 是R 子類的一種類型。 resource_name 是定義在XML 檔案裡的資源名或者為其他檔案類型定義的資源檔案(沒有字尾)名。每種類型的資源會被加入到一個特定的R 的子類中;為了學習哪種R 的子類裡有你編譯的資源類型,參考資源引用 文檔。被編譯進應用程式的資源不需要包的名字就可以直接被通路到(像這樣:

R.resource_type.resource_name). Android 包含一些标準資源,如螢幕的類型,按鈕的背景。要使用這些代碼,你需要包含 android, 如

android.R.drawable.button_background.

這裡有一些好的和糟糕的例子說明如何在代碼裡使用編譯後的資源:

// 從畫圖資源類裡裝載一個目前螢幕背景。

this.getWindow().setBackgroundDrawableResource(R.drawable.my_background_image);

// 錯誤! 将一個資源ID 裝入一個需要字元串的方法中

this.getWindow().setTitle(R.string.main_title);

//正确!需要從資源封裝類裡取得标題。

this.getWindow().setTitle(Resources.getText(R.string.main_title));

// 從目前螢幕中裝載布局資料。

setContentView(R.layout.main_screen);

//從ViewFlipper 對象中設定動畫中一幀 。

mFlipper.setInAnimation(AnimationUtils.loadAnimation(this,

R.anim.hyperspace_in));

// 在TextView 對象中設定文本内容。

TextView msgTextView = (TextView)findViewByID(R.id.msg);

msgTextView.setText(R.string.hello_message);

資源引用

一個在屬性(或者資源)裡提供的數值可以被指向一個具體的資源。這常常被使用在布局檔案中用于字元串(可以被本地化) 以及圖檔(存在于其他檔案中的),通過一個引用可以是包括顔色和整數的任何資源類型。

例如,如果有 顔色資源, 我們可以将文本的顔色值寫在布局檔案中,顔色值可以從資源檔案裡取得:

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

<EditText id="text"

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="@color/opaque_red"

android:text="Hello, World!" />

注意這裡使用‘@’的字首是說明資源引用 -- 後面的文本是資源的名字

@[package:]type/name. 這裡我們不需要指定包,因為我們在我們自己的包裡引用資源。為了指定一個系統資源,你需要這樣寫:

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

<EditText id="text"

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="@android:color/opaque_red"

android:text="Hello, World!" />

另外一個例子,當你在布局檔案裡使用字元串,你必須做資源引用,這樣字元串才能被使用:

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

<EditText id="text"

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="@android:color/opaque_red"

android:text="@string/hello_world" />

這段代碼也能被用來建立資源間引用。例如,我們能這樣建立圖像資源:

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

<resources>

<drawable

id="my_background">@android:drawable/theme2_background</drawab

le>

</resources>

主題屬性引用

另一種資源數值允許你引用目前主題屬性值。這種屬性引用隻能被用于特殊的資源類以及XML 屬性中;它允許你根據現在主題風格将你定制的UI 變得更标準化,而不用使用大量的具體數值。

這裡有個例子,我們能在布局檔案中将文本顔色設定為基本系統主題中定義好的标準顔色:

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

<EditText id="text"

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:textColor="?android:textDisabledColor"

android:text="@string/hello_world" />

注意除來我們将字首'?'代替了'@',其他非常像資源引用。當你使用這個标記,系統會自動查找你提供的屬性的名字 -- 資源工具知道肯定會有資源屬性相符合,你不需要詳細指定(?android:attr/android:textDisabledColor).

使用資源辨別符到主題裡去尋找相應的資料而不是直接使用原資料,其文法和'@'模式是一樣的: ?[namespace:]type/name 這裡的type 是可選擇的.

使用系統資源

許多系統資源應用程式是可以使用的。這樣的資源定義在"android.R"的類裡。 例如,你可以使用下面的代碼在螢幕上顯示一個标準的應用程式圖示:

public class MyActivity extends Activity

{

public void onStart()

{

requestScreenFeatures(FEATURE_BADGE_IMAGE);

super.onStart();

setBadgeResource(android.R.drawable.sym_def_app_icon);

}

}

用相似的方法,這段代碼能将你的螢幕變成系統定義的标準的“綠色背景”:

public class MyActivity extends Activity

{

public void onStart()

{

super.onStart();

setTheme(android.R.style.Theme_Black);

}

}

對于不同的語言和設定支援不同的資源

你可以根據産品界面語言以及硬體配置設定不同的資源。注意,雖然你可以包含不同的字串,布局以及其他資源,但開發包(SDK)不會給你顯式的方法去指定不同的資源去加載。Android 檢測你的硬體以及位置資訊選擇合适的設定去加載。使用者可以到裝置上的設定界面去選擇不同的語言。

要包含不同的資源,在同一目錄下建立并行的檔案夾,在每個檔案夾後加上合适的名字,這個名字能表明一些配置資訊(如語言,原始螢幕等等)。例如,這裡的項目字元串檔案一個是英文版的,另一個是法文版的:

MyApp/

res/

values-en/

strings.xml

values-fr/

strings.xml

Android 支援不同類型的修飾語,并可以加多條在檔案夾名的後面, 修飾語之間以破折号分開。例如:一個繪圖資源類指定全部配置名稱命名會像這樣:

MyApp/

res/

drawable-en-rUS-port-160dpi-finger-keysexposed-qwerty-dpad-480

x320/

更典型的,你可以僅僅指定部分特定的配置選項。隻要保證所有的數值都是按順序排列:

MyApp/

res/

drawable-en-rUS-finger/

drawable-port/

drawable-port-160dpi/

drawable-qwerty/

修飾語 值

語言 兩個小寫字母 ISO 639-1。例如: en, fr, es

地區 兩個大寫字母加上一個小寫字母'r' ISO 3166-1-alpha-2。 例如:

rUS, rFR, rES

螢幕方向 port, land, square

螢幕像素

92dpi, 108dpi, 等等。

觸摸屏類型 notouch, stylus, finger

鍵盤是否有效 keysexposed, keyshidden

基本文本輸入模式

nokeys, qwerty, 12key

無觸摸屏的主要導航模式

notouch, dpad, trackball, wheel

螢幕分辨率

320x240, 640x480, 等等。大分辨率需要開始指定。

這個清單不包含一些特殊的參數,如載體,商标,裝置/硬體,制造商。任何應用程式需要知道的資訊都在資源修飾語裡有說明。

這裡有一些通用的關于資源目錄的命名指導:

· 各個變量用破折号分開 (每個基本的目錄名後跟一個破折号)

· 變量大小寫敏感(其大小寫法必須始終一緻)例如,

o 一個drawable 的目錄必須命名為 drawable-port, 而不是drawable-PORT。

o 你不能有兩個目錄命名為 drawable-port 以及 drawable-PORT,

甚至故意将"port" 和 "PORT"指為不同的參數也不可以。

· 一個式子裡同一個類型修飾語中隻有一個值是有效的(你不能指定像這樣

drawable-rEN-rFR/)

· 你可以指定多個參數去定義不同的配置,但是參數必須是上面表格裡的。例如,

drawable-en-rUS-land 意思在US-English 的機器裡載入風景視圖。

· Android 會尋找最适合目前配置的目錄,這會在下面描述

· 表格裡所列的參數是用來打破平衡以防止多重路徑限制。 (看下面的例子)

· 所有目錄,無論是限制的,還是不限制的,隻要在 res/ 目錄下.一些目錄是不能嵌套的(這樣 res/drawable/drawable-en 是不可以的)

· 所有的資源在被代碼引用中最好都使用簡單的、不加修飾的名字,如果一個資源這樣命名:

MyApp/res/drawable-port-92dp/myimage.png

它将這樣被引用:

R.drawable.myimage (code)

@drawable/myimage (XML)

Android 如何找到最合适的目錄

Android 将會挑出哪些基本資源檔案在運作時會被使用,這依靠目前的配置。 選擇過程如下:

1. 删去一些和目前裝置配置不符合的資源。例如,如果螢幕的像素是108dpi,這可以删除 MyApp/res/drawable-port-92dpi/.

2. MyApp/res/drawable/myimage.png

3. MyApp/res/drawable-en/myimage.png

4. MyApp/res/drawable-port/myimage.png

5. MyApp/res/drawable-port-92dpi/myimage.png

6. 挑出一些最經常符合配置的資源。例如,如果我們的地區是 en-GB, 方向是

port,那我們有兩個符合配置的選項: MyApp/res/drawable-en/ 和

MyApp/res/drawable-port/. 這個目錄 MyApp/res/drawable/ 可以被

删除了,因為當另外一個有一次比對正确,而它沒有。

7. MyApp/res/drawable/myimage.png

8. MyApp/res/drawable-en/myimage.png

9. MyApp/res/drawable-port/myimage.png

10. 根據配置的優先級選取最終适合的檔案,它們按順利被排列在上面的表格裡。更确切得說,語言比對比方位比對更重要, 是以我們可以通過選擇語言檔案來平衡,MyApp/res/drawable-en/.

11. MyApp/res/drawable-en/myimage.png

12. MyApp/res/drawable-port/myimage.png

術語

資源系統将一系列分散内容集合在一起形成最終的完整的資源功能,去幫助我們了解整個系統。這裡有一些核心概念以及元件的概要說明,你在開發中将可能使用到:

最終檔案: 應用程式的獨立的資料包。這包含所有從java 程式編譯成的目标檔案,圖像(例如PNG 圖檔), XML 檔案等等。這些檔案以一種特定的方式組織在一起,在程式打包最後時,它們被打包成一個獨立的ZIP 檔案。

aapt: Android 最終檔案打包工具。這個工具産生最終程式的ZIP 檔案。除了将最終的中繼資料檔案打包在一起,它也解析資源定義到最終的二進制資料裡。

資源表:aapt 工具産生的特殊的檔案,描述了所有在程式/包裡的資源。這個檔案可以通過資源類來通路;它不能直接和應用程式接觸。

資源: 資源表裡一條記錄描述的是單一的命名值。大體上, 資源分成兩種:基本的和包裝的.資源辨別符: 在資源表裡所有的資源都被唯一的整數辨別着。所有的代碼中(資源描述,XML 檔案,Java 源代碼)你可以直接使用符号名代替真實的整數數值。

基本資源: 所有基本資源都可以被寫成一個簡單的字串,使用一定的格式可以描述資源系統裡各種不同的基本類型:整數,顔色,字串,其他資源的引用,等等。像圖檔以及XML 描述檔案這些複雜資源,被以基本字串資源儲存,它們的值就是相關最終資料檔案的路徑。

包裝資源: 有一種特殊類型的資源,不是簡單的字元串,而是有一個随意的名字/數值配對清單。每個數值可以對應它本身的資源辨別,每個值可以持相同類型的字元串格式的資料作為一個正常的資源。包裝資源支援繼承:一個包裡的資料能從其他包裡繼承,有選擇地替換或者擴充能産生你自己需要的内容。

種類: 資源種類是對于不同需求的資源辨別符而言的。例如,繪制資源類常常執行個體化繪制類的對象,是以這些包含顔色以及指向圖檔或XML 檔案的字元串路徑資料是原始資料。其它常見資源類型是字元串(本地化字元串),顔色(基本顔色),布局(一個指向XML 檔案的字串路徑,它描述的是一個使用者界面)以及風格(一個描述使用者接口屬性的包裝資源)。還有一個标準的“attr”資源類型,它定義了命名包裝資料以及XML 屬性的資源辨別符。

風格: 包含包裝資源類型的名字常常用來描述一系列使用者接口屬性。例如,一個

TextView 的類可能會有一個描述界面風格的類來定義文本大小,顔色以及對齊方式。

在一個界面布局的XML 檔案中,可以使用“風格” 屬性來确定整體界面風格,它的值就是風格資源的名字。

風格類: 這裡将詳述一些屬性資源類。其實資料不會被放在資源表本身,通常在源代碼裡它以常量的形式出現,這也可以使你在風格類或者XML 的标簽屬性裡友善找到它的值。例如,Android 平台裡定義了一個“視圖”的風格類,它包含所有标準視圖的屬性:

畫圖區域,可視區域,背景等。這個視圖被使用時,它就會借助風格類去從XML 檔案取得資料并将其載入到執行個體中。

配置: 對許多特殊的資源辨別符,根據目前的配置,可以有多種不同的值。配置包括地區(語言和國家),螢幕方向,螢幕分辨率,等等。目前的配置用來選擇當資源表載入時哪個資源值生效。

主題: 一個标準類型的資源能為一個特殊的上下文提供全局的屬性值。例如,當應用工程師寫一個活動時,他能選擇一個标準的主題去使用,白色的或者黑色的;這個類型能提供很多資訊,如螢幕背景圖檔/顔色,預設文本顔色,按鈕類型,文本編輯框類型,文本大小,等。當布置一個資源布局時,控件(文本顔色,選中後顔色,背景)的大部分設定值取自目前主題;如果需要,布局中的風格以及屬性也可以從主題的屬性中獲得。

覆寫層: 資源表不能定義新類型的資源,但是你可以在其他表裡替換資源值。就像配置值,這可以在裝載時候進行;它能加入新的配置值(例如,改變字串到新的位置),替換現有值(例如,将标準的白色背景替換成"Hello Kitty"的背景圖檔),修改資源包(例如修改主題的字型大小。白色主題字型大小為18pt)。這實際上允許使用者選擇裝置不同的外表,或者下載下傳新的外表檔案。

資源引用

資源引用 這份文檔提供了不同類型資源的詳細清單,并提供了如何在Java 代碼中使用資源以及如何引用資源的描述。

國際化和本地化

即将完成: 國際化和本地化是非常關鍵的,但現在的SDK 還沒有完全支援好。當SDK成熟時,這個章節會包含Android 平台國際化和本地化的相關資訊。 那時,外部字串以及良好的結構将會使得建立和使用資源變得更省事。

cuihai 2011-03-03 12:15

三、開發工具箱

Android 設計哲學

即使平台之間有很大的不同,但是如何利用API 建立應用程式的學習過程是大同小異的。一般來說,有兩個步驟:首先,應該知道怎麼用API 實作你的功能。其次,要了解平台間的細微差别。換句話說,首先你應該學會如何建立應用程式(了解應用程式的基本結構等),然後就要學會根據具體情況實作這個應用程式。

相比而言,第二階段(學習使用正确的方法來實作應用程式)通常需要很長一段時間,在這個過程中你會不斷地寫代碼,犯錯誤,然後從錯誤中吸取教訓。顯然,這不是一個有效的學習方法,本小節和下面的一些連接配接針對這向你伸出援助之手,教你怎麼學習建立你的應用程式。

在此之前,先講一個要點:成功的應用程式往往提供一個突出的使用者體驗。當Android團隊建構了一個有着健壯核心的系統時,大多數的使用者體驗将來源于使用者和應用程式之間的的互動。是以,我們鼓勵你們花時間去建構應用程式優秀的使用者體驗。

顯著的使用者體驗展現在三個核心特征上:1、快速;2、響應;3、無縫。當然,自從計算機出現以後,每一個平台都曾經有過類似的三種性質。盡管如此,每個平台實作這些特性的方式也有所不同;下面将會簡單的介紹在Android 平台下面你的應用程式将如何達到這些要求。

快速(Fast)

Android 程式執行應該是很快的。當然,準确來說它的程式應該執行的很有效率(有效率才會快)。在目前的計算機世界裡喲這樣一個傾向:假設摩爾定律能夠最終解決我們所有的問題。當這種傾向遇到嵌入式應用程式的時候,摩爾定律就變得有些複雜了。

與桌面和服務應用程式不一樣,摩爾定律在移動裝置應用程式上面不是真正适用的。摩爾定律實際上是關于電子半導體內建密度的,它原本的含義是:随着時間的流逝,在給定的電路尺寸上面可以內建更多的電路。對于桌面和服務應用陳旭來說,它的含義是:

你可以打包更多的“速度”在一個大小差不多的晶片上面,速度的提高,其他相應的一些性能也會顯著的提高。對以像手機這樣的嵌入式應用程式來說,相反的,使用摩爾定律是為了使晶片變得更小。這樣,随着晶片密度的提高,相同功能的晶片會變得越來越小,功耗會越來越低,進而使手機做的越來越小,電池的持續的時間越來越長。這樣就導緻手持嵌入式裝置相對于桌面系統來說正在以一種比較慢二實際的速度在增漲。因而,對于嵌入式裝置來說,摩爾定律就意味着更多的功能和更少的功耗,速度隻是次要的。這就是我們要寫出高效代碼的原因:你不能天真的認為電話在速度上面的增漲速度和桌面、服務應用程式是一樣的。一般來說,高效的代碼意味着最小的記憶體占用,意味着緊湊的風格,意味着避免了因為某種語言和編碼習慣對性能的影響。我們用面向對象這個概念來了解,大部分這樣的工作都是在方法層面上,與實際的代碼,循環等等相類似。

在 “編寫高效Android” 一文中,我們會對此做詳細的介紹。

響應(Responsive)

我們有可能能夠編寫赢得世界上所有的性能測試的代碼,但是使用者使用起來會感到很惱火,這是因為應用程式沒有足夠的響應性——讓人感覺反映遲鈍,在關鍵的時刻失靈,或者處理輸入太慢。在Android 平台下,那些響應性不夠的應用程式會經常彈出"Application Not Responding" (ANR)這樣的緻命消息。

通常,這會在應用程式不能響應使用者的輸入的情況下發生。例如,你的應用程式在一些I/O 操作上(如網絡接口調用)阻塞,這是主線程将不會處理使用者的輸入事件,一段時間之後應用系統就會認為你的程式挂起了,就會給一個選項給使用者詢問是否結束它。同樣的,如果你的應用程式花費很多時間去建構記憶體中的一個結構、或者計算遊戲的下一步,這時系統同樣會認為程式已經挂起。當碰到上面情況的時候,要確定計算的高效性,但是即使是最高效的代碼也需要花費時間。

在上面兩個例子中,問題的解決方案是建立一個子線程來處理大部分的工作,這樣就能保證你的主線程(響應使用者界面事件)一直運作,這樣防止系統認為你的程式已經僵化。因為這種線程的實作一般在“類”(CLASS)這個層次上,你可以把響應當成是類的問題來處理(這裡,可以和方法層次描述的基本性能相比較)。

這裡隻是一個簡單的介紹,在 “建構響應Android 應用程式” 一文中對于應用程式的響應性有詳細的介紹。

無縫性(Seamless)

即使是你的應用程式執行很快,并且具有很高的響應性,它仍然有可能讓使用者苦惱。一個常見的例子是背景程序(比如Android 的 Service 和 BroadcastReceiver)對某些事件可能會突然彈出一個UI 響應。這似乎是無關緊要的,并且一般開發者會認為這這是正常的,因為他們花費了戴亮時間去測試和使用自己的應用程式。可是,Android 應用程式模型的建構是能夠允許使用者在不同的應用程式之間進行流暢的切換。這就意味着,當你的背景程序實際上彈出那個UI 的時候,使用者可能正在系統的其他部分中,做一些其他的事情,如在接電話。想像一下,如果SMS 服務每次都會在文本消息傳入時彈出一個對話框,這很快就會使使用者崩潰。這就是為什麼Android 标準對于這些事件使用的是通知(Notifications)機制;這使使用者能夠自己控制。

這僅僅是一個例子,相似的例子數不勝數。比如,如果Activities 沒有正确的實作onPause()方法和其他生命周期方法,這将會導緻資料丢失。或者如果你的應用程式有意的暴露資料給其他應用程式使用,你應該使用一個ContentProvider,而不是用一個路人皆可見的未加工過的檔案或者資料庫。

這些例子有一個共同的特點,他們都涉及到程式與程式或則程式與系統之間的互動。系統被設計為将多個應用程式視為一種松耦合元件的聯合,而不是大塊的代碼黑盒。這就允許作為開發人員的你将整個系統看作是一個這些元件的大聯合。這允許你幹淨地封裝,無縫地和其他應用程式結合,因而你能設計自己喜歡的程式。

這使一種元件層次的概念(與性能和響應的類層次和方法層次相對應)。至于怎樣編寫無縫性能很高的代碼, “與系統相結合” 一文中将會對此做出介紹,提供代碼提示和最佳執行個體。

建構自定義元件

Android 中,你的應用程式程式與View 類元件有着一種固定的聯系,例如按鈕(Button)、文本框(TextView), 可編輯文本框(EditText), 清單框(ListView), 複選框(CheckBox),單選框(RadioButton), 滾動條(Gallery), 微調器(Spinner), 等等,還有一些比較先進的有着特殊用途的View 元件,例如 AutoCompleteTextView, ImageSwitcher 和TextSwitcher。除此之外,種類繁多的像 線性布局(LinearLayout), 架構布局(FrameLayout), 這樣的布局元件(Layout)也被認為是View 元件,他們是從View類派生過來的。

你的應用程式就是這些控制元件和布局元件以某種方式結合顯示在螢幕上,一般來說這些元件對你來說基本夠用,但是你也應該知道你是可以通過類繼承建立屬于自己的元件,一般可以繼承像View、Layouts(布局元件)這樣的元件,甚至可以是一些比較進階的控制類元件。下面我們說一下為什麼要繼承:

· 你可以為實作某種功能建立一個完全自定義風格的元件,例如用二維的圖形建立控制元件實作聲音的控制,就像電子控制一樣。

· 你可以把幾種元件結合形成一個新的元件,你的元件可能同時包含ComboBox

(一個能輸入的文本清單)和dual-pane selector control(左右兩個List 視窗,

你可以配置設定視窗每一項的從屬關系)等等。

· 你可以建立自己的布局元件(Layout)。SDK 中的布局元件已經提供了一系列的選項讓你打造屬于自己的應用程式,但是進階的開發人員會發現根據現有的

Layout 元件開發新的Layout 元件是很有必要的,甚至是完全從底層開發新的組

件。

· 你可以覆寫一個現有元件的顯示或功能。例如,改變EditText(可編輯文本)元件在螢幕上的顯示方式(可以參考Notepad 的例子,裡面教你如何建立一個下劃線的顯示頁面)。

· 你可以捕獲像按鍵按下這樣的事件,以一些通用的方法來處理這些事件(一個遊戲的例子)。

為了實作某種目标你可能很有必要擴充一個已經存在的View 元件,下面我們結合一些例子教你如何去做。

基本方法(The Basic Approach )

下面的一些步驟都比較概括,教你如何建立自己的元件:

1. 讓你的類(Class)繼承一個現有的View 類或View 的子類。

2. 重載父類的一些方法:需要重載的父類方法一般以‘on’開頭,如onDraw(),

onMeasure()和 onKeyDown()等等。

o 這個在Activity 或則 ListActivity 派生中同樣适用,你需要重載一些生命

周期函數和一些其他功能性的HOOK 函數。

3. 使用你的繼承類:一旦你的繼承類建立完成,你可以在基類能夠使用的地方使用你的繼承類,但完成功能就是你自己編寫的了。

繼承類能夠定義在activities 裡面,這樣你能夠友善的調用,但是這并不是必要的(或許在你的應用程式中你希望建立一個所有人都可以使用的元件)。

完全自定義元件(Fully Customized Components)

完全自定義元件的方法可以建立一些用于顯示的圖形元件(graphical components),也許是一個像電壓表的圖形計量器,或者想卡拉OK 裡面顯示歌詞的小球随着音樂滾動。

無論那種方式,你也不能單純的利用元件的結合完成,無論你怎麼結合這些現有的元件。

幸運的是,你可以以你自己的要求輕松地建立完全屬于自己的元件,你會發現不夠用的隻是你的想象力、螢幕的尺寸和處理器的性能(記住你的應用程式最後隻會在那些性能低于桌面電腦的平台上面運作)。

下面簡單介紹如何打造完全自定義的元件:

1. 最為通用的VIEW 類的父類毫無疑問是View 類,是以,最開始你要建立一個基于此類的一個子類。

2. 你可以寫一個構造函數從XML 檔案中提取屬性和參數,當然你也可以自己定義這些屬性和參數(也許是圖形計量器的顔色和尺寸,或者是指針的寬度和幅度等等)

3. 你可能有必要寫自己的事件監聽器,屬性的通路和修改函數和一些元件本身的功能上的代碼。

4. 如果你希望元件能夠顯示什麼東西,你很有可能會重載 onMeasure() 函數,因而你就不得不重載 onDraw() 函數。當兩個函數都用預設的,那麼 onDraw()函數将不會做任何事情,并且預設的 onMeasure() 函數自動的設定了一個100x100 —的尺寸,這個尺寸可能并不是你想要的。

5. 其他有必要重載的on... 系列函數都需要重新寫一次。

onDraw()和onMeasure() onDraw()函數将會傳給你一個 Canvas 對象,通過它你可以在二維圖形上做任何事情,包括其他的一些标準和通用的元件、文本的格式,任何你可以想到的東西都可以通過它實作。

注意: 這裡不包括三維圖形如果你想使用三維的圖形,你應該把你的父類由View 改為SurfaceView 類,并且用一個單獨的線程。可以參考GLSurfaceViewActivity 的例子。

onMeasure() 函數有點棘手,因為這個函數是展現元件和容器互動的關鍵部分,onMeasure()應該重載,讓它能夠有效而準确的表現它所包含部分的測量值。這就有點複雜了,因為我們不但要考慮父類的限制(通過onMeasure()傳過來的),同時我們應該知道一旦測量寬度和高度出來後,就要立即調用setMeasuredDimension() 方法。

概括的來講,執行onMeasure()函數分為一下幾個階段:

1. 重載的onMeasure()方法會被調用,高度和寬度參數同時也會涉及到

(widthMeasureSpec 和heighMeasureSpec 兩個參數都是整數類型),同時你應該考慮你産品的尺寸限制。這裡詳細的内容可以參考View.onMeasure(int,int) (這個連接配接内容詳細的解釋了整個measurement 操作)。

2. 你的元件要通過onMeasure()計算得到必要的measurement 長度和寬度進而來顯示你的元件,它應該與規格保持一緻,盡管它可以實作一些規格以外的功能(在這個例子裡,父類能夠選擇做什麼,包括剪切、滑動、送出異常或者用不同的參數又一次調用onMeasure()函數)。

3. 一旦高度和寬度計算出來之後,必須調用setMeasuredDimension(int

width, int height),否則就會導緻異常。

一個自定義元件的例子(A Customized Component Example)

在 API Demos 中的CustomView 提供了以一個自定義元件的例子,這個自定義元件在LabelView 類中定義。

LabelView 例子涉及到了自定義元件的方方面面:

· 首先讓自定義元件從View 類中派生出來。

· 編寫帶參數的構造函數(參數可以來源于XML 檔案)。這裡面的一些處理都已經在View 父類中完成,但是任然有些Labelview 使用的自定義元件特有的新的參數需要處理。

· 一些标準的Public 函數,例如setText(), setTextSize(),

setTextColor()

· 重載onMeasure()方法來确定元件的尺寸(注意:在LabelView 中是通過一個私有函數measureWidth()來實作的)

· 重載onDraw()函數把Lable 顯示在提供的canvas 上。

在例子中,你可以通過custom_view_1.xml 看到自定義元件LabelView 的用法。在XML檔案中特别要注意的是android:和app:兩個參數的混合運用,app:參數表示應用程式中被認為是LabelView 元件的個體,這些也會作為資源在R 類中定義。

元件混合技術Compound Components (or Compound

Controls)

如果你不想建立一個完全自定義的元件,而是由幾個現有元件的組合産生的新的元件,那麼混合元件技術就更加适合。簡單的來說,這樣把幾個現有的元件融合到一個邏輯組合裡面可以封裝成一個新的元件。例如,一個Combo Box 元件可以看作是是一個EditText 和一個帶有彈出清單的Button 元件的混合體。如果你點選按鈕為清單選擇一項,

在Android 中,其實還有其他的兩個View 類可以做到類似的效果: Spinner 和

AutoCompleteTextView,,但是Combo Box 作為一個例子更容易讓人了解。

下面簡單的介紹如何建立組合元件:

1. 一般從Layout 類開始,建立一個Layout 類的派生類。也許在Combo box 我們會選擇水準方向的LinearLayout 作為父類。記住,其他的Layout 類是可以嵌套到裡面的,是以混合元件可以是任何元件的混合。注意,正如Activity 一樣,你既可以使用外部XML 檔案來聲明你的元件,也可以嵌套在代碼中。

2. 在新的混合元件的構造函數中,首先,調用所有的父類的構造函數,傳入對應的參數。然後可以設定你的混合元件的其他的一些方面,在哪建立EditText 元件,又在哪建立PopupList 元件。注意:你同時也可以在XML 檔案中引入一些自己的屬性和參數,這些屬性和參數也可以被你的混合元件所使用。

3. 你也可以建立時間監聽器去監聽新元件中View 類觸發的事件,例如,對List 選項單擊事件的監聽,你必須在此時間發生後更新你EditText 的值。

4. 你可能建立自己的一些屬性,帶有通路和修改方法。例如,允許設定EditText

初始值并且提供通路它的方法。

5. 在Layout 的派生類中,你沒有必要去重載onDraw()和onMeasure()方法,因為Layout 會有比較好的預設處理。但是,如果你覺得有必要你也可以重載它。

6. 你也可能重載一些on 系列函數,例如通過onKeyDown()的重載,你可以通過按某個鍵去選擇清單中的對應的值。

總之,把Layout 類作為基類有下面幾個優點:

· 正如activity 一樣,你也可以通過XML 檔案去聲明你的新元件,或者你也可以在代碼中嵌套。

· onDraw()函數和onMeasure()函數是沒有必要重載的,兩個函數已經做得

很好了。

· 你可以很快的建立你的混合元件,并且可以像單一元件那樣使用。

混合元件的例子(Examples of Compound Controls)

In the API Demos project 在API Demos 工程中,有兩個List 類的例子——Example 4

和Example 6,裡面的SpeechView 元件是從LinearLayout 類派生過來,實作顯示演講顯示功能,對應的原代碼是List4.java 和List6.java。

調整現有元件(Tweaking an Existing Component)

在某些情況下,你可能有更簡單的方法去建立你的元件。如果你應經有了一個非常類似的元件,你所要做的隻是簡單的從這個元件派生出你的元件,重在其中一些有必要修改的方法。通過完全自定義元件的方法你也可以同樣的實作,但通過沖View 派生産生新的元件,你可以簡單擷取一些已經存在的處理機制,這些很可能是你所想要的,而沒有必要從頭開始。

例如,在SDK 中有一個NotePad 的例子(NotePad application )。該例子示範了很多Android 平台實用的細節,例如你會學到從EditView 派生出能夠自動換行的記事本。這還不是一個完美的例子,因為相比早期的版本來說,這些API 已經感變了很多,但它确實說明了一些問題。

如果你還未檢視該程式,現在你就可以在Eclipse 中導入記事本例程(或僅通過提供的連結檢視相應的源代碼)。特别是檢視NoteEditor.java 中的MyEditText 的定義。

下面有幾點要注意的地方:

1. 聲明(The Definition)

這個類是通過下面一行代碼來定義的:

public static class MyEditText extends EditText

o 它是定義在NoteEditor activity 類裡面的,但是它是共有的

(public),是以如果有必要,它可以通過NoteEditor.MyEditText 從

NoteEditor 外面來調用。

o 它是static 類(靜态類),意味着不會出現所謂的通過父類通路資料的

“虛态方法”, 這樣就使該類成為一個可以不嚴重依賴NoteEditor 的單獨

類。對于不需要從外部類通路的内聯類的建立,這是一個很清晰地思路,保

證所産生的類很小,并且允許它可以被其他的類友善的調用。

o 它是EditText 類的擴充,它是我們選擇的用來自定義的父類。當我們

完成以後,新的類就可以作為一個普通的EditText 來使用。

2. 類的初始化

一般來說,父類是首先調用的。進一步來說,這不是一個預設的構造函數,而是

一個帶參數的構造函數。因為EditText 是使用從XML 布局檔案提取出來的參

數進行建立,是以我們的構造函數也要取出參數并且将這些參數傳遞給父類。

3. 方法重載

在本例中,僅對onDraw()一個方法進行重載。但你可以很容易地為你的定制組

件重載其他需要的方法。

對于記事本例子來說,通過重載onDraw()方法我們可以在EidtView 的畫布

(canvas)上繪制藍色的線條(canvas 類是通過重寫的onDraw()方法傳遞)。

該函數快要結束時要調用super.onDraw()函數。父類的方法是應該調用,但

是在這個例子裡面,我們是在我們劃好了藍線之後調用的。

4. 使用定制元件

現在,我們已經有自己定制的元件了,但是應該怎樣使用它呢?在記事本例子中,

定制的元件直接在預定義的布局檔案中使用,讓我們看一看res/layout 目錄

中的note_editor.xml 檔案。

<view

xmlns:android="http://schemas.android.com/apk/res/android"

class="com.android.notepad.NoteEditor$MyEditText"

id="@+id/note"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:background="@android:drawable/empty"

android:padding="10dip"

android:scrollbars="vertical"

android:fadingEdge="vertical" />

o 該自定義元件在XML 中是作為一個一般的View 類來建立的,并且是通過

全路徑包來描述的。注意這裡内聯類是通過NoteEditor$MyEditText 來

表示的,這是Java 程式設計中引用内聯類的标準方法。

o 在定義中的其他屬性和參數将傳遞給定制元件的構造函數,然後才傳到

EditText 構造函數中,是以這些參數也是你使用EditText 元件的參數。注意,

這裡你也可以增加你自己的參數,我們将在下面讨論這個問題。

這就是你全部需要做的,誠然這是一個簡單的例子。但問題的關鍵是:你的需求有多複雜,那麼你的自定義元件就有多麼複雜。

一個更為複雜的元件可能需要重載更多的on 系列函數,并且還要很多特有的函數來充分實作自定義元件的功能。唯一的限制就是你的想象力和你需要元件去執行什麼工作。

現在開始你的元件化之旅吧

如你所見,Android 提供了一種精巧而又強大的元件模型,讓你盡可能的完成你的工作。從簡單的元件調整到元件混合,甚至完全自定義元件,靈活的運用這些技術,你應該可以得到一個完全符合你外觀要求的的Android 程式

Android 平台的可選API

Android 适用于各種各樣的手機,從最低端直到最高端的智能手機。核心的Android API在每部手機上都可使用,但任然有一些API 接口有一些特别的适用範圍:這就是所謂的“可選API”。這些API 之是以是“可選的”,主要是因為一個手持裝置并不一定要完全支援這類API,甚至于完全不支援。例如,一個手持裝置可能沒有GPS 或Wi-FI 的硬體。在這個條件下,這類功能的API 任然存在,但不會以相同的方式來工作。例如Location API 任然在沒有GPS 的裝置上存在,但極有可能完全沒有安裝功能提供者,意味着這類API 就不能有效地使用。

你的應用應該無障礙地運作或連接配接在一個可能不支援你API 的裝置,因為你的裝置上有這些上層接口(the classes)。當然執行起來可能什麼也不會做,或者抛出一個異常。

每個API 會做些什麼我們可以參考這些API 的說明文檔,你應該編寫你的程式來适當的處理這類問題。

Wi-Fi API

Wi-Fi API 為應用程式提供了一種與那些帶有Wi-FI 網絡接口的底層無線堆棧互相交流的手段。幾乎所有的請求裝置資訊都是可利用的,包括網絡的連接配接速度、IP 位址、目前狀态等等,還有一些其他可用網絡的資訊。一些可用的互動操作包括掃描、添加、儲存、結束和發起連接配接。

Wi-Fi API 在 android.net.wifi 包中。

定位服務(Location-Based Services)

定位服務允許軟體擷取手機目前的位置資訊。這包括從全球定位系統衛星上擷取地理位置,但相關資訊不限于此。例如,未來其他定位系統可能會營運,屆時,對其相應的API 接口也會加入到系統中。定位服務的API 在android.location 包中。

多媒體API(Media APIs)

多媒體API 主要用于播放媒體檔案。這同時包括對音頻(如播放MP3 或其他音樂檔案以及遊戲聲音效果等)和視訊(如播放從網上下載下傳的視訊)的支援,并支援"播放URI位址"(Note:URI 即是統一資源識别位址)模式-在網絡上直接播放的流媒體。技術上來說,多媒體API 并不是“可選的”,因為它總是要用到。但是不同的硬體環境上面可能有不同的編解碼的硬體機制,因而它又是“可選的”。

多媒體API 在 android.media 包中。

基于OpenGL 的3D 圖形(3D Graphics with OpenGL)

Android 的主要使用者接口架構是一個典型的面向控件的類繼承系統。但不要讓表面的情況迷惑了你,因為在它下面是一種非常快的2D 和3D 組合的圖形引擎,并且支援硬體加速。用來通路平台3D 功能的API 接口是OpenGL ES API。和多媒體API 一樣,OpenGL 也不是嚴格意義上的“可選”,因為這些API 會總是存在并且實作那些固定的功能。但是,一些裝置可能有硬體加速環節,使用它的時候就會影響你的應用程式的表現。

l        用于HTTP請求中的常用頭

•         Accept: text/html,image/*  

•         Accept-Charset: ISO-8859-1

•         Accept-Encoding: gzip,compress

•         Accept-Language: en-us,zh-cn

•         Host: www.it315.org:80

•         If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT

•         Referer: http://www.it315.org/index.jsp

•         User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)

•         Cookie

•         Connection: close/Keep-Alive  

•         Date: Tue, 11 Jul 2000 18:23:51 GMT

l        HTTP請求中的常用響應頭

•         Location: http://www.it315.org/index.jsp

•         Server:apache tomcat

•         Content-Encoding: gzip

•         Content-Length: 80

•         Content-Language: zh-cn

•         Content-Type: text/html; charset=GB2312

•         Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT

•         Refresh: 1;url=http://www.it315.org

•         Content-Disposition: attachment; filename=aaa.zip

•         Transfer-Encoding: chunked

•         Set-Cookie:SS=Q0=5Lb_nQ; path=/search

•         Expires: -1

•         Cache-Control: no-cache

•         Pragma: no-cache  

•         Connection: close/Keep-Alive  

•         Date: Tue, 11 Jul 2000 18:23:51 GMT

l        通用資訊頭指既能用于請求,又能用于響應的一些消息頭。

•         Cache-Control: no-cache

•         Pragma: no-cache  

•         Connection: close/Keep-Alive  

•         Date: Tue, 11 Jul 2000 18:23:51 GMT

資料的存儲與通路

接口程式設計會對軟體有影響,少用接口程式設計。

多用内部類,少建立類。

Androd的檔案輸出流:context.openFileOutput(“檔案名稱(不能帶有路徑)”,操作模式);

操作模式:context.MODE_PRIVATE:如果有新的檔案内容會覆寫原來的檔案内容,而且隻能被建立這個檔案的應用所通路。其它應用是不能被通路,因為私有了。而且預設也是這種私有的操作模式。如果檔案不存在,會自動建立。如果這個檔案已經存在,這個模式會把新的幾個問題覆寫舊的内容。

openFileOutput()方法的第一參數用于指定檔案名稱,不能包含路徑分隔符“/” ,如果檔案不存在,Android 會自動建立它。建立的檔案儲存在/data/data/<package name>/files目錄,如: /data/data/cn.itcast.action/files/itcast.txt ,通過點選Eclipse菜單“Window”-“Show View”-“Other”,在對話視窗中展開android檔案夾,選擇下面的File Explorer視圖,然後在File Explorer視圖中展開/data/data/<package name>/files目錄就可以看到該檔案。

SAX解析xml文檔:

//創 建SAX工廠

SAXParserFactory factory = SAXParserFactory.newInstance();

//建立解析器

SAXParser saxParser = factory.newSAXParser();

建立一個類myDefaultHandler,繼承DefaultHandler

saxParser.parse(inputStream,handler);

在myDefaultHandler類中重寫charactors()方法、startDocument()方法、startElement()方法、endElement()方法。

在startDocument方法中建立一個存xml檔案資料的集合對象。

在startElement方法中判斷開始标簽是否為我們要的資料的開始标簽,如果是就建立對象來存取。還要用一個變量記住所經過的标簽。

在charactors方法中,判斷startElement元素的标簽是否為内容字元串的開始标簽,如果是就可以new String();獲得裡面的資料。

在endElement方法中,把記住标簽的變量置空。最後還要判斷結束标簽是否為一個完整對象的結束标簽,如果是就可以把這個對象加到集合中,再把這個對象置空。

DOM解析xml文檔

先用獲得讀取xml檔案的流。

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

DocumentBuilder builder = factory.newDocumentBuilder();

Document dom = builder.parser(inputStream);

Pull解析xml文檔

Pull解析器也是基于事件驅動解析。

思路:首先獲得pull解析器,然後設定讀入的流setIput(inputStream,”UTF-8”);就開始觸發第一個事件。得到一個int常量,然後就是用while循環,判斷是否為結束文檔事件,隻要不是結束文檔事件,就不要結束。在這個循環中完成資料的獲得。

代碼如下:

public static List<Person> readXML(InputStream inStream) {

         XmlPullParser parser = Xml.newPullParser();

         try {    parser.setInput(inStream, "UTF-8");

         int eventType = parser.getEventType();

         Person currentPerson = null;

         List<Person> persons = null;

         while (eventType != XmlPullParser.END_DOCUMENT) {

                   switch (eventType) {

                   case XmlPullParser.START_DOCUMENT://文檔開始事件,可以進行資料初始化處理

                            persons = new ArrayList<Person>();

                            break;

                   case XmlPullParser.START_TAG://開始元素事件

                            String name = parser.getName();

                            if (name.equalsIgnoreCase("person")) {

                                     currentPerson = new Person();

                                     currentPerson.setId(new Integer(parser.getAttributeValue(null, "id")));

                            } else if (currentPerson != null) {

                                     if (name.equalsIgnoreCase("name")) {

                            currentPerson.setName(parser.nextText());// 如果後面是Text元素,即傳回它的值

                                     } else if (name.equalsIgnoreCase("age")) {

                                               currentPerson.setAge(new Short(parser.nextText()));

                                     }

                            }

                            break;

                   case XmlPullParser.END_TAG://結束元素事件

                            if (parser.getName().equalsIgnoreCase("person") && currentPerson != null) {

                                     persons.add(currentPerson);

                                     currentPerson = null;

                            }break;

                   }

                   eventType = parser.next();

         }

         inStream.close();

         return persons;

         } catch (Exception e) {

                   e.printStackTrace();

         }

         return null;

}

生成xml檔案

代碼如下:

public static String writeXML(List<Person> persons, Writer writer){

    XmlSerializer serializer = Xml.newSerializer();

try {

//也可以輸出要指定的SD卡檔案

File file = new File(Enviroment.getExternalStorageDirectory(),”XXX.xml”);

FileOutputStream out = new FileOutputStream(file);

        serializer.setOutput(writer);

        serializer.startDocument("UTF-8", true);

      //第一個參數為命名空間,如果不使用命名空間,可以設定為null

       serializer.startTag("", "persons");

        for (Person person : persons){

            serializer.startTag("", "person");

            serializer.attribute("", "id", person.getId().toString());

            serializer.startTag("", "name");

            serializer.text(person.getName());

            serializer.endTag("", "name");

            serializer.startTag("", "age");

            serializer.text(person.getAge().toString());

            serializer.endTag("", "age");

            serializer.endTag("", "person");

        }

        serializer.endTag("", "persons");

        serializer.endDocument();

        return writer.toString();

    } catch (Exception e) {

        e.printStackTrace();

    }

    return null;

}

第六天

第三個視訊:

使用廣播:

要繼承廣播接收者類:BroadcastReceiver,如:

,隻要繼承這個類,就能完成對短信的竊聽。

訂閱廣播接收者有兩種方式:其一:

SmsMessage能把PDU(移動的資料格式)資料,轉換成短能被閱讀的資訊。代碼如:

短信接收權限:

廣播被分為兩種不同的類型:“普通廣播(Normal broadcasts)”和“有序廣播(Ordered broadcasts)”。前者是完全異步的,所有接收者(邏輯上)都在同一時刻運作,對消息傳遞的效率而言這是很好的做法,但缺點是:接收者不能傳遞處理結果給下一個接收者,并且無法終止廣播Intent的傳播;然而後者是逐個執行接收者——系統會按照接收者聲明的優先級别(聲明在intent-filter元素的android:priority屬性中,數越大優先級别越高,取值範圍:-1000到1000。也可以調用IntentFilter對象的setPriority()進行設定),按順序逐次執行。

Context.sendBroadcast()

   發送的是普通廣播,所有訂閱者都有機會獲得并進行處理。

Context.sendOrderedBroadcast()

   發送的是有序廣播,系統會根據接收者聲明的優先級别按順序逐個執行接收者,前面的接收者有權終止廣播,如果廣播被前面的接收者終止,後面的接收者就再也無法擷取到廣播。對于有序廣播,前面的接收者可以将處理結果存放進廣播Intent,然後傳給下一個接收者。

在Android中,程式的響應(Responsive)被活動管理器(Activity Manager)和視窗管理器(Window Manager)這兩個系統服務所監視。當BroadcastReceiver在10秒内沒有執行完畢,Android會認為該程式無響應。是以在BroadcastReceiver裡不能做一些比較耗時的操作,否側會彈出ANR(Application No Response)的對話框。如果需要完成一項比較耗時的工作,應該通過發送Intent給Service,由Service來完成。而不是使用子線程的方法來解決,因為BroadcastReceiver的生命周期很短(在onReceive() 執行後BroadcastReceiver 的執行個體就會被銷毀),子線程可能還沒有結束它就先結束了。當然如果BroadcastReceiver結束了,它的宿主程序還在運作,子線程還會繼續執行。但宿主程序此時很容易在系統需要記憶體時被優先殺死,因為它屬于空程序(沒有任何活動元件的程序)。

每次廣播消息到來時都會建立BroadcastReceiver執行個體并執行onReceive() 方法。

開發調用者跟服務進行通信的應用:

開發線上音樂播放器,與伺服器進行播放和停止的操作,就可以用到。

比如:多線程上傳下載下傳。