Android學習筆記
Android platform是一個用于開發移動程式的軟體包,它包括了作業系統、中間件及一些關鍵應用。開發者能使用android SDK為Androidplatform開發應用,這些應用使用JAVA語言書寫,運作在虛拟機Dalvik(一個專為手機程式開發的基于linux核心的JAVA虛拟機)。
1 什麼是Android
1.1 Android的特性
ü 應用架構,讓一些基礎設施得以重用
ü Dalvik虛拟機,專為開發移動程式優化
ü 內建的浏覽器,(基于WebKit引擎)
ü 優化的圖形庫,(2D圖形庫以及基于OpenGL ES 1.0規範的3D圖形庫)
ü SQLite,用于結構化資料的存儲,是一個資料庫
ü 多媒體支援,支援多種音頻,視訊格式(MPEG4, H.264, MP3, AAC, AMR, JPG, PNG, GIF)
ü GSM技術(依賴具體硬體)
ü Bluetooth, EDGE, 3G, WiFi(依賴具體硬體)
ü Camera, GPS, compass,accelerometer(依賴具體硬體)
ü 豐富的開發環境(DEBUG工具,記憶體及性能工具,Eclipse的插件等)
1.2 Android的架構
Android作業系統的子產品如下:
Ø 應用:Android已內建了一些應用,如郵件用戶端,SMS程式,月曆,地圖,浏覽器等
Ø 應用架構:
Ø 程式庫:
Ø 例行程式
Ø Linux核心
2 Andvoid起步
2.1 開發環境配置
使用Eclipse + AndroidDevelopment Tools (ADT)插件。在Help > Software Updates > Find and Install....中鍵入更新位址:
https://dl-ssl.google.com/android/eclipse/
2.2 運作第一個Andvoid程式
使用Eclipse插件
1、建立的工程類型為:AndvoidProject
2、建立LaunchConfiguration. Run > Open Run Dialog... or Run > Open Debug Dialog
指令行運作程式
1、建立工程 activityCreator your.package.name.ActivityName
2、編譯。在build.xml所在的目錄ant一下。
3、啟動模拟器. 運作指令:emulator
4、在模拟器中,切換到主螢幕。
5、在指令行輸入: adbinstall myproject/bin/<appname>.apk将其上載至模拟器。
6、在模拟器中選擇程式并啟動。
Android需要專門的編譯工具來正确的編譯資源檔案和Android程式的其他部分。基于此,你需要為你的程式準備一個專門的編譯環境。
Andorid的編譯過程通常包括編譯XML和其他資源檔案、建立正确的輸入格式。經過編譯的Android程式是一個.apk檔案,.apk檔案是一個壓縮檔案,它其中包含了.dex檔案、資源檔案、rawdata檔案和其他檔案。
Andoriod暫時還不支援用本地代碼(C/C++)開發第三方程式。
移除Andorid程式
要移除你安裝在模拟器上的程式,你需要通過run adb并删除相應的.apk檔案。通過adb shell指令在模拟器上打開一個UNIX shell,進入目錄data/app/,通過指令rm 你程式的名稱.apk來移除檔案。
2.3 調試程式
Andvoid用于調試的手段有:
DDMS,DDMS是一個圖形化的程式,支援端口轉發(是以你可以在程式中設定斷點),支援模拟器上的截屏,支援線程和堆棧資訊和其他的一些特性。
Logcat,Dump一份系統消息的日志。這些消息包括模拟器抛出錯誤時的堆棧跟蹤。
Android Log, 列印日志的類,用來将消息寫入模拟器上的日志檔案中。如Log.v()用于列印verbose級别的日志
Traceview,Android可以儲存一個日志用來記錄被調用的方法以及該方法被調用的次數,通過Traceview你可以在一個圖形化的界面中檢視這個日志檔案。
可接解設定emulator的設定以友善調試,
模拟器上調試和測試的設定
Android提供了衆多的設定使你可以更容易的調試和測試程式。要進入開發設定頁面,在模拟器中轉到Dev Tools > Development Settings。在該設定頁面有以下選項:
· Debugapp:選擇要調試的程式。你不需要設定其關聯至調試器,但是設定這個值有兩個效果:
o 在調試的時候,如果你在一個斷點處暫停了過長的時間,這個設定會防止Android抛出一個錯誤
o 這個設定使你可以選擇“等待調試器”選項,使程式隻有在調試器關聯上之後才啟動
· Waitfor Debugger:阻塞所選的程式的加載直到有調試器關聯上,這樣你就可以在onCreate()中設定斷點,這對于調試一個Activity的啟動程序是非常重要的。當你對該選項進行了更改,任何正在運作的程式的執行個體都會被終止。你隻有在上面的選項中選擇了一個調試程式才能夠選中該選項。你一也可以在代碼中添加waitForDebugger()來實作同樣的功能。
· Immediatelydestroy activities:告訴系統一旦一個activity停止了就銷毀該activity(例如當Android釋放記憶體的時候)。這對于測試代碼onFreeze(Bundle)/onCreate(android.os.Bundle)是非常有用的,否則會比較困難。如果你的程式沒有儲存狀态,那麼選擇這個選項很可能會引發很多問題。
· Showscreen updates:對于任何正在被重繪的screen sections都會在其上閃現一個粉紅色的矩形。這對于發現不必要的screen繪制是很有必要的。
· ShowCPU usage:在螢幕上方顯示CPU資訊,顯示有多少CPU資源正在被使用。上方紅色條顯示總的CPU使用率,它下方綠色的條顯示CPU用在compositing the screen上的時間。注意:在沒有重新開機模拟器之前,一旦你開啟了該功能就不能關閉。
· Showscreen FPS:顯示目前的幀率。這對于檢視遊戲達到的總的幀率是非常有用的。注意:在沒有重新開機模拟器之前,一旦你開啟了該功能就不能關閉。
· Showbackground:當沒有activity screens可見時,顯示一個背景模式。一般是不會出現的,僅僅在Debug的時候會出現。
設定的選項在模拟器重新開機之後仍然有效,如果要取消設定的選項,在取消設定以後還要重新開機模拟器,才能生效。
2.4 andvoid中的概念
一個andvoid應用包括四個部分:
n Activity活動 (個人認為 :類似于JSP, 也相當于SWT中的Shell, View則相當于wegiet)
n IntentReceiver (個人認為 :類似于Struts action)
n Service (個人認為 :類似于Servlet)
n ContentProvider (個人認為 :用于持久化)
用上面哪些元件,要在AndroidManifest.xml檔案中聲明。
1、 Activity. 一個activity是應用中的一個單一的螢幕,它繼承自Activity類,它将顯示由Views組成的UI以及響應事件。(個人了解,相當于JSP)
2、 Intent與Intent Filters.Intent用于從一個螢幕跳到别一個螢幕,描述一個應用想做什麼,它的資料結構包括action與data兩部分,action如MAIN、VIEW、PICK、EDIT等等, data被表達成一個URI;IntentFilter相當于告訴跳到哪個activity,;IntentReceiver用于響應事件,雖然它不顯示UI,但它可以用NotificationManager去通知使用者。它需要在AndroidManifest.xml檔案中注冊,或者是用Context.registerReceiver()寫死。
3、 Service是一段有生命周期的無UI的代碼。
4、 Content Provider, 持久化,例如存儲在檔案系統中,或者存儲在SQLite資料庫中。
2.5 例子
1,下載下傳例子工程。http://code.google.com/android/intro/codelab/NotepadCodeLab.zip
例如:Notepadv1工程是問題工程,Notepadv1Solution則是相應的解決工程。
2,3個例子的函義如下:
通過練習1>
1. 資料庫對象的例子程式
2. 如何在一個Activity初始化的時候建立資料庫對象
3. 如何為一個Activity建立一個pop菜單
4. 如何得到一個使用者在POP菜單中選擇的ITEM 的ID
5. 如何向一個ListView中寫入資料
通過練習2>
1. 如何取得資料集中被選擇資料行的ID, 注意rows是java的一個listarray對象,是以它有它的getSelection()的方法
2. 如何調用一個SubActivity以及在調用一個SubActivity之前應該做些事情。
3. 要實作一個方法, 當SubActivity傳回後,應該做些什麼
4. 關于layout。由于Android采用MVC的模式, 是以螢幕的布局采用了XML進行定義。一個好的布局會讓程式顯的更漂亮。可以參考http://code.google.com/android/reference/view-gallery.html,有很多的布局模式可供利用。
5. 如何建立一個開始的時候代碼中不含onCreate()的類,因為到目前為止還沒有建立過自己的類,以前看到的類和修改的方法都是已經寫好的了。不過這個類也是一個從android.app.Activity繼承的。建立後還是要通過Source Override一個onCreate的
6. 從一個SubActivity傳回時,在傳回之前應該做哪些事情
7. 如何在AndroidManifest.xml中聲明一個新的Activity
8. 如何建立一個内部隐含的onClickLister及實作其應有的功能。
通過練習3>
1. 除了onCreate()還有哪些和lifecycle有關的方法
a.onFreeze():
b.onPause()
c.onResume()
等等…
2. 大部分程式都有必要考慮lifecycle的問題,應該把下面的這個圖記住:
3. 為了不丢失需要儲存的資訊,要明确的了解Activity的各個狀态,并相應的給出在各個狀态的行為。
4.開始的時候對于onFreeze()和onPause()有些了解上的困難,要多讀幾遍DOC來會了解。通過額外練習>
進一步的了解lifecycle和通過手機按鍵進行操作時的時間和程式狀态關系
另外,SDK目錄下也有一些例子。
練習目标:
1. 使用ListActivities,并使用菜單
2. 學習使用操作SQLite資料庫
3. 使用ArrayAdapter綁定資料到ListView中
4. 掌握一些基本的操作,如菜單的顯示,菜單指令的處理,增加資料項等。
第一步:
在SDK中下載下傳獲得Notepadv1的代碼,并導入到Eclipse中。導入步驟:
a. 在Package Explorer中,右鍵選擇Import.../General/Existing Projects into Workspace
b. 點Browse按鈕,選擇Notepadv1的目錄,并點OK
c. 你将會看到Notepadv1被列在項目區中,預設會被打勾,如果沒有打勾,請手動勾上。
d. 點Finish
e. Notepadv1将被列在Package Explorer中
f. 如果有提示關于AndroidManifest.xml的錯誤,請選中此項目,并右鍵選擇Android Tools->Fix Project,他将會自動幫你修複錯誤。
第二步:
看一下資料庫操作類:DBHelper,還是比較簡單的,自己看去 :)。
第三步:
打開res/layout/notepad_list.xml這個檔案,快速的看下就可以了:
a.<?xml version="1.0" encoding="utf-8"?>,XML檔案的固定頭
b.一個Layout的定義,這裡是 LinearLayout,但不一定是這個,可以是其他的Layout
第四步:
在上面的那個檔案中加入:
<ListView id="@id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView id="@id/android:empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_notes"/>
a.ListView和TextView不會同是顯示,如果沒有資料,則預設顯示TextView(這個View裡會顯示一個字元串)。如果有資料,則會顯示ListView。
[email protected]是預設的關鍵字,XML解析器将會自動替換這個符号後面的ID
c.android:list 和android:empty 是android平台預定義好的ID,如果你想顯示空的TextView,可以調用setEmptyView().
第五步:
建立一個新檔案res/layout/notes_row.xml,檔案内容如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView id="@+id/text1"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
然後儲存,R.java下将會自動重新整理。
第六步
改變繼承類
public class Notepadv1 extends ListActivity
第七步:
看一下這三個事件:
onCreate():界面初始化的時候調用
onCreateOptionsMenu():按了Menu按鈕的時候調用
onOptionsItemSelected() :選擇了一個菜單項的時候調用
第八步:
改寫OnCreate函數:
private DBHelper dbHelper;
@Override
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.notepad_list);
dbHelper = new DBHelper(this);
fillData();
}
第九步:
在strings.xml中增加:<stringname="menu_insert">Add Item</string>
并在Notepadv1類中加入:publicstatic final int INSERT_ID = Menu.FIRST;
改寫onCreateOptionsMenu()
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean result =super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID,R.string.menu_insert);
return result;
}
第十步:
改寫onOptionsItemSelected()
@Override
public boolean onOptionsItemSelected(Item item) {
switch (item.getId()) {
case INSERT_ID:
createNote();
break;
}
returnsuper.onOptionsItemSelected(item);
}
第十一步:
實作兩個函數:
private void createNote() {
String noteName = "Note "+ noteNumber++;
dbHelper.createRow(noteName,"");
fillData();
}
private void fillData() {
// We need a list of strings for thelist items
List<String> items = newArrayList<String>();
// Get all of the rows fromthe database and create the item list
List<Row> rows =dbHelper.fetchAllRows();
for (Row row : rows) {
items.add(row.title);
}
// Now create an array adapter andset it to display using our row
ArrayAdapter<String> notes =
newArrayAdapter<String>(this, R.layout.notes_row, items); //指定notes_row視圖,作為資料容器。
setListAdapter(notes);
}
第十二步:
運作:RunAs -> Android Application
2.6 開發工具
2.6.1 仿真器
仿真器是在計算機中運作的一個虛拟的移動裝置,用它來設計、調試應用。
2.6.2 ADT
ADT是一個用于開發andvoid應用的确良eclipse插件,它友善了我們的開發。例如,它讓我們友善從eclipse内部通路DDMS工具(可用于截屏,管理port-forwarding,設斷點,檢視線程和程序資訊)
2.6.3 DDMS
DDMS(Dalvik Debug Monitor Service)和Dalvik虛拟機內建,将在IDE和模拟器起到一個轉發服務。用它能管理仿填器或者裝置的程序,并且輔助調試。你可用它殺死程序,選擇某一程序去DEBUG,産生TRACE資料,檢視堆和線程資訊等等。
2.6.4 ADB
ADB(Andvoid Debug Bridge)。在指令行操作。它能安裝.apk檔案到仿真器上等等。用于将檔案發送到仿真器。adb(Android Debug Bridge)是Android提供的一個通用的調試工具,借助這個工具,我們可以管理裝置或手機模拟器的狀态。還可以進行以下的操作:
1、快速更新裝置或手機模拟器中的代碼,如應用或Android系統更新;
2、在裝置上運作shell指令;
3、管理裝置或手機模拟器上的預定端口;
4、在裝置或手機模拟器上複制或粘貼檔案;
以下為一些常用的操作:
1、安裝應用到模拟器:
adb install
比較郁悶的是,Android并沒有提供一個解除安裝應用的指令,隻能自己手動删除:
adb shell
cd /data/app
rm app.apk
2、進入裝置或模拟器的shell:
adb shell
通過上面的指令,就可以進入裝置或模拟器的shell環境中,在這個Linux Shell中,你可以執行各種Linux的指令,另外如果隻想執行一條shell指令,可以采用以下的方式:
adb shell [command]
如:adb shell dmesg會列印出核心的調試資訊。
3、釋出端口:
你可以設定任意的端口号,做為主機向模拟器或裝置的請求端口。如:
adb forward tcp:5555 tcp:8000
4、複制檔案:
你可向一個裝置或從一個裝置中複制檔案,
複制一個檔案或目錄到裝置或模拟器上:
adb push
如:adb push test.txt /tmp/test.txt
從裝置或模拟器上複制一個檔案或目錄:
adb pull
如:adb pull /addroid/lib/libwebcore.so.
5、搜尋模拟器/裝置的執行個體:
取得目前運作的模拟器/裝置的執行個體的清單及每個執行個體的狀态:
adb devices
6、檢視bug報告:
adb bugreport
7、記錄無線通訊日志:
一般來說,無線通訊的日志非常多,在運作時沒必要去記錄,但我們還是可以通過指令,設定記錄:
adb shell
logcat -b radio
8、擷取裝置的ID和序列号:
adb get-product
adb get-serialno
9、通路資料庫SQLite3
adb shell
sqlite3
2.6.5 aapt
Aapt(Andvoid Asset Packaging Tool),用于建立.apk檔案。
2.6.6 aidl
Aidl(Andvoid Interface Description Language)用于産生代碼。
2.6.7 sqlite3
用于通路SQLite資料檔案。
2.6.8 Traceview
檢視LOG資訊
2.6.9 mksdcard
Helps you create a disk image that youcan use with the emulator, to simulate the presence of an external storage card(such as an SD card
2.6.10 dx
Dx工具用于重寫.class位元組碼到Andvoid位元組碼
2.6.11 activityCreator
用于産生ant檔案的腳本。當然,若用eclipse插件就不需要它了。
2.7 Andvoid應用的生命周期
在大部份情況下,每個Android應用都将運作在自己的Linux程序當中。當這個應用的某些代碼需要執行時,程序就會被建立,并且将保持運作,直到該程序不再需要,而系統需要釋放它所占用的記憶體,為其他應用所用時,才停止。
Android一個重要并且特殊的特性就是,一個應用的程序的生命周期不是由應用自身直接控制的,而是由系統,根據運作中的應用的一些特征來決定的,包括:這些應用對使用者的重要性、系統的全部可用記憶體。
對于應用開發者來說,了解不同的應用元件(特别是Activity、Service、IntentReceiver)對應用程序的生命周期的影響,這是非常重要的。如果沒有正确地使用這些元件,将會導緻當應用正在處理重要的工作時,程序卻被系統消毀的後果。
對于程序生命周期,一個普遍的錯誤就是:當一個Intent Receiver在它的onReceiveIntent()方法中,接收到一個intent後,就會從這個方法中傳回。而一旦從這個方法傳回後,系統将會認為這個Intent Receiver不再處于活動狀态了,也就會認為它的宿主程序不需要了(除非宿主程序中還存在其它的應用元件)。進而,系統随時都會消毀這個程序,收回記憶體,并中止其中還在運作的子線程。問題的解決辦法就是,在IntentReceiver中,啟動一個Service,這樣系統就會知道在這個程序中,還有活動的工作正在執行。
為了決定在記憶體不足情況下消毀哪個程序,Android會根據這些程序内運作的元件及這些元件的狀态,把這些程序劃分出一個“重要性層次”。這個層次按順序如下:
1、前端程序是擁有一個顯示在螢幕最前端并與使用者做互動的Activity(它的onResume已被調用)的程序,也可能是一個擁有正在運作的IntentReceiver(它的onReceiveIntent()方法正在運作)的程序。在系統中,這種程序是很少的,隻有當記憶體低到不足于支援這些程序的繼續運作,才會将這些程序消毀。通常這時候,裝置已經達到了需要進行記憶體整理的狀态,為了保障使用者界面不停止響應,隻能消毀這些程序;
2、可視程序是擁有一個使用者在螢幕上可見的,但并沒有在前端顯示的Activity(它的onPause已被調用)的程序。例如:一個以對話框顯示的前端activity在螢幕上顯示,而它後面的上一級activity仍然是可見的。這樣的程序是非常重要的,一般不會被消毀,除非為了保障所有的前端程序正常運作,才會被消毀。
3、服務程序是擁有一個由startService()方法啟動的Service的程序。盡管這些程序對于使用者是不可見的,但他們做的通常是使用者所關注的事情(如背景MP3播放器或背景上傳下載下傳資料的網絡服務)。是以,除非為了保障前端程序和可視程序的正常運作,系統才會消毀這種程序。
4、背景程序是擁有一個使用者不可見的Activity(onStop()方法已經被調用)的程序。這些程序不直接影響使用者的體驗。如果這些程序正确地完成了自己的生命周期(詳細參考Activity類),系統會為了以上三種類型程序,而随時消毀這種程序以釋放記憶體。通常會有很多這樣的程序在運作着,因些這些程序會被儲存在一個LRU清單中,以保證在記憶體不足時,使用者最後看到的程序将在最後才被消毀。
5、空程序是那些不擁有任何活動的應用元件的程序。保留這些程序的唯一理由是,做為一個緩存,在它所屬的應用的元件下一次需要時,縮短啟動的時間。同樣的,為了在這些緩存的空程序和底層的核心緩存之間平衡系統資源,系統會經常消毀這些空程序。
當要對一個程序進行分類時,系統會選擇在這個程序中所有活動的元件中重要等級最高的那個做為依據。可以參考Activity、Service、IntentReceiver文檔,了解這些元件如何影響程序整個生命周期的更多細節。這些類的文檔都對他們如何影響他們所屬的應用的整個生命周期,做了詳細的描述。
2 開發應用
2.1 前端UI
2.1.1 .螢幕元素的層次
1. Views
一個View是android.view.View基礎類的一個對象,它是一個有螢幕上特定的一個矩形内布局和内容屬性的資料結構。一個View對象處理測量和布局,繪圖,焦點變換,滾動條,還有螢幕區域自己表現的按鍵和手勢。
View類作為一個基類為widget(窗體部件)服務,widget--是一組用于繪制互動螢幕元素的完全實作子類。Widget處理它們自己的測距和繪圖,是以你可以更快速地用它們去建構你的UI。可用到的widget包括Text,EditText,InputMethod,Button,RadioButton,Checkbox,和ScrollView。
2. Viewgroups
一個ViewGroup是一個android.view.Viewgroup類的對象。一個viewgroup是一個特殊的view對象,它的功能是去裝載和管理一組下層的view和其他viewgroup,Viewgroup讓你可以為你的UI增加結構并且将複雜的螢幕元素建構成一個獨立的實體。
Viewgroup類作為一個基類為layout(布局)服務,layout--是一組提供螢幕界面通用類型的完全實作子類。layout讓你可以為一組view建構一個結構。
3. A Tree-Structured UI
在Android平台上,你用view樹和viewgroup節點來定義一個Activity的UI,就如同下面圖表一樣。這個樹可以如你需要那樣簡單或者複雜,并且你可以使用Android的預定義widget和layout或者你自定義的view類型來建構它。
要将螢幕綁定一個樹以便于渲染,你的Activity調用它的setContentView()方法并且傳遞一個參數給根節點對象。一旦Android系統獲得了根節點的參數,它就可以直接通過節點來無效化,測距和繪制樹。當你的Activity被激活并且獲得焦點時,系統會通知你的activity并且請求根節點去測距并繪制樹,根節點就會請求它的子節點去繪制它們自己。每個樹上的viewgroup節點都為它的子節點的繪制負責。
正如之前提到的,每個view group都有測量它的有效空間,布局它的子對象,并且調用每個子對象的Draw()方法去繪制它們自己。子對象可能會請求獲得一個它們在父對象中的大小和位置,但是父對象對于每個子對象的大小和位置有最終的決定權。
4. LayoutParams:一個子對象如何指定它的位置和大小
每個viewgroup類都會使用一個繼承于Viewgroup.LayoutParams的嵌套類。這個子類包含了包含了定義一個子對象位置和大小的屬性類型,并且需适用于view group類。
要注意的是,每個LayoutParams子類都有它自己指派的文法。每個子元素必須定義适用于它們父對象的LayoutParams,盡管父對象可能會為子元素定義不同的LayoutParams。
所有的viewgroup都包括寬和高。很多還包括邊界的定義(margin和border)。你可以非常精确地描述寬和高,盡管你并不想經常這麼做。更多時候你希望你的view自行調整到适應内容大小,或者适應容器大小。
2.1.2.通用布局對象(最普遍的view groups)
1. FrameLayout (上下壓着的那種)
FrameLayout是最簡單的一個布局對象。它被定制為你螢幕上的一個空白備用區域,之後你可以在其中填充一個單一對象 — 比如,一張你要釋出的圖檔。所有的子元素将會固定在螢幕的左上角;你不能為FrameLayout中的一個子元素指定一個位置。後一個子元素将會直接在前一個子元素之上進行覆寫填充,把它們部份或全部擋住(除非後一個子元素是透明的)。
2. LinearLayout
LinearLayout以你為它設定的垂直或水準的屬性值,來排列所有的子元素。所有的子元素都被堆放在其它元素之後,是以一個垂直清單的每一行隻會有一個元素,而不管他們有多寬,而一個水準清單将會隻有一個行高(高度為最高子元素的高度加上邊框高度)。LinearLayout保持子元素之間的間隔以及互相對齊(相對一個元素的右對齊、中間對齊或者左對齊)。
LinearLayout還支援為單獨的子元素指定weight。好處就是允許子元素可以填充螢幕上的剩餘空間。這也避免了在一個大螢幕中,一串小對象擠成一堆的情況,而是允許他們放大填充空白。子元素指定一個weight值,剩餘的空間就會按這些子元素指定的weight比例配置設定給這些子元素。預設的weight值為0。例如,如果有三個文本框,其中兩個指定了weight值為1,那麼,這兩個文本框将等比例地放大,并填滿剩餘的空間,而第三個文本框不會放大。
Tip:為了在螢幕上建立一個按比例安排大小的layout,需要根據這個螢幕上每個元素将按什麼比例顯示,建立一個指定fill_parent,子元素的height或width為0,且為每一個子元素配置設定weight值的容器對象。
下面的兩個窗體采用LinearLayout,包含一組的元素:一個按鈕,幾個标簽,幾個文本框。兩個窗體都為布局做了一番修飾。文本框的width被設定為FILL_PARENT;其它元素的width被設定為WRAP_CONTENT。預設的對齊方式為左對齊。左邊的窗體沒有設定weight(預設為0);右邊的窗體的comments文本框weight被設定為1。如果Name文本框也被設定為1,那麼Name和Comments這兩個文本框将會有同樣的高度。
在一個水準排列的LinearLayout中,各項按他們的文本基線進行排列(第一列第一行的元素,即最上或最左,被設定為參考基線)。是以,人們在一個窗體中檢索元素時,就不需要七上八下地讀元素的文本了。我們可以在layout的XML中設定android:baselineAligned="false",來關閉這個設定。
3. TableLayout
TableLayout将子元素的位置配置設定到行或列中。一個TableLayout由許多的TableRow組成,每個TableRow都會定義一個row(事實上,你可以定義其它的子對象,這在下面會解釋到)。TableLayout容器不會顯示row、cloumns或cell的邊框線。每個row擁有0個或多個的cell;每個cell擁有一個View對象。表格由列和行組成許多的單元格。表格允許單元格為空。單元格不能跨列,這與HTML中的不一樣。下圖顯示了一個TableLayout,圖中的虛線代表不可視的單元格邊框。
列可以被隐藏,也可以被設定為伸展的進而填充可利用的螢幕空間,也可以被設定為強制列收縮直到表格比對螢幕大小。對于更詳細資訊,可以檢視這個類的參考文檔。
4. AbsoluteLayout
AbsoluteLayout可以讓子元素指定準确的x/y坐标值,并顯示在螢幕上。(0,0)為左上角,當向下或向右移動時,坐标值将變大。AbsoluteLayout沒有頁邊框,允許元素之間互相重疊(盡管不推薦)。我們通常不推薦使用AbsoluteLayout,除非你有正當理由要使用它,因為它使界面代碼太過剛性,以至于在不同的裝置上可能不能很好地工作。
5. RelativeLayout
RelativeLayout允許子元素指定他們相對于其它元素或父元素的位置(通過ID指定)。是以,你可以以右對齊,或上下,或置于螢幕中央的形式來排列兩個元素。元素按順序排列,是以如果第一個元素在螢幕的中央,那麼相對于這個元素的其它元素将以螢幕中央的相對位置來排列。如果使用XML來指定這個layout,在你定義它之前,被關聯的元素必須定義。
這是一個RelativeLayout例子,其中有可視的和不可視的元素。基礎的螢幕layout對象是一個RelativeLayout對象。
這個視圖顯示了螢幕元素的類名稱,下面是每個元素的屬性清單。這些屬性一部份是由元素直接提供,另一部份是由容器的LayoutParams成員(RelativeLayout的子類)提供。RelativeLayout參數有width,height,below,alignTop,toLeft,padding和marginLeft。注意,這些參數中的一部份,其值是相對于其它子元素而言的,是以才RelativeLayout。這些參數包括toLeft,alignTop和below,用來指定相對于其它元素的左,上和下的位置。
6. Summary of Important View Groups
重要View Group摘要,這些對象擁有UI子元素。一些提供可視的UI,另一些隻處理子元素的布局。
2.1.3 資料綁定
這部分會提及UI有的一些View groups,些組成對象是經典AdapterView類的子類.例如包括圖像,數層結構表現.這些對象有2個通用的任務: 資料層的填充與使用者操作選擇
1. 資料層填充
This is typically done by binding the class to an Adapter that gets its datafrom somewhere — either a list that the code supplies, or query results fromthe device's database.
// Get a Spinnerand bind it to an ArrayAdapter that
// references aString array.
private String[]fruit = {"apples", "oranges", "lemons"}
Spinner s1 =(Spinner)findViewById(R.id.fruitlist);
s1.setAdapter(newArrayAdapter<String>(this, mStrings));
// Load a Spinnerand bind it to a data query.
private String[]cols={android.provider.Contacts.PeopleColumns.NAME};
private Cursor cur= managedQuery(android.provider.Contacts.People.CONTENT_URL, cols, null, null);
s2.setAdapter(new CursorAdapter(cur,this));
2. 使用者操作選擇
設定類的AdapterView.OnItemClickListener方法監聽和捕捉使用者的操作事件.
// Create a messagehandling object as an anonymous class.
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener(){
public void onItemClick(AdapterView parent, View v,int position, long id)
{
// Display a messagebox.
showAlert("You've got anevent", "Clicked me!", "ok", false);
}
};
// Now hook into our object and set its onItemClickListener member
// to our class handler object.
mHistoryView = (ListView)findViewById(R.id.accept_button);
mHistoryView.setOnItemClickListener(mMessageClickedHandler);
2.1.4使用XML設計你的螢幕顯示
1. Android定義了大量的自定義元素,各自代表了特定的Android顯示子類。
2. 你可以象建立HTML文檔一樣,通過儲存在應用res/layout/目錄下的XML檔案中一系列的嵌套标簽來設計你的螢幕顯示。
3. 每個文檔描述一個android.view.View這個元素既可以是
一個簡單的顯示元素,也可以是一個在子節點中包含了一個集合的版面設計的元素,當Android編譯你的應用時,他将每個檔案都編譯進android系統。你可以在代碼Activity.onCreate()實作中通過調用setContentView(R.layout.layout_file_name)方法加載顯示資源。
2.1.5 在螢幕元素中設定句柄
1.您可以使用Activity.findViewById來取得螢幕上的元素的句柄. 使用該句柄您可以設定或擷取任何該對象外露的值.
TextViewmsgTextView = (TextView)findViewById(R.id.msg);
msgTextView.setText(R.string.push_me);
2.2 建構組成子產品
Android應用是由各種各樣的元件來構成.這些元件大部分都是松散聯接,你可以精确的描述它們的聯接程度,是以組建的聯合比單個程式更有結合力.
顯然,所有的元件運作在同一個系統程序裡面.在這個程序裡面建立多線程是可以允許的,并且是常見的.如果你需要,也可以對剛才那個系統程序建立互相獨立的子程序.即使會有很多執行個體運作,但是他們之間互不幹擾,這個是很難得的,因為Android可以確定代碼是程序間透明的.
以下部分是很重要的Android APIs;
AndroidManifest.xml 是控制檔案,告訴所有由進階元件構成的系統可以做什麼.這些進階的元件包括(特殊activities,服務,接收器,第三方提供商).控制檔案用來告訴系統如何使用你所建立的元件.
Activity 是一個有生命周期的對象. 一個Activity做一些工作需要相當大的數量的代碼;如必要的話,這部分工作還可能包括對使用者UI界面的顯示,也可能是沒有UI界面.代表性地解釋Activity就是,你必須标明你應用程式的入口點.
視圖(Views)可以将其自身繪制到螢幕(screen)上。Android的接口都是由一組以樹的形式出現的視圖組成的。開發者可以通過建立一個新的視圖的方法來使用自定義的圖形處理技術(比如開發遊戲,或者是使用了不常用的使用者圖形(UI)視窗界面(widget))。
Intents是一個消息操作對象.如果一個應用程式想要顯示一個網頁,那麼它表示為Intent,我們可以通過Intent執行個體建立一個URI視圖并且可以手工斷開系統.系統設定一些代碼(例如浏覽器),可以讓我們知道如果去操作Intent并且運作它.Intents 也可以被用于廣播系統範圍内的有效事件(例如播發一則通知).
An Intent is asimple message object that represents an "intention" to do something.For example, if your application wants to display a web page, it expresses its"Intent" to view the URI by creating an Intent instance and handingit off to the system.
The system locatessome other piece of code (in this case, the Browser) that knows how to handlethat Intent, and runs it. Intents can also be used to broadcast interestingevents (such as a notification) system-wide.
服務是運作在背景的一段代碼.它可以運作在它自己的程序,也可以運作在其他應用程式的程序裡面,這要取決于自身的需要. 其他元件綁定到這個服務上面,并且可以請求遠端方法調用.例如媒體播放器的服務,甚至當使用者退出媒體使用者向導界面,音樂依然可以持續播放.甚至當使用者界面關閉,音樂播放依然繼續.
A Service is a bodyof code that runs in the background. It can run in its own process, or in thecontext of another application's process, depending on its needs. Othercomponents "bind" to a Service and invoke methods on it via remoteprocedure calls. An example of a Service is a media player; even when the userquits the media-selection UI, she probably still intends for her music to keepplaying. A Service keeps the music going even when the UI has completed.
通知将以小圖示的形式呈現在狀态欄裡.收到消息以後,使用者可以與圖示進行互動式操作.大部分熟知的通知是以短資訊,通話記錄,語音郵件的形式建立出來.通知是提請使用者注意的重要機制.
A Notification is asmall icon that appears in the status bar. Users can interact with this icon toreceive information. The most well-known notifications are SMS messages, callhistory, and voicemail, but applications can create their own. Notificationsare the strongly-preferred mechanism for alerting the user of something thatneeds their attention.
ContentProvider是通路資料裝置的提供者.典型的例子是通路使用者聯系清單.你的應用程式需要通路的資料可以由ContentProvider來支援.并且你也可以定義自己專用資料的ContentProviders.
A ContentProvideris a data storehouse that provides access to data on the device; the classicexample is the ContentProvider that's used to access the user's list ofcontacts. Your application can access data that other applications have exposedvia a ContentProvider, and you can also define your own ContentProviders toexpose data of your own.
2.2.1 AndroidManifest.xml檔案
AndroidManifest.xml is a required file for everyapplication. It sits in the root folder for an application, and describesglobal values for your package, including the application components(activities, services, etc) that the package exposes and the implementationclasses for each component, what kind of data each can handle, and where theycan be launched.
AndroidManifest.xml是每一個應用都需要的檔案. 位于應用根目錄下, 描述了程式包的一個全局變量, 包括暴露的應用元件(activities, services等等)和為每個元件的實作類, 什麼樣的資料可以操作, 以及在什麼地方運作.
An important aspect of this fileare the intent filters that it includes. These filters describe where and whenthat activity can be started. When an activity (or the operating system) wantsto perform an action such as open a Web page or open a contact picker screen,it creates an Intent object. This object can hold several descriptorsdescribing what you want to do, what data you want to do it to, the type ofdata, and other bits of information. Android compares the information in anIntent object with the intent filter exposed by every application and finds theactivity most appropriate to handle the data or action specified by the caller.More details on intents is given in the Intent reference page.
這個檔案的一個重要方面(概念)是其中的intent過濾器. 這個過濾器描述了何時何種情況下讓activity 啟動. 當一個activity(或是作業系統)想要執行一個動作, 例如打開一個Web頁或是打開一個聯系人選取螢幕, 會建立一個Intent對象. 該對象包含了很多的描述資訊, 描述了你想做什麼操作, 你想處理什麼資料, 資料的類型, 以及一些其他的重要資訊.Android拿這個Intent的資訊與所有應用暴露的intent過濾器比較, 找到一個最能恰當處理請求者要求的資料和action的activity. intents的更多資訊在Intent頁.
Besides declaring yourapplication's Activities, Content Providers, Services, and Intent Receivers,you can also specify permissions and instrumentation (security control andtesting) in AndroidManifest.xml. For a reference of the tags and theirattributes, please see AndroidManifest.
另外還要聲明您的應用的Activities,Content Providers, Services, 和 Intent Receivers, 你也可以在AndroidManifest.xml檔案中指定權限和instrumentation(安全控制和測試). 請檢視AndroidManifest, 了解這個标簽和他們的屬性.
A simple AndroidManifest.xml lookslike this:
一個AndroidManifest.xml檔案的例子:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.my_domain.app.helloactivity">
<application android:label="@string/app_name">
<activity class=".HelloActivity">
<intent-filter>
<action android:value="android.intent.action.MAIN"/>
<category android:value="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Some general items to note:
這裡記錄了一些通用特性:
· Almostevery AndroidManifest.xml (as well as many other Android XML files) willinclude the namespace declaration xmlns:android="http://schemas.android.com/apk/res/android"in its first element. This makes a variety of standard Android attributesavailable in the file, which will be used to supply most of the data forelements in that file.
· 幾乎所有的AndroidManifest.xml 檔案(同其他的Android檔案一樣)都會包含一個命名空間的聲明-xmlns:android="http://schemas.android.com/apk/res/android"-在第一個元素中. 該聲明使标準Android屬性在該檔案中得以使用,該屬性為檔案的xml元素提供了大部分資料.
· Mostmanifests include a single <application> element, which defines all ofthe application-level components and properties that are available in thepackage.
· 大多數AndroidManifest.xml 檔案僅包含一個<application>元素, 該元素定了這個程式包内所有應用層面上可用的元件和屬性.
· Anypackage that will be presented to the user as a top-level application availablefrom the program launcher will need to include at least one Activity componentthat supports the MAIN action and LAUNCHER category as shown here.
· TODO
Here is a detailed outline of thestructure of an AndroidManifest.xml file, describing all tags that areavailable.
下面列出了AndroidManifest.xml 這個檔案詳細的結構大綱, 描述了所有可用标簽.
<manifest>
The root node of the file, describing the completecontents of the package. Under it you can place:
檔案根節點, 描述了程式包的所有内容. 在其節點下面内可以放置:
<uses-permission>
Requests a security permission that your package mustbe granted in order for it to operate correctly. See the Security Modeldocument for more information on permissions. A manifest can contain zero ormore of these elements.
請求一個安全授權, 必須被授予該權限, 您的程式包才能正确的操作. 檢視安全子產品文檔, 了解有關授權的更多資訊. 一個manifest可以包含零個或多個這樣的節點.
<permission>
Declares a security permission that can be used torestrict which applications can access components or features in your (oranother) package. See the Security Model document for more information onpermissions. A manifest can contain zero or more of these elements.
聲明一個安全授權, 用來限制哪些應用可以通路您的程式包内的元件和特有機制. 檢視安全子產品文檔, 了解有關授權的更多資訊. 一個manifest可以包含零個或多個這樣的節點.
<instrumentation>
Declares the code of an instrumentation component thatis available to test the functionality of this or another package. SeeInstrumentation for more details. A manifest can contain zero or more of theseelements.
TODO
<application>
Root element containing declarations of theapplication-level components contained in the package. This element can alsoinclude global and/or default attributes for the application, such as a label,icon, theme, required permission, etc. A manifest can contain zero or one ofthese elements (more than one application tag is not allowed). Under it you canplace zero or more of each of the following component declarations:
描述程式包内應用級别元件的根節點. 該節點能夠描述應用程式的全局(和/或)預設屬性, 例如标簽, 圖示, 主題, 需要的授權, 等等. 一個manifest可以包含零個或一個這樣的節點(多個application 節點是不允許的). 在該節點下, 可以包含零個或多個以下每個元件的聲明:
<activity>
An Activity is the primary facility for an applicationto interact with the user. The initial screen the user sees when launching anapplication is an activity, and most other screens they use will be implementedas separate activities declared with additional activity tags.
Activity 是應用于使用者互動的最主要機制.當一個應用運作的時候, 使用者看到的第一個螢幕就是activity, 并且, 使用者所使用的其他絕大多數螢幕(界面)也會是
Note:Every Activity must have an <activity> tag in the manifest whether it isexposed to the world or intended for use only within its own package. If anActivity has no matching tag in the manifest, you won't be able to launch it.
Optionally, to support late runtime lookup of youractivity, you can include one or more <intent-filter> elements todescribe the actions the activity supports:
<intent-filter>
Declares a specific set of Intent values that acomponent supports, in the form of an IntentFilter. In addition to the variouskinds of values that can be specified under this element, attributes can begiven here to supply a unique label, icon, and other information for the actionbeing described.
<action>
An Intent action that the component supports.
<category>
An Intent category that the component supports.
<type>
An Intent data MIME type that the component supports.
<scheme>
An Intent data URI scheme that the component supports.
<authority>
An Intent data URI authority that the componentsupports.
<path>
An Intent data URI path that the component supports.
<receiver>
An IntentReceiver allows an application to be toldabout changes to data or actions that happen, even if it is not currentlyrunning. As with the activity tag, you can optionally include one or more<intent-filter> elements that the receiver supports; see the activity's<intent-filter> description for more information.
一個IntentReceiver可以讓應用接收到一次資料變化和一次行為發生的通知, 甚至這個應用沒有在運作也可以.同activity 标簽一樣, 你可以選擇包含一個或多個<intent-filter>元素; 檢視activity的<intent-filter>标簽描述了解更多資訊.
<service>
A Service is a component that can run in the backgroundfor an arbitrary amount of time. As with the activity tag, you can optionallyinclude one or more <intent-filter> elements that the receiver supports;see the activity's <intent-filter> description for more information.
Service 是一個在背景任意時刻都可以運作的元件. 同activity 标簽一樣, 你可以選擇包含一個或多個<intent-filter>元素; 檢視activity的<intent-filter>标簽描述了解更多資訊.
<provider>
A ContentProvider is a component that managespersistent data and publishes it for access by other applications.
ContentProvider元件是用來管理資料持久化及資料釋出的, 釋出的資料可以被其他的應用通路.
2.2.2 Activity
2.2.3 View
android.view
公有類
android.view.View
java.lang.Object
android.view.View Drawable.Callback KeyEvent.Callback
視圖(View)類代表了一種基本的使用者界面組成子產品。一個視圖占據了螢幕上的一個矩形區域,并響應繪制圖形和事件處理。視圖類是窗體類(Widget)的基類,而窗體類用來生成可互動的使用者圖形接口(interactiveGUI)。
視圖類的使用視窗中所有的視圖構成一個樹形結構。要想增加視圖,既可以用直接添加代碼的方法,也可以在一個或者多個XML檔案中聲明新視圖構成的樹。在視圖類的子類中,有的可以用來控制,有的具有顯示文字、圖檔或者其他内容的功能。
當視圖樹被建立後,以下這若幹種通用操作将可以被使用: 1.設定屬性(properties):比如,可以設定TextView類的一個執行個體的文本内容。不同的子類可以用來設定的屬性與方法不同。注意:隻有編譯時能夠檢測到的屬性才可以在XML布局管理(layout)檔案中設定。
2.設定輸入焦點(focus):為了響應使用者輸入,整個架構将處理移動的焦點。如果想把焦點強制指向某一個特定的視圖,必須調用requestFocus()方法。
3.設定監聽器(listener):在視圖中,允許設定監聽器來捕獲使用者感興趣的某些事件。比如說,在所有的視圖中,無論視圖是獲得焦點還是失去焦點,都可以通過設定監聽器來捕獲。可以通過調用setOnFocusChangeListener(View.OnFocusChangeListener)來注冊一個監聽器。在其他視圖子類中,提供了一些更加特殊的監聽器。比如,一個按鍵(Button)可以觸發按鍵被按下的事件。
4.設定是否可視(visibility):可以通過調用setVisibility(int)來顯示或者隐藏視圖。
2.2.4 Intent
Intent 介紹
Intent是對被執行操作的抽象描述。調用 startActivity(Intent),可以啟動 Activity;調用broadcastIntent(Intent),可以把 Intent 發送給任何相關的IntentReceiver 元件;調用 startService(Intent, Bundle) 以及 bindService(Intent, String, ServiceConnection, int) 可以讓應用和背景服務進行通信。
Intent 提供了一個在不同應用的代碼之間進行晚綁定 (late runtime binding) 的機制。它主要被用來啟動 Activities,是以可以被看作是Activities 之間的粘合劑。Intent 大體上是一個被動資料結構,該資料結構包括被執行動作的抽象描述。Intent 中的主要内容有:
action -- 需要被執行的動作。比如 VIEW_ACTION, EDIT_ACTION, MAIN_ACTION 等。
data -- 執行動作要操作的資料,在 Intent 裡用指向資料記錄的URI (ContentURI) 表示。比如聯系人資料庫中的一個聯系人記錄。
譯注:被動資料結構:隻能由外部線程或者程序改變的資料結構。與能夠通過相關的線程或者程序執行内部操作進而産生外部行為的主動資料結構相對應。
下面是一些 action/data 對的例子:
VIEW_ACTIONcontent://contacts/1 -- 顯示辨別符為"1"的聯系人的資訊。
EDIT_ACTIONcontent://contacts/1 -- 編輯辨別符為"1"的聯系人的資訊。
VIEW_ACTIONcontent://contacts/ -- 顯示可周遊的聯系人清單。這是用來進入聯系人應用主界面(頂級入口,top-level entry)的典型方法。在這個界面中察看某個聯系人會産生一個新的 Intent:{VIEW_ACTIONcontent://contacts/N},用來啟動新的Activity,顯示該聯系人的詳細資訊。
PICK_ACTIONcontent://contacts/ -- 先是可周遊的聯系人清單,并且允許使用者在清單中選擇一個聯系人,然後把這個聯系人傳回給"上級活動"(parent activity)。例如:電子郵件用戶端可以使用這個 Intent,要求使用者在聯系人清單中選擇一個聯系人。
除了 action, data 兩個主要屬性,Intent還具有一些其它屬性,這些屬性也可以被用在Intent 裡:
category -- 類别,被執行動作的附加資訊。例如 LAUNCHER_CATEGORY 表示Intent 的接受者應該在 Launcher 中作為頂級應用出現;而ALTERNATIVE_CATEGORY 表示目前的 Intent 是一系列的可選動作中的一個,這些動作可以在同一塊資料上執行。
type -- 資料類型,顯式指定 Intent 的資料類型 (MIME)。一般上 Intent 的資料類型能夠根據資料本身進行判定,但是通過設定這個屬性,可以強制采用顯式指定的類型而不再進行推導。
component -- 元件,為使用 Intent 的元件類指定名稱。通常會根據 Intent 中包含的其它資訊 ——比如 action, data/type, categories ——進行查找,最終找到一個與之比對的元件。如果這個屬性存在的話,将直接使用它指定的元件,不再執行上述查找過程。指定了這個屬性以後,Intent 的其它所有屬性都是可選的。
extras -- 額外的附加資訊,是其它所有附加資訊的集合。使用 extras 可以為元件提供擴充資訊,比如,如果要發送電子郵件,也就是要執行“發送電子郵件”的動作,可以将電子郵件的标題、正文等儲存在 extras 裡。
在 Intent 類裡定義了多種标準 action 和 category 常量(字元串),同時應用也可以根據自己的需要進行定義。這些字元串使用 JAVA 風格的 scoping,進而保證它們的唯一性。比如标準 VIEW_ACTION 的定義是 “android.app.action.VIEW”。
概括而言,“動作”、“資料類型”、“類别”(譯注:Intent的action類型)和“附加資料”一起形成了一種語言。這種語言使得系統能夠了解諸如“打john的手機”之類的短語。随着應用不斷的加入到系統中,它們可以添加新的“動作”、“資料類型”、“類别”來擴充這種語言。應用也可以提供自己的 activities 來處理已經存在的“短語”,進而改變這些“短語”的行為。
[編輯] Intent 解析
Intent 有兩種主要形式:
顯式意圖(直接意圖?)。顯式意圖是指定了 component 屬性的 intents。調用 setComponent(ComponentName) 或者 setClass(Context, Class) 可以為 intents 設定 component屬性——指定具體的元件類。這些 intents 一般不包括包括其它任何資訊,它們通常隻是用來通知應用啟動内部的 activities 作為該應用的(目前)使用者界面。
隐式意圖(含蓄意圖?)。隐式意圖是沒有指明 comonent 的 intents。這些 intents 必須包括足夠的資訊,這樣系統才能确定在所有的可用元件中,對一個 intent 來說運作哪一個元件才是最合适的。
在使用 implicit intents 的時候,對于一個任意的 intent,我們需要知道用它來做什麼。“Intent 解析過程”用來處理這個問題。“Intent 解析過程”将 intent 映射到可以處理它的activity, IntentReceiver 或者 service。
Intent 解析機制主要是将已安裝應用程式包裡的 Intent-Filter 描述和 Intent 進行比對。如果使用廣播發送 Intent,還要在已經注冊的IntentReceiver 中盡心比對。更多的相關描述可以在 IntentFilter 中找到。
在解析 Intent 的過程中要用到Intent 的三個屬性:動作、資料類型和類别。使用這些屬性,就可以 PackageManager 上查詢能夠處理目前 intent 的合适元件。元件是否合适由 AndroidManifest.xml 檔案中提供的 intent 資訊決定。判斷的方法如下:
The action, if given, must be listed by thecomponent as one it handles.
如果 intent 指明了要執行的action,元件 action 清單中就必須包含着個action,否則不能比對;
如果 Intent 沒有提供資料類型(type),系統從資料 (data) 中得到資料類型。和action 一樣,元件的資料類型清單中必須包含 intent 的資料類型,否則不能比對。
如果 Intent 中的資料不是content: 類型的 URL,而且 Intent 也沒有明确指定它的資料類型,将根據 Intent 中資料的 scheme(比如 http: or mailto:) 進行比對。同上,Intent 的 scheme 必須出現在元件的 scheme 清單中。
如果 Intent 指定了一個或多個類别,這些類别必須全部出現在組建的類别清單中。比如 intent 中包含了兩個類别:LAUNCHER_CATEGORY 和 ALTERNATIVE_CATEGORY,解析得到的元件必須至少包含這兩個類别。
以一個應用執行個體作為例子,這個應用可以讓使用者浏覽便箋清單、檢視每一個便箋的詳細資訊:
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.notepad">
<applicationandroid:icon="@drawable/app_notes"
android:label="@string/app_name">
<provider class="NotePadProvider"
android:authorities="com.google.provider.NotePad" />
<activityclass=".NotesList"android:label="@string/title_notes_list">
<intent-filter>
<actionandroid:value="android.intent.action.MAIN" />
<categoryandroid:value="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<actionandroid:value="android.intent.action.VIEW" />
<actionandroid:value="android.intent.action.EDIT" />
<actionandroid:value="android.intent.action.PICK" />
<categoryandroid:value="android.intent.category.DEFAULT" />
<typeandroid:value="vnd.android.cursor.dir/vnd.google.note" />
</intent-filter>
<intent-filter>
<actionandroid:value="android.intent.action.GET_CONTENT" />
<categoryandroid:value="android.intent.category.DEFAULT" />
<typeandroid:value="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>
</activity>
<activityclass=".NoteEditor" android:label="@string/title_note">
<intent-filterandroid:label="@string/resolve_edit">
<action android:value="android.intent.action.VIEW"/>
<actionandroid:value="android.intent.action.EDIT" />
<categoryandroid:value="android.intent.category.DEFAULT" />
<typeandroid:value="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>
<intent-filter>
<actionandroid:value="android.intent.action.INSERT" />
<categoryandroid:value="android.intent.category.DEFAULT" />
<type android:value="vnd.android.cursor.dir/vnd.google.note"/>
</intent-filter>
</activity>
<activityclass=".TitleEditor"android:label="@string/title_edit_title"
android:theme="@android:style/Theme.Dialog">
<intent-filterandroid:label="@string/resolve_title">
<actionandroid:value="com.google.android.notepad.action.EDIT_TITLE" />
<categoryandroid:value="android.intent.category.DEFAULT" />
<category android:value="android.intent.category.ALTERNATIVE"/>
<categoryandroid:value="android.intent.category.SELECTED_ALTERNATIVE" />
<typeandroid:value="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>
</activity>
</application>
</manifest>
例子中的第一個 activity 是com.google.android.notepad.NotesList。它是進入應用的主入口(mainentry),具有三種功能,分别由三個 intent 模闆進行描述。
第一個功能是進入便箋應用的頂級入口。它的類型是 android.app.category.LAUNCHER,說明這個應用應該在 Launcher 中被列出。
第二個功能用來浏覽可用的便箋,或者讓使用者選擇一個特定的便箋并且把這個便箋傳回給調用者。當資料類型是 vnd.android.cursor.dir/vnd.google.note (便箋記錄的目錄) 的時候,執行動作 android.app.action.VIEW 可以浏覽可用的便箋;執行動作 android.app.action.PICK 可以讓使用者選擇便箋。
第三個功能傳回給調用者一個使用者選擇的便箋。當資料類型是 vnd.android.cursor.dir/vnd.google.note 的時候,執行動作 android.app.action.GET_COUTENT 調用者不需要知道
有了這些功能,就能夠将下列 intents 比對到NotesList 的 activity:
{action=android.app.action.MAIN }. 如果activities 能夠被用作進入應用的頂級入口,就可以和這個 intent 進行比對。
{action=android.app.action.MAIN, category=android.app.category.LAUNCHER }. 這是目前 Launcher 實際使用的 intent,構成了它的頂級清單。
問題:怎麼構成??
{action=android.app.action.VIEW data=content://com.google.provider.NotePad/notes}. 顯示"content://com.google.provider.NotePad/notes" 下所有便箋的清單,使用者可以周遊這個清單,并且察看便箋的詳情。
{action=android.app.action.PICK data=content://com.google.provider.NotePad/notes}. 讓使用者在"content://com.google.provider.NotePad/notes" 之下的便箋清單中選擇一個,然後将這個便箋的 URL 傳回給調用者。
{action=android.app.action.GET_CONTENTtype=vnd.android.cursor.item/vnd.google.note }. 這個 intent 和上面的 pick 動作類似,不同的是這個 intent 允許調用者(僅僅)指定它們需要的資料類型(,而不需要了解資料存放的詳細位置,即資料的 URI)。系統根據這個資料類型選擇恰當的 activity,然後讓使用者選擇某些資料。
第二個 activity 是com.google.android.notepad.NoteEditor,它為使用者顯示一個單獨的便箋,并且允許使用者對這個便箋進行修改。它具有兩個 intent 模闆,是以具有兩個功能。第一個操作是主要的操作,允許使用者察看和編輯一個便簽(執行 android.app.action.VIEW 和 android.app.action.EDIT 動作,資料類型是vnd.android.cursor.item/vnd.google.note)。第二個模闆可以讓調用者顯示建立新便箋的使用者界面,并且将新便箋插入到便箋清單中(執行 android.app.action.INSERT 動作,資料類型是 vnd.android.cursor.dir/vnd.google.note)。
有了這兩個功能,下列 intents 就能夠比對到NotesList 的 activity:
{action=android.app.action.VIEW data=content://com.google.provider.NotePad/notes/{ID}} 向使用者顯示辨別為 ID 的便箋。将辨別為 ID 的便箋縮寫為 note{ID},下同。
{action=android.app.action.EDITdata=content://com.google.provider.NotePad/notes/{ID} } 讓使用者能夠編輯 notes{ID}。
{action=android.app.action.INSERT data=content://com.google.provider.NotePad/notes} 建立一個新的便箋,新便箋被建立在“content://com.google.provider.NotePad/notes”所表示的便箋清單中。使用者可以編輯這個便簽。當使用者儲存這個便箋後,這個新便箋的 URI 将會傳回給調用者。
最後一個 activity 是com.google.android.notepad.TitleEditor,它可以讓使用者編輯便箋的标題。它可以被實作為一個類,在 intent 中明确設定 component 屬性後,應用可以直接調用這個類;不過在這裡我們展示的是如何在已有資料上釋出可選操作。這個 activity 隻有一個單獨的intent 模闆,它具有一個私有 action:com.google.android.notepad.action.EDIT_TITLE,允許使用者編輯便箋的标題。和前面的 view 和 edit 動作一樣,調用這個intent 的時候,也必須指定具體的便箋。不一樣的是,這裡顯示和編輯的隻是便箋資料中的标題。
除了支援确省類别 (default category, android.intent.category.DEFAULT,原文是 android.intent.category.VIEW,有誤),标題編輯器還支援另外兩個标準類别:android.intent.category.ALTERNATIVE 和 android.intent.category.SELECTED_ALTERNATIVE。實作了這兩個類别之後,其它 activities 可以調用函數queryIntentActivityOptions(ComponentName, Intent[], Intent, int) 查詢這個 activity 支援的 actions,而不需要了解它的具體實作;或者調用 addIntentOptions(int, int, ComponentName,Intent[], Intent, int, Menu.Item[]) 建立動态菜單。需要說明的是,這個 intent 模闆有一個明确的名稱(通過android:label="@string/resolve_title" 指定)。在使用者浏覽資料的時候,如果這個 activity 是資料的一個可選操作,指定明确的名稱可以為使用者提供一個更好控制界面。
有了這個功能,下列 intents 就能夠比對到NotesList 的 activity:
{action=com.google.android.notepad.action.EDIT_TITLEdata=content://com.google.provider.NotePad/notes/{ID} } 顯示并且允許使用者編輯 note{ID} 的标題。
[編輯] Activity 的标準動作 (Actions)
下面是 Intent 為啟動activities 定義的标準動作,一般使用 startActivity(Intent) 啟動 activities。其中最重要也是最經常使用的是 MAIN_ACTION 和 EDIT_ACTION.
MAIN_ACTION
VIEW_ACTION
EDIT_ACTION
PICK_ACTION
GET_CONTENT_ACTION
DIAL_ACTION
CALL_ACTION
SENDTO_ACTION
ANSWER_ACTION
INSERT_ACTION
DELETE_ACTION
RUN_ACTION
LOGIN_ACTION
CLEAR_CREDENTIALS_ACTION
SYNC_ACTION
PICK_ACTIVITY_ACTION
WEB_SEARCH_ACTION
[編輯] 标準的廣播動作 (broadcase actions)
下面是 Intent 為接收廣播而定義的動作。可以通過 registerReceiver(IntentReceiver, IntentFilter),或者在 manifest 中增加 receiver标記來注冊。
TIME_TICK_ACTION
TIME_CHANGED_ACTION
TIMEZONE_CHANGED_ACTION
BOOT_COMPLETED_ACTION
PACKAGE_ADDED_ACTION
PACKAGE_REMOVED_ACTION
BATTERY_CHANGED_ACTION
[編輯]标準類别
下面是已定義的标準類别。通過 addCategory(String) 可以為 Intent 設定類别。
DEFAULT_CATEGORY
BROWSABLE_CATEGORY
TAB_CATEGORY
ALTERNATIVE_CATEGORY
SELECTED_ALTERNATIVE_CATEGORY
LAUNCHER_CATEGORY
HOME_CATEGORY
PREFERENCE_CATEGORY
GADGET_CATEGORY
TEST_CATEGORY
[編輯]标準附加資料
下面是已定義的标準字段,用來在 putExtra(String, Object) 中為 Intent 設定附加資料。
TEMPLATE_EXTRA
INTENT_EXTRA
[編輯] 啟動标記 (launchflags)
下面是 Intent 中可能用到的啟動标記,通過setLaunchFlags(int) 和 addLaunchFlags(int) 使用這些标記。
NO_HISTORY_LAUNCH
SINGLE_TOP_LAUNCH
NEW_TASK_LAUNCH
MULTIPLE_TASK_LAUNCH
FORWARD_RESULT_LAUNCH
[編輯]嵌套類
Intent.FilterComparison 持有一個 Intent 對象,并且為過濾實作了Intent 的比較操作。
[編輯]概要
[編輯]常量
Values | |||
String | ADD_SHORTCUT_ACTION | 動作:在系統中添加一個快捷方式。. | "android.intent.action.ADD_SHORTCUT" |
String | ALL_APPS_ACTION | 動作:列舉所有可用的應用。 輸入:無。 | "android.intent.action.ALL_APPS" |
String | ALTERNATIVE_CATEGORY | 類别:說明 activity 是使用者正在浏覽的資料的一個可選操作。 | "android.intent.category.ALTERNATIVE" |
String | ANSWER_ACTION | 動作:處理撥入的電話。 | "android.intent.action.ANSWER" |
String | BATTERY_CHANGED_ACTION | 廣播:充電狀态,或者電池的電量發生變化。 | "android.intent.action.BATTERY_CHANGED" |
String | BOOT_COMPLETED_ACTION | 廣播:在系統啟動後,這個動作被廣播一次(隻有一次)。 | "android.intent.action.BOOT_COMPLETED" |
String | BROWSABLE_CATEGORY | 類别:能夠被浏覽器安全使用的 activities 必須支援這個類别。 | "android.intent.category.BROWSABLE" |
String | BUG_REPORT_ACTION | 動作:顯示 activity 報告錯誤。 | "android.intent.action.BUG_REPORT" |
String | CALL_ACTION | 動作:撥打電話,被呼叫的聯系人在資料中指定。 | "android.intent.action.CALL" |
String | CALL_FORWARDING_STATE_CHANGED_ACTION | 廣播:語音電話的呼叫轉移狀态已經改變。 | "android.intent.action.CFF" |
String | CLEAR_CREDENTIALS_ACTION | 動作:清除登陸憑證 (credential)。 | "android.intent.action.CLEAR_CREDENTIALS" |
String | CONFIGURATION_CHANGED_ACTION | 廣播:裝置的配置資訊已經改變,參見 Resources.Configuration. | "android.intent.action.CONFIGURATION_CHANGED" |
Creator | CREATOR | 無 | 無 |
String | DATA_ACTIVITY_STATE_CHANGED_ACTION | 廣播:電話的資料活動(data activity)狀态(即收發資料的狀态)已經改變。 | "android.intent.action.DATA_ACTIVITY" |
String | DATA_CONNECTION_STATE_CHANGED_ACTION | 廣播:電話的資料連接配接狀态已經改變。 | "android.intent.action.DATA_STATE" |
String | DATE_CHANGED_ACTION | 廣播:日期被改變。 | "android.intent.action.DATE_CHANGED" |
String | DEFAULT_ACTION | 動作:和 VIEW_ACTION 相同,是在資料上執行的标準動作。 | "android.intent.action.VIEW" |
String | DEFAULT_CATEGORY | 類别:如果 activity 是對資料執行确省動作(點選, center press)的一個選項,需要設定這個類别。 | "android.intent.category.DEFAULT" |
String | DELETE_ACTION | 動作:從容器中删除給定的資料。 | "android.intent.action.DELETE" |
String | DEVELOPMENT_PREFERENCE_CATEGORY | 類别:說明 activity 是一個設定面闆 (development preference panel). | "android.intent.category.DEVELOPMENT_PREFERENCE" |
String | DIAL_ACTION | 動作:撥打資料中指定的電話号碼。 | "android.intent.action.DIAL" |
String | EDIT_ACTION | 動作:為制定的資料顯示可編輯界面。 | "android.intent.action.EDIT" |
String | EMBED_CATEGORY | 類别:能夠在上級(父)activity 中運作。 | "android.intent.category.EMBED" |
String | EMERGENCY_DIAL_ACTION | 動作:撥打緊急電話号碼。 | "android.intent.action.EMERGENCY_DIAL" |
int | FORWARD_RESULT_LAUNCH | 啟動标記:如果這個标記被設定,而且被一個已經存在的 activity 用來啟動新的 activity,已有 activity 的回複目标 (reply target) 會被轉移給新的 activity。 | 16 0x00000010 |
String | FOTA_CANCEL_ACTION | 廣播:取消所有被挂起的 (pending) 更新下載下傳。 | "android.server.checkin.FOTA_CANCEL" |
String | FOTA_INSTALL_ACTION | 廣播:更新已經被确認,馬上就要開始安裝。 | "android.server.checkin.FOTA_INSTALL" |
String | FOTA_READY_ACTION | 廣播:更新已經被下載下傳,可以開始安裝。 | "android.server.checkin.FOTA_READY" |
String | FOTA_RESTART_ACTION | 廣播:恢複已經停止的更新下載下傳。 | "android.server.checkin.FOTA_RESTART" |
String | FOTA_UPDATE_ACTION | 廣播:通過 OTA 下載下傳并安裝作業系統更新。 | "android.server.checkin.FOTA_UPDATE" |
String | FRAMEWORK_INSTRUMENTATION_TEST_CATEGORY | 類别:To be used as code under test for framework instrumentation tests. | "android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" |
String | GADGET_CATEGORY | 類别:這個 activity 可以被嵌入宿主 activity (activity that is hosting gadgets)。 | "android.intent.category.GADGET" |
String | GET_CONTENT_ACTION | 動作:讓使用者選擇資料并傳回。 | "android.intent.action.GET_CONTENT" |
String | HOME_CATEGORY | 類别:主螢幕 (activity),裝置啟動後顯示的第一個 activity。 | "android.intent.category.HOME" |
String | INSERT_ACTION | 動作:在容器中插入一個空項 (item)。 | "android.intent.action.INSERT" |
String | INTENT_EXTRA | 附加資料:和 PICK_ACTIVITY_ACTION 一起使用時,說明使用者選擇的用來顯示的 activity;和 ADD_SHORTCUT_ACTION 一起使用的時候,描述要添加的快捷方式。 | "android.intent.extra.INTENT" |
String | LABEL_EXTRA | 附加資料:大寫字母開頭的字元标簽,和 ADD_SHORTCUT_ACTION 一起使用。 | "android.intent.extra.LABEL" |
String | LAUNCHER_CATEGORY | 類别:Activity 應該被顯示在頂級的 launcher 中。 | "android.intent.category.LAUNCHER" |
String | LOGIN_ACTION | 動作:擷取登入憑證。 | "android.intent.action.LOGIN" |
String | MAIN_ACTION | 動作:作為主入口點啟動,不需要資料。 | "android.intent.action.MAIN" |
String | MEDIABUTTON_ACTION | 廣播:使用者按下了“Media Button”。 | "android.intent.action.MEDIABUTTON" |
String | MEDIA_BAD_REMOVAL_ACTION | 廣播:擴充媒體(擴充卡)已經從 SD 卡插槽拔出,但是挂載點 (mount point) 還沒解除 (unmount)。 | "android.intent.action.MEDIA_BAD_REMOVAL" |
String | MEDIA_EJECT_ACTION | 廣播:使用者想要移除擴充媒體(拔掉擴充卡)。 | "android.intent.action.MEDIA_EJECT" |
String | MEDIA_MOUNTED_ACTION | 廣播:擴充媒體被插入,而且已經被挂載。 | "android.intent.action.MEDIA_MOUNTED" |
String | MEDIA_REMOVED_ACTION | 廣播:擴充媒體被移除。 | "android.intent.action.MEDIA_REMOVED" |
String | MEDIA_SCANNER_FINISHED_ACTION | 廣播:已經掃描完媒體的一個目錄。 | "android.intent.action.MEDIA_SCANNER_FINISHED" |
String | MEDIA_SCANNER_STARTED_ACTION | 廣播:開始掃描媒體的一個目錄。 | "android.intent.action.MEDIA_SCANNER_STARTED" |
String | MEDIA_SHARED_ACTION | 廣播:擴充媒體的挂載被解除 (unmount),因為它已經作為 USB 大容量存儲被共享。 | "android.intent.action.MEDIA_SHARED" |
String | MEDIA_UNMOUNTED_ACTION | 廣播:擴充媒體存在,但是還沒有被挂載 (mount)。 | "android.intent.action.MEDIA_UNMOUNTED" |
String | MESSAGE_WAITING_STATE_CHANGED_ACTION | 廣播:電話的消息等待(語音郵件)狀态已經改變。 | "android.intent.action.MWI" |
int | MULTIPLE_TASK_LAUNCH | 啟動标記:和 NEW_TASK_LAUNCH 聯合使用,禁止将已有的任務改變為前景任務 (foreground)。 | 8 0x00000008 |
String | NETWORK_TICKLE_RECEIVED_ACTION | 廣播:裝置收到了新的網絡 "tickle" 通知。 | "android.intent.action.NETWORK_TICKLE_RECEIVED" |
int | NEW_TASK_LAUNCH | 啟動标記:設定以後,activity 将成為曆史堆棧中的第一個新任務(棧頂)。 | 4 0x00000004 |
int | NO_HISTORY_LAUNCH | 啟動标記:設定以後,新的 activity 不會被儲存在曆史堆棧中。 | 1 0x00000001 |
String | PACKAGE_ADDED_ACTION | 廣播:裝置上新安裝了一個應用程式包。 | "android.intent.action.PACKAGE_ADDED" |
String | PACKAGE_REMOVED_ACTION | 廣播:裝置上删除了一個應用程式包。 | "android.intent.action.PACKAGE_REMOVED" |
String | PHONE_STATE_CHANGED_ACTION | 廣播:電話狀态已經改變。 | "android.intent.action.PHONE_STATE" |
String | PICK_ACTION | 動作:從資料中選擇一個項目 (item),将被選中的項目傳回。 | "android.intent.action.PICK" |
String | PICK_ACTIVITY_ACTION | 動作:選擇一個 activity,傳回被選擇的 activity 的類(名)。 | "android.intent.action.PICK_ACTIVITY" |
String | PREFERENCE_CATEGORY | 類别:activity是一個設定面闆 (preference panel)。 | "android.intent.category.PREFERENCE" |
String | PROVIDER_CHANGED_ACTION | 廣播:更新将要(真正)被安裝。 | "android.intent.action.PROVIDER_CHANGED" |
String | PROVISIONING_CHECK_ACTION | 廣播:要求 polling of provisioning service 下載下傳最新的設定。 | "android.intent.action.PROVISIONING_CHECK" |
String | RUN_ACTION | 動作:運作資料(指定的應用),無論它(應用)是什麼。 | "android.intent.action.RUN" |
String | SAMPLE_CODE_CATEGORY | 類别:To be used as an sample code example (not part of the normal user experience). | "android.intent.category.SAMPLE_CODE" |
String | SCREEN_OFF_ACTION | 廣播:螢幕被關閉。 | "android.intent.action.SCREEN_OFF" |
String | SCREEN_ON_ACTION | 廣播:螢幕已經被打開。 | "android.intent.action.SCREEN_ON" |
String | SELECTED_ALTERNATIVE_CATEGORY | 類别:對于被使用者選中的資料,activity 是它的一個可選操作。 | "android.intent.category.SELECTED_ALTERNATIVE" |
String | SENDTO_ACTION | 動作:向 data 指定的接收者發送一個消息。 | "android.intent.action.SENDTO" |
String | SERVICE_STATE_CHANGED_ACTION | 廣播:電話服務的狀态已經改變。 | "android.intent.action.SERVICE_STATE" |
String | SETTINGS_ACTION | 動作:顯示系統設定。輸入:無。 | "android.intent.action.SETTINGS" |
String | SIGNAL_STRENGTH_CHANGED_ACTION | 廣播:電話的信号強度已經改變。 | "android.intent.action.SIG_STR" |
int | SINGLE_TOP_LAUNCH | 啟動标記:設定以後,如果 activity 已經啟動,而且位于曆史堆棧的頂端,将不再啟動(不重新啟動) activity。 | 2 0x00000002 |
String | STATISTICS_REPORT_ACTION | 廣播:要求 receivers 報告自己的統計資訊。 | "android.intent.action.STATISTICS_REPORT" |
String | STATISTICS_STATE_CHANGED_ACTION | 廣播:統計資訊服務的狀态已經改變。 | "android.intent.action.STATISTICS_STATE_CHANGED" |
String | SYNC_ACTION | 動作:執行資料同步。 | "android.intent.action.SYNC" |
String | TAB_CATEGORY | 類别:這個 activity 應該在 TabActivity 中作為一個 tab 使用。 | "android.intent.category.TAB" |
String | TEMPLATE_EXTRA | 附加資料:新記錄的初始化模闆。 | "android.intent.extra.TEMPLATE" |
String | TEST_CATEGORY | 類别:作為測試目的使用,不是正常的使用者體驗的一部分。 | "android.intent.category.TEST" |
String | TIMEZONE_CHANGED_ACTION | 廣播:時區已經改變。 | "android.intent.action.TIMEZONE_CHANGED" |
String | TIME_CHANGED_ACTION | 廣播:時間已經改變(重新設定)。 | "android.intent.action.TIME_SET" |
String | TIME_TICK_ACTION | 廣播:目前時間已經變化(正常的時間流逝)。 | "android.intent.action.TIME_TICK" |
String | UMS_CONNECTED_ACTION | 廣播:裝置進入 USB 大容量存儲模式。 | "android.intent.action.UMS_CONNECTED" |
String | UMS_DISCONNECTED_ACTION | 廣播:裝置從 USB 大容量存儲模式退出。 | "android.intent.action.UMS_DISCONNECTED" |
String | UNIT_TEST_CATEGORY | 類别:應該被用作單元測試(通過 test harness 運作)。 | "android.intent.category.UNIT_TEST" |
String | VIEW_ACTION | 動作:向使用者顯示資料。 | "android.intent.action.VIEW" |
String | WALLPAPER_CATEGORY | 類别:這個 activity 能過為裝置設定牆紙。 | "android.intent.category.WALLPAPER" |
String | WALLPAPER_CHANGED_ACTION | 廣播:系統的牆紙已經改變。 | "android.intent.action.WALLPAPER_CHANGED" |
String | WALLPAPER_SETTINGS_ACTION | 動作:顯示選擇牆紙的設定界面。輸入:無。 | "android.intent.action.WALLPAPER_SETTINGS" |
String | WEB_SEARCH_ACTION | 動作:執行 web 搜尋。 | "android.intent.action.WEB_SEARCH" |
String | XMPP_CONNECTED_ACTION | 廣播:XMPP 連接配接已經被建立。 | "android.intent.action.XMPP_CONNECTED" |
String | XMPP_DISCONNECTED_ACTION | 廣播:XMPP 連接配接已經被斷開。 | "android.intent.action.XMPP_DISCONNECTED" |
[編輯]公共構造函數
Public Constructor | |
Intent() | 建立空的intent對象。 |
Intent(Intent o) | 拷貝構造函數。 |
Intent(String action) | 用指定的動作建立 intent 對象。 |
Intent(String action, ContentURI uri) | 建立 intent 對象,指定動作和資料 (URI)。 |
Intent(Context packageContext, Class cls) | 建立 intent 對象,指定 component。 |
Intent(String action, ContentURI uri, Context packageContext, Class cls) | 建立 intent 對象,指定動作、資料群組件。 |
[編輯]公共方法
Public Constructor | |
Intent addCategory(String category) | 向 intent 添加新的類别。 |
Intent addLaunchFlags(int flags) | 向 intent 添加新的啟動标記。 |
boolean filterEquals(Intent other) | 判斷兩個 intent 是否相等:檢查他們是否有完全相同的意圖(用于過濾)。 |
int filterHashCode() | 生成 intent 的哈希代碼,該代碼與 filterEquals 有同樣的語義,即能用于進行 intent 比較。 |
String getAction() | 擷取 intent 要執行的動作,如:VIEW_ACTION。 |
Set getCategories() | 擷取 intent 對象所屬的所有類别(集合)。 |
ComponentName getComponent() | 擷取 intent 關聯的具體元件。 |
ContentURI getData() | 擷取 intent 對象要操作的資料 (URI)。 |
Object getExtra(String name, Object def) | 擷取 intent 的擴充資料。 |
Object getExtra(String name) | 擷取 intent 的擴充資料。 |
Bundle getExtras() | 擷取 intent 的擴充資料 map。 |
static Intent getIntent(String uri) | 由 URI 建立 Intent。 |
int getLaunchFlags() | 擷取 intent 的所有啟動标記。 |
String getScheme() | 擷取 intent 中資料的 sheme。 |
String getType() | 擷取 intent 明确聲明的資料類型(顯式聲明的 MIME 類型,不是推導出來的類型)。 |
boolean hasCategory(String category) | Intent 是否指定了類别。 |
Intent putExtra(String name, Object value) | 向 intent 添加擴充資料。 |
void putExtras(Intent src) | 将 src 中的所有擴充資料複制到 intent 中。 |
void putExtras(Bundle extras) | 向 intent 添加擴充資料。 |
void readFromParcel(Parcel in) | 無。 |
void removeCategory(String category) | 從 intent 删除一個類别。 |
void removeExtra(String name) | 從 intent 删除擴充資料。 |
ComponentName resolveActivity(PackageManager pm) | 取得用來處理這個 intent 的 activity 元件。 |
ActivityInfo resolveActivityInfo(PackageManager pm) | 取得用來處理這個 intent 的 activity 的資訊 (PackageManager.ActivityInfo)。 |
String resolveType(ContentResolver resolver) | 取得 intent 的 MIME 資料類型。(判斷順序:intent 明确指定的類型;intent 資料隐式包含的資料類型) |
String resolveType(Context context) | 取得 intent 的 MIME 資料類型。(判斷順序:intent 明确指定的類型;intent 資料隐式包含的資料類型) |
String resolveTypeIfNeeded(ContentResolver resolver) | 如果 resolver 需要,傳回 intent 的資料類型,否則傳回空。 |
Intent setAction(String action) | 設定 intent 要執行的動作。 |
Intent setClass(Context packageContext, Class cls) | 設定運作 intent 的元件,和 setComponent 功能相同。 |
Intent setClassName(String packageName, String className) | 設定運作 intent 的元件,和 setComponent 功能相同。 |
Intent setClassName(Context packageContext, String className) | 設定運作 intent 的元件,和 setComponent 功能相同。 |
Intent setComponent(ComponentName component) | 設定運作 intent 的元件。 |
Intent setData(ContentURI data) | 設定處理 intent 的時候要操作的資料。 |
Intent setDataAndType(ContentURI data, String type) | 設定 intent 的資料和資料類型 (MIME)。 |
Intent setLaunchFlags(int flags) | 設定啟動标記(用來控制 intent 被處理的方式)。 |
Intent setType(String type) | 設定明确的 MIME 資料類型。 |
String toString() | 為 intent 生成一個可讀的字元串描述。 |
String toURI() | 無 |
void writeToParcel(Parcel out) | 無 |
[編輯] 從 java.lang.Object 繼承的方法
clone, equals,finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
[編輯] 從 android.os.Parcelable 繼承的方法
void writeToParcel(Parceldest)
2.2.5 Service
服務是在背景長時間運作的應用元件,不和使用者直接進行互動。在每一個服務類的包的 AndroidManifest.xml 檔案中,必須有一個相應的 <service> 聲明。服務必須用Context.startService() 或者 Context.bindService() 啟動。
和其它應用對象一樣,服務運作在它們宿主程序的主線程裡。這意味着,如果一個服務需要執行阻塞操作(比如網絡操作)或者 CPU 敏感的操作(比如 MP3播放器),它應該分離出一個線程來執行這樣的操作。
服務類是應用程式的生命周期中的一個重要部分。
在這裡要讨論的内容有:
服務的生命周期
通路權限
程序生命周期
[編輯] 服務的生命周期
啟動服務有兩種方法。
如果客戶調用 Context.startService(),系統将獲得服務(如果服務不存在,系統建立服務,然後調用它的 onCreate() 方法),然後使用調用者提供的參數調用服務的 onStart(int, Bundle) 方法。從此以後,服務開始持續運作,直到 Context.stopService() 或者 stopSelf() 被調用。注意:多次調用Context.startService() 雖然會導緻 onStart() 被多次調用,但是服務本身不會嵌套(原文如此,應該是說服務的執行個體隻有一個,不會啟動多個服務)。是以無論調用多少次 Context.startService(),隻要調用一次 Context.stopService() 或者stopSelf(),服務就會停止運作。
客戶也可以調用 Context.bindService() 獲得到服務的永久連接配接。如果服務之前沒有啟動,一樣會建立服務然後調用它的 onCreate() 方法;但是不會調用它的onStart() 方法。服務調用它的 getBinder() 方法,并且将傳回的IBinder 對象傳遞給客戶。連接配接建立以後,不管客戶是否保留這個 IBinder 對象的引用,隻要連接配接還存在,服務都會持續運作。通常傳回的 IBinder 對象是一個由 AIDL 實作的複雜接口。
服務可以同時被啟動和綁定多個連接配接。在這種情況下,隻要服務被啟動,或者存在着到這個服務的連接配接,服務都會持續運作。當兩個條件都不滿足時,系統調用服務的 onDestroy() 方法,服務從此被終止。當onDestroy() 傳回的時候,所有的清理工作(停止線程,取消已經注冊的 receivers)都已經完成。
[編輯]通路權限
對服務的全局通路權限可以通過服務的 manifest 中的<service> 元素指定。這樣,其它應用需要在它們的 manifest 中聲明對應的<uses-permission> 元素,這樣才能啟動、停止和綁定到服務。
同時,在執行 IPC 調用之前,服務可以調用checkCallingPermission(String) 對這次 IPC 調用的權限進行檢查。
關于權限和安全方面的資訊,請參考安全模型文檔。
[編輯]程序生命周期
隻要服務被啟動或者被客戶綁定(建立連接配接),Android 系統就盡可能維護一個程序來作這個服務的宿主。當系統記憶體不足的時候,系統需要殺死程序來出讓記憶體。這時候在下列情況下,服務的宿主程序具有較高的優先級:
如果服務已經被啟動,它的宿主程序比任何在螢幕上對使用者可見的程序都具有更低的優先級;但是比其它所有不可見的程序都具有更高的優先級。通常對使用者可見的程序的數量非常少,是以正在運作的服務在絕大多數時候不會被殺死 —— 除非系統的可用記憶體極其匮乏。
如果有客戶綁定在服務上,服務的宿主程序的優先級至少和客戶的優先級一樣(不會比客戶更低)。這意味着如果客戶對使用者可見,那麼服務本身也會被系統認為對使用者可見。
在服務的宿主程序中運作有其它應用元件,比如 activity,可以提高整個程序的優先級,而不是僅僅提高服務本身的優先級。
[編輯]概要
[編輯]常量
Values | ||
String | TAG | "Service" |
[編輯] 從類 android.content.Context 繼承的常量:
ALARM_SERVICE,BIND_AUTO_CREATE, CONTEXT_IGNORE_SECURITY, CONTEXT_INCLUDE_CODE,INFLATE_SERVICE, KEYGUARD_SERVICE, LOCATION_SERVICE, MODE_APPEND, MODE_PRIVATE,MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE, NOTIFICATION_SERVICE, POWER_SERVICE,WINDOW_SERVICE
[編輯]公共構造函數
Service()
Service() |
[編輯]公共成員函數
公共成員函數 | ||
final | Application getApplication() | 傳回擁有這個服務的應用。 |
abstract | IBinder getBinder() | 傳回到這個服務的通信通道 (communication channel)。 |
final | void stopSelf(int startId) | 停止服務,如果它最後一次啟動的 ID 是 startID。 |
final | void stopSelf() | 如果服務已經啟動,則停止服務。 |
[編輯]保護成員函數
保護成員函數 | |
void onCreate() | 在服務第一次被建立的時候被調用。 |
void onDestroy() | 在服務不再被使用,需要被删除時被調用。 |
void onStart(int startId, Bundle arguments) | 客戶調用 startService(Intent, Bundle) 直接啟動服務的時候被調用。Bundle 是客戶提供的參數,startID 是這次服務啟動的唯一辨別。 |
[編輯]從類 android.app.ApplicationContext 繼承的方法:
applyThemeResource,bindService, broadcastIntent, broadcastIntent, broadcastIntent,checkCallingOrSelfPermission, checkCallingPermission, checkPermission,clearWallpaper, closeExternalStorageFiles, createDatabase,createPackageContext, deleteDatabase, deleteFile, fileList, getAssets,getClassLoader, getContentResolver, getDataDir, getFileStreamPath,getPackageManager, getPackageName, getPackagePath, getResources, getSharedPreferences,getSystemService, getTheme, getWallpaper, openDatabase, openFileInput,openFileOutput, peekWallpaper, registerExternalStorageListener,registerReceiver, registerReceiver, setTheme, setWallpaper, setWallpaper,showAlert, showAlert, showAlert, showAlert, startActivity,startInstrumentation, startService, stopService, unbindService,unregisterReceiver
[編輯] 從類 android.content.Context 繼承的方法:
bindService,broadcastIntent, broadcastIntent, broadcastIntent,checkCallingOrSelfPermission, checkCallingPermission, checkPermission,clearWallpaper, createDatabase, createPackageContext, deleteDatabase,deleteFile, fileList, getAssets, getClassLoader, getContentResolver,getDataDir, getFileStreamPath, getPackageManager, getPackageName, getResources,getSharedPreferences, getString, getSystemService, getText, getTheme,getWallpaper, obtainStyledAttributes, obtainStyledAttributes,obtainStyledAttributes, obtainStyledAttributes, openDatabase, openFileInput,openFileOutput, peekWallpaper, registerReceiver, registerReceiver, setTheme,setWallpaper, setWallpaper, showAlert, showAlert, showAlert, showAlert,startActivity, startInstrumentation, startService, stopService, unbindService,unregisterReceiver
[編輯] 從類 java.lang.Object 繼承的方法:
clone, equals,finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
[編輯]詳細資料
[編輯]常數
[編輯] public static finalString TAG
常數值: "Service"
[編輯]公共構造函數
[編輯] public Service()
無
[編輯]公共成員函數
[編輯]public final Application getApplication()
傳回擁有這個服務的應用。
[編輯] public abstractIBinder getBinder()
傳回到服務的通信通道 (communication channel)。如果客戶沒有綁定到服務,傳回 null。 IBinder 通常是一個複雜接口(參見 AIDL)。
傳回值 傳回一個 IBinder 對象,客戶可以通過它調用服務(的功能)。
[編輯] public final voidstopSelf(int startId)
如果服務最後一次啟動的 ID 是 startID,則停止服務。這個函數和調用 stopService(Intent) 有同樣的結果,但是如果客戶發起了新的啟動請求,而請求還沒有進入到 onStart(int, Bundle),這個函數可以避免停止服務。
參數 startId: 最後一次啟動時在onStart(int, Bundle) 中收到的啟動辨別 (ID)。
參考 stopSelf()
[編輯] public final voidstopSelf()
如果服務已經被啟動,則停止服務,和 stopService(Intent) 有同樣的效果。 參見 stopSelf(int)
[編輯]保護成員函數
[編輯] protected voidonCreate()
在服務第一次被建立時被調用。
[編輯] protected voidonDestroy()
服務不再被使用而且需要被删除的時候被調用。服務在這裡清理所有它占有的資源,包括線程、已經注冊的 receivers 等。函數傳回後,不應該再調用這個服務的任何方法。事實上,服務已經中止。
[編輯]protected void onStart(int startId, Bundle arguments)
在客戶每次調用 startService(Intent, Bundle) 直接啟動服務的時候被調用。
參數 startId: 表示這次啟動請求的整數辨別。可以用在 stopSelf(int) 中。 arguments: 客戶在startService(Intent, Bundle) 中提供的 Bundle。可以為空。
參見 stopSelf(int)
2.2.6 NotificationManager
2.2.7 ContentProvider
2.3 資料存儲與檢索(共享資料的方法)
典型的桌面作業系統提供一種公共檔案系統——任何應用軟體可以使用它來存儲和讀取檔案,該檔案也可以被其它的應用軟體所讀取(也許會有一些權限控制設定)。Andorid采用了一種不同的系統:在Android,所有的應用軟體資料(包括檔案)為該應用軟體所私有。然而,Android同樣也提供了一種标準方式供應用軟體将私有資料開放給其它應用軟體。這一章節描述一個應用軟體存儲和擷取資料、開放資料給其它應用軟體、從其他應用軟體請求資料并且開放它們的多種方式
Android提供如下機制以存儲和擷取資料。
參數
一個輕量級的存儲和擷取機制,采用一對簡單的資料類型:key和value。它的典型應用是存儲應用軟體參數。
檔案
你可以将檔案存儲在手機或可移動存儲媒介中。預設其他程式不可通路這些檔案。
資料庫
Android API包含有對SQLite的支援。你的應用程式可以建立并使用一個私有SQLite資料庫。每個資料庫為建立它的包所私有。
内容提供器
内容提供器是一個開放私有資料讀寫權限的可選元件。受限于該私有資料想施加的任何限制。内容提供器執行标準的資料請求語句、通過标準的資料通路機制傳回資料。Android 提供了多種針對标準資料類型的内容提供器,例如個人通訊錄。
網絡
不要忘記你同樣可以使用網絡來存儲和擷取資料
2.3.1 參數
你可以存儲應用軟體啟動時需要載入的參數,例如預設問候語或文本字型。調用Context.getSharedPreferences()讀取和寫入參數值,如果你想将參數共享給包内的其它元件,請為參數配置設定一個名字。或者使用Activity.getPreferences()和無名參數以對調用保持私有。你不能跨越包将參數共享。
這裡是一個為電腦靜音按鍵模式設定使用者參數的例子。
public class Calc extends Activity { public static final String PREFS_NAME = "MyPrefsFile"; } |
2.3.2 使用檔案
Android 提供接口去讀寫一個軟體的本地檔案的streams。調用Context.openFileOutput() 和Context.openFileInput(),使用本地檔案名和路徑去讀寫檔案。另一個軟體調用這些方法時使用同樣的檔案名和路徑字元串将無法工作。你隻能通路本地檔案。如果你有靜态檔案需要在編譯時同軟體一起打包,你可以把檔案儲存在工程的res/raw/<mydatafile>下,并且可以用Resources.openRawResource(R.raw.mydatafile)來擷取它。
取自"http://www.androidcn.net/wiki/index.php/Devel/data/files"
2.3.3 SQLite資料庫
Android支援SQLite資料庫系統并開放資料庫管理函數,這使你可以将複雜的資料集合儲存到有用的對象中。例如,Android定義了一種由字元串型姓名、字元串型位址、數字型電話号碼、一個位圖圖像和多種其它個人資訊描述字段組成的通訊錄資料類型。使用Context.createDatabase()和Context.openDatabase()建立讀寫資料庫和适當的讀寫資料(注意:位圖這樣的檔案資料與本地檔案路徑一樣,以檔案路徑字元串值形式存放在資料庫中)
Android整合sqlite3資料庫工具,允許你在SQLite資料庫中浏覽表内容、運作SQL指令并執行其它有用的函數。
SQLite及其它的所有的資料庫,被儲存于/data/data/<package_name>/databases
建立多少表、包含多少字段、如何連接配接,已經超越了這篇文檔的讨論範圍,但是Android沒有施加任何越過SQLiteconcepts的限制。我們極力推薦包含一個如唯一ID的自增字段以快速查找記錄。對于私有資料,這并不需要。但是如果你使用一個内容提供器,你必須包括一個類似唯一ID的字段。請參考NotePad示例程式中的示例類NotePadProvider.java,那是一個建立群組裝新資料庫的例子。任何資料庫可以憑借資料庫名被該軟體中的任何一個類通路,但不能在該軟體範圍外通路。
2.3.4 内容提供器
通路内容提供器
如果希望公開你的資料,你可以建立(或調用)一個内容提供器。這是一個可以從所有應用軟體中存儲和擷取資料的對象。這也是穿越包共享資料的唯一方式——沒有供所有包共享資料的公共存儲區域。Android整合了基于多種公共資料類型(音頻、視訊、圖像、個人通訊錄資訊等等)的内容提供器。你可以在provider包中看到許多Android自帶的内容提供器。
實際資料儲存的方式取決于它對接口的具體實作。但是所有内容提供器必須執行公共協定去請求資料和傳回結果。内容提供器可以使用自定義幫助器功能使被開放的指定資料更容易被存儲/擷取。
使用内容提供器存儲和擷取資料
這一節闡述你或其他任何人如何使用内容提供器存儲和擷取資料。Adnroid為廣泛的資料類型開放了多種資料提供器,從音樂檔案、圖像檔案到電話号碼。你可以從有用的android.provider包中看到一個開放的内容提供器清單。
Android的内容提供器被用戶端寬松地連接配接。每一個内容提供器開放一個唯一的字元串(URI)來識别将要操作的資料類型,用戶端必須使用該字元串來存儲和擷取相應類型的資料。在《資料請求》章節中我們将對此做更多解釋。
這一節請求下列内容
請求資料
制作請求
請求的返還值
請求檔案
讀取獲得的資料
修改資料
添加記錄
删除記錄
資料請求
每一種内容提供器開放一個唯一公共URI(由ContentURI封裝),它将被用戶端用于從内容提供器請求/添加/更新/删除資料。URI有2種形式:一是指出該類型資料的所有值(例如所有個人通訊錄),二是指出該類型資料的特定記錄(例如喬•史密斯的聯絡資訊)
content://contacts/people/從裝置傳回通訊錄姓名清單
content://contacts/people/23傳回通訊錄中ID=23的單行記錄
當應用将請求發送到裝置,要求擷取整體資料(所有電話号碼)或指定資料(鮑勃的電話号碼)。Android将傳回一個包含指定行的記錄集遊标。讓我們來看一個假定的請求字元串和結果集(結果已被調整的更清晰一些)。
請求字元串 = content://contacts/people/
結果:
_ID | _COUNT | NUMBER | NUMBER_KEY | LABEL | NAME | TYPE |
13 | 4 | (425) 555 6677 | 425 555 6677 | California office | Bully Pulpit | Work |
44 | 4 | (212) 555-1234 | 212 555 1234 | NY apartment | Alan Vain | Home |
45 | 4 | (212) 555-6657 | 212 555 6657 | Downtown office | Alan Vain | Work |
53 | 4 | 201.555.4433 | 201 555 4433 | Love Nest | Rex Cars | Home |
注意請求字元串不是一個标準的SQL請求,URI字元串描述了傳回資料的類型。這個URI由3部分組成:字元串“content://”;一個描述資料類型的段;一個可選的在特定内容範圍内某特定記錄的ID。這裡有一些請求字元串的例子:
content://media/images從裝置傳回所有圖像的URI
content://contacts/people/從裝置傳回通訊錄中所有姓名清單的URI
content://contacts/people/23傳回通訊錄中ID=23的單行記錄
2.3.5 Android網絡存儲
除了選擇基于裝置的存儲,你還可以從可用網絡存儲和恢複資料。要操作網絡,你需要使用下列包:
· java.net.*
· android.net.*
3 布局學習
http://code.google.com/android/reference/view-gallery.html
API程式設計舉例
1 SQLite
1、 建立資料庫
SQLiteDatabase db =Context.createDatabase(DATABASE_NAME, DATABASE_VERSION, 0,null);
2、 打開資料庫
Context.openDatabase(DATABASE_NAME,null);
3、 建表
Db.execSQL();
4、 插入一行
ContentValues initialValues = newContentValues();
initialValues.put("title",title);
initialValues.put("body", body);
db.insert(DATABASE_TABLE, null,initialValues);
5、 删除一行
Db.delete(DATABASE_TABLE,”rowed=”+rowed,null);
6、 查詢所有
Cursor c =
db.query(DATABASE_TABLE, newString[] {
"rowid","title", "body"}, null, null, null, null, null);
int numRows = c.count();
c.first();
for (int i = 0; i < numRows;++i) {
Row row = new Row();
row.rowId = c.getLong(0);
row.title = c.getString(1);
row.body = c.getString(2);
ret.add(row);
c.next();
}
7、 查詢一行
Cursor c =
db.query(true, DATABASE_TABLE, new String[] {
"rowid", "title", "body"}, "rowid=" + rowId, null, null,
null, null);
if (c.count() > 0) {
c.first();
row.rowId = c.getLong(0);
row.title = c.getString(1);
row.body = c.getString(2);
return row;
} else {
row.rowId = -1;
row.body = row.title = null;
}
8、 更新
ContentValues args = new ContentValues();
args.put("title", title);
args.put("body", body);
db.update(DATABASE_TABLE, args, "rowid=" + rowId, null);
9、 調用
可在Activity中如此調用:dbHelper = new DBHelper(this)
學習過程中遇到的問題
1, eclipse中debug與DDMS的關系?DDMS怎麼用?
2,每次應用啟動時,模拟器要模拟連網,速度挺慢,能不能省去這步?
3, 有時不能Debug ? 難道有2個的上限嗎?
4,書寫資源檔案時如何提示?
5, DEBUG時表現怪異,不能跟蹤源代碼?
Android學習筆記-讓我們快速上手吧
Google的Android SDK釋出也有一段時間了,一直想研究一下卻苦于找不到時間。利用這個周未,開始強迫自己再次進入學習狀态,原因很簡單:我看好開放的gPhone。
SDK的下載下傳與安裝并不複雜,網上也有不少同學已經進入狀态了,我就不再重複了吧。
今天主要讨論的,還是永遠不變的話題:Hello World.
1.最簡單的HelloWorld
安裝了SDK後,直接生成一個Android Project,一句代碼不用寫,就能跑出一個最簡單的HelloWorld例程。我們看一下它的代碼:
public void onCreate(Bundle icicle) ...{
super.onCreate(icicle);
setTheme(android.R.style.Theme_Dark);
setContentView(R.layout.main);
}
看上去實在很簡單,隻有兩句話而已。關鍵在這個R.layout.main上,憑直覺,這應該是定義的資源。的确,在R.java中隻是定義了一個static int 而已,真正的資源描述在res/layout/main.xml檔案裡(注意:這裡的R.java不要手工編輯,每次build project時它都會根據res下的資源描述被自動修改)。
<?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 id="@+id/txt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World"
/>
</LinearLayout>
這個檔案很好讀,一個描述了這是一個線性排列的布局,android:orientation=vertical表示所有元件将縱向排布。而經典的Hello World是用一個TextView來展示的。
由此,我們知道,Android的程式從一個Activity派生出來,并且從它的onCreate開始啟動;Android裡要顯示的元件用XML檔案描述而不用在代碼中寫死(這是一個好的習慣,我們應該從一開始就堅持下去);
2.讓Button來說HelloWorld
上面的例子是ADT自動生成的代碼,似乎與我們一點關系也沒有。那我們來改一下代碼,因為在windows平台上的Helloworld經常是由一個按鈕觸發的,是以,我們想第二個Helloworld應該是這樣的:加一個按鈕和文本輸入框,單擊按鈕後在原來的TextView後面加上輸入框中輸入的文字。
第一步是,增加一個Button和一個EditText,與TextView一樣,它們也在main.xml裡描述一下:
<EditText id="@+id/edt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text=""
/>
<Button id="@+id/go"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@string/go">
<requestFocus />
</Button>
這裡有兩個地方要注意:[email protected]+id/go,這表示需要一個唯一的UID來作為Button的ID,它的引用名是go。還有一個是android:[email protected]/go表示這個按鈕的文本不是直接寫有main.xml裡了,而是來源于另一個資源描述檔案strings.xml裡,本例中的strings.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">helloTwo</string>
<string name="tit_dialog">提示</string>
<string name="msg_dialog">你好,中國</string>
<string name="ok_dialog">确定</string>
<string name="go">浏覽</string>
</resources>
然後,在代碼裡(onCreate函數中)我們加上以下代碼(簡單起見,用了嵌套類):
Button btn = (Button)findViewById(R.id.go);
btn.setOnClickListener(new View.OnClickListener()
...{
public void onClick(View v)
...{
EditText edt=(EditText)helloTwo.this.findViewById(R.id.edt);
TextView txt= (TextView)helloTwo.this.findViewById(R.id.txt);
txt.setText(getString(R.string.msg_dialog)+edt.getText());
}
});
為铵鈕增加一個onClick事件處理器,在點選事件中,設定txt的文本為R.string.msg_dialgo+edt.getText()。
這裡的關鍵是兩個函數的使用: findViewById(R.id.go)可以根據資源的名稱加載View類型的資源,同樣用函數getString(R.string.msg_dialog)可以加載字元串資源。
編譯,run一下看看效果。
3. 再讓菜單Say Hello
從API文檔中我們看到Activity中有兩個函數:onCreateOptionsMenu和onOptionsItemSelected,顯示,這個OptionsMenu就是所謂的上下文菜單(在GPhone的模拟器上,有個鍵專用于彈出這個菜單)。下面我們就為這個HelloWorld例子加上一個菜單,并且讓它可以Say hello。
這次,我們不涉及到資源的描述檔案了,而是直接使用這兩個函數來實作,其實代碼也很簡單,是以,我們再增加一個退出應用的功能(否則每次都是按取消鍵退出應用顯示太不專業了)。
代碼如下:
public boolean onCreateOptionsMenu(Menu menu)
...{
super.onCreateOptionsMenu(menu);
menu.add(0,1,"say hello");
menu.add(0,2,"exit");
return true;
}
public boolean onOptionsItemSelected(Item item)
...{
super.onOptionsItemSelected(item);
int id = item.getId();
switch(id)...{
case 1:
AlertDialog.show(this,getString(R.string.app_name),
getString(R.string.msg_dialog), getString(R.string.ok_dialog), true);
break;
case 2:
finish();
break;
}
在CreateOptionsMenu時,我們簡單地增加兩個菜單項,menu.add(組ID,項ID,顯示文本),(注意:這裡我直接将文字寫在代碼裡,這并不提倡)。然後,在OptionsItemSelected事件中,我們根據選中的菜單項做相應處理,如果選中1,則彈出一個對話框顯示資源檔案中的“你好,中國”,如果選中2則退出應用。
AlertDialog.show是一個靜态方法,類似于我們在WIN平台上經常使用的MessageBox一樣,很友善的。
來源:http://www.sf.org.cn/Android/lumen/20976.html
Android學習筆記(2)-初識Activity
根據文檔的解釋,Activity是Android開發中非常重要的一個基礎類。我把它想像成J2ME中的Display類,或者是Win32平台上的Form類,也許不準确,但是它的重要性我覺得應該是一樣的(當然,如果我們寫的是一個沒有界面的應用,例如背景運作的服務之類的,可以不用Display的)。
1. 在一個Activity中使用多個View
如果把Activity看作MVC中的Control?它負責管理UI和接受事件(包括使用者的輸入),雖然說一個Activity通常對應一個螢幕,但事實上,我們是可以隻用一個Activity管理多個不同的View來實作簡單的邏輯。
首先,我們增加一個新的資源描述layout/second.xml。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView id="@+id/txt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello 中國"
/>
<Button id="@+id/go2"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="back">
<requestFocus />
</Button>
</LinearLayout>
除了一個“Hello中國”以外,增加一個按鈕可以傳回前一個界面。然後,在代碼中我們要為helloTwo增加兩個方法,setViewOneCommand和setViewTwoCommand,分别處理一下在不同界面時,從資源裡加載元件并為元件綁定一個事件處理器。
public void setViewOneCommand()
...{
Button btn = (Button)findViewById(R.id.go);
btn.setOnClickListener(new View.OnClickListener()
...{
public void onClick(View v)
...{
helloTwo.this.setContentView(R.layout.second);
helloTwo.this.setViewTwoCommand();
}
});
Button btnExit=(Button)findViewById(R.id.exit);
btnExit.setOnClickListener(new View.OnClickListener()...{
public void onClick(View v)...{
helloTwo.this.finish();
}
});
}
public void setViewTwoCommand()
...{
Button btnBack=(Button)findViewById(R.id.go2);
btnBack.setOnClickListener(new View.OnClickListener()...{
public void onClick(View v)...{
helloTwo.this.setContentView(R.layout.main);
helloTwo.this.setViewOneCommand();
}
});
}
最後,我們需要在onCreate的時候,也就是啟動後的main界面上設定一下按鈕事件處理器。新的onCreate方法如下:
public void onCreate(Bundle icicle) ...{
super.onCreate(icicle);
setTheme(android.R.style.Theme_Dark);
setContentView(R.layout.main);
setViewOneCommand();
}
編譯,運作,OK。
2. 還是回到正道上,多個Activity之間的跳轉
Android中提供一個叫Intent的類來實作螢幕之間的跳轉,按文檔的說法,似乎他們也建議采用這種方法,Intent的用法比較複雜,現在我先看看它最簡單的用法。
先在應用中增加兩個Activity,這需要修改AndroidManifest.xml檔案了,如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.sharetop.android.hello.three">
<application android:icon="@drawable/icon">
<activity class=".HelloThree" android:label="@string/app_name">
<intent-filter>
<action android:value="android.intent.action.MAIN" />
<category android:value="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity class=".HelloThreeB" android:label="@string/app_name">
</activity>
</application>
</manifest>
很簡單,就是加一個标簽而已,新标簽的class是.HelloThreeB,顯示的應用标題與前一個Activity一樣而已,然後第二步就是修改一個HelloThree類的實作,在onCreate方法中綁定按鈕的事件處理器:
public void onCreate(Bundle icicle) ...{
super.onCreate(icicle);
setTheme(android.R.style.Theme_Dark);
setContentView(R.layout.main);
setViewOneCommand();
}
public void setViewOneCommand()
...{
Button btn = (Button)findViewById(R.id.go);
btn.setOnClickListener(new View.OnClickListener()
...{
public void onClick(View v)
...{
Intent intent = new Intent();
intent.setClass(HelloThree.this, HelloThreeB.class);
startActivity(intent);
finish();
}
});
Button btnExit=(Button)findViewById(R.id.exit);
btnExit.setOnClickListener(new View.OnClickListener()...{
public void onClick(View v)...{
HelloThree.this.finish();
}
});
}
這裡的跳轉功能用Intent來操作,它的最簡單用法就是用函數setClass()設定跳轉前後兩個Activity類的執行個體,然後調用Activity自己的startActivity(intent)即可。最後一句finish()表示将目前Activity關掉(如果不關掉會如何?你可以自己試一下看效果,事實上有時我們是不需要關掉目前Activity的)。
然後,我們同樣弄一個Activity類HelloThreeB,代碼與前面的差不多,隻是将setClass的兩個參數反一下,這樣就可以簡單地實作在兩個Activity界面中來回切換的功能了。
3. 如果我想在兩個Activity之間進行資料交換,怎麼辦?
前例中的startActivity()隻有一個參數,如果需要向新打開的Activity傳遞參數,我們得換一個函數了, Android提供了startSubActivity(Intent,int)這個函數來實作這個功能。
函數原型為: public voidstartSubActivity(Intent intent, int requestCode)
這裡的requestCode用來辨別某一個調用,一般由我們定義一個常量。
如何把參數傳過去呢?Intent類在提供setClass()函數的同時也提供了一個setData()函數。
函數原型為:public Intent setData(ContentURI data)
參數類型是ContentURI,它的詳細内容下回再分析,現在就把它當成一個String類型來用吧。
參數帶到新的Activity後,同樣用Activity.getIntent()函數可以得到目前過來的Intent對象,然後用getData()就取到參數了。
把參數帶回來的方法是Activity.setResult(),它有幾個形式,現在先看最簡單的一個吧。
函數原型是:public final void setResult(intresultCode, String data)
resultCode是傳回代碼,同樣用來辨別一個傳回類型,而data則是它要傳回的參數。
在原來的Activity中的事件處理回調函數onActivityResult,會被系統調用,從它的參數裡可以得到傳回值。
函數原型為:protected void onActivityResult(intrequestCode, int resultCode,String data, Bundle extras)
這裡的requestCode就是前面啟動新Activity時的帶過去的requestCode,而resultCode則關聯上了setResult中的resultCode,data是參數,extras也是一個很重要的東西,後面再研究一下它的作用。
下面,我們來看一下代碼吧,先看看HelloThree中的代碼:
public void setViewOneCommand()
...{
Button btn = (Button)findViewById(R.id.go);
btn.setOnClickListener(new View.OnClickListener()
...{
public void onClick(View v)
...{
try
...{
Intent intent = new Intent();
intent.setClass(HelloThree.this, HelloThreeB.class);
intent.setData(new ContentURI("One"));
startSubActivity(intent,REQUEST_TYPE_A);
}
catch(Exception ex)...{}
}
});
Button btnExit=(Button)findViewById(R.id.exit);
btnExit.setOnClickListener(new View.OnClickListener()...{
public void onClick(View v)...{
HelloThree.this.finish();
}
});
}
protected void onActivityResult(int requestCode, int resultCode,
String data, Bundle extras)
...{
if (requestCode == REQUEST_TYPE_A) ...{
if (resultCode == RESULT_OK) ...{
Log.v(TAG,data);
TextView txt = (TextView)findViewById(R.id.txt);
txt.setText(data);
}
}
}
這裡的REQUEST_TYPE_A是我們定義的一個常量。在onActivityResult中用它與RESULT_OK一起作為條件判斷如何處理傳回值,這裡隻是簡單将TextView顯示值換成傳來的字串。
再來看看另一個HelloThreeB類的實作代碼:
private Intent i;
protected void onCreate(Bundle icicle) ...{
super.onCreate(icicle);
setContentView(R.layout.second);
i = getIntent();
android.util.Log.v(TAG,"onCreate");
Button btn = (Button)findViewById(R.id.go);
btn.setOnClickListener(new View.OnClickListener()...{
public void onClick(View v)...{
String result=HelloThreeB.this.i.getData().toString()+" And Two";
HelloThreeB.this.setResult(RESULT_OK,result);
finish();
}
});
TextView v = (TextView)findViewById(R.id.txt);
v.setText("Param is "+i.getData().toString());
}
在按鈕處理事件中,從Intent取出參數,處理一下再用setResult傳回給前一個Activity即可。
編譯運作即可。
來源:http://www.sf.org.cn/Android/lumen/20977.html
Android學習筆記(3)-Activity的生命周期
注意到在Activity的API中有大量的onXXXX形式的函數定義,除了我們前面用到的onCreate以外,還有onStart,onStop以及onPause等等。從字面上看,它們是一些事件回調,那麼次序又是如何的呢?其實這種事情,自己做個實驗最明白不過了。在做這個實驗之前,我們先得找到在Android中的Log是如何輸出的。
顯然,我們要用的是android.util.log類,這個類相當的簡單易用,因為它提供的全是一些靜态方法:
Log.v(String tag, String msg); //VERBOSE
Log.d(String tag, String msg); //DEBUG
Log.i(String tag, String msg); //INFO
Log.w(String tag, String msg); //WARN
Log.e(String tag, String msg); //ERROR
前面的tag是由我們定義的一個辨別,一般可以用“類名_方法名“來定義。
輸出的LOG資訊,如果用Eclipse+ADT開發,在LogCat中就可以看到,否則用adb logcat也行,不過我是從來都依賴于IDE環境的。
好了,現在我們修改前面的HelloThree代碼:
public void onStart()
...{
super.onStart();
Log.v(TAG,"onStart");
}
public void onStop()
...{
super.onStop();
Log.v(TAG,"onStop");
}
public void onResume()
...{
super.onResume();
Log.v(TAG,"onResume");
}
public void onRestart()
...{
super.onRestart();
Log.v(TAG,"onReStart");
}
public void onPause()
...{
super.onPause();
Log.v(TAG,"onPause");
}
public void onDestroy()
...{
super.onDestroy();
Log.v(TAG,"onDestroy");
}
public void onFreeze(Bundle outState)
...{
super.onFreeze(outState);
Log.v(TAG,"onFreeze");
}
在HelloThreeB中也同樣增加這樣的代碼,編譯,運作一下,從logcat中分析輸出的日志。
在啟動第一個界面Activity One時,它的次序是:
onCreate (ONE) - onStart (ONE) -onResume(ONE)
雖然是第一次啟動,也要走一遍這個resume事件。然後,我們點goto跳到第二個Activity Two中(前一個沒有關閉),這時走的次序是:
onFreeze(ONE) - onPause(ONE) -onCreate(TWO) - onStart(TWO) - onResume(TWO) - onStop(ONE)
說明,第二個Activity Two在啟動前,One會經曆一個:當機、暫停的過程,在啟動Two後,One才會被停止?
然後,我們再點back回到第一個界面,這時走的次序是:
onPause(TWO) - onActivityResult(ONE) -onStart(ONE) - onRestart(ONE) - onResume(ONE) - onStop(TWO) - onDestroy(TWO)
說明,傳回時,Two沒有經曆當機就直接暫停了,在One接收參數,重新開機後,Two就停止并被銷毀了。
最後,我們點一下Exit退出應用,它的次序是:
onPause(ONE) - onStop(ONE) - onDestroy(ONE)
說明如果我們用了finish的話,不會有freeze,但是仍會經曆pause - stop才被銷毀。
這裡有點疑問的是:為什麼回來時先是Start才是Restart?可是文檔中的圖上畫的卻是先restart再start的啊?不過,後面的表格中的描述好象是正确的,start後面總是跟着resume(如果是第一次)或者restart(如果原來被stop掉了,這種情況會在start與resume中插一個restart)。
下面不跑例子了,看看文檔吧。
1.Android用Activity Stack來管理多個Activity,是以呢,同一時刻隻會有最頂上的那個Activity是處于active或者running狀态。其它的Activity都被壓在下面了。
2.如果非活動的Activity仍是可見的(即如果上面壓着的是一個非全屏的Activity或透明的Activity),它是處于paused狀态的。在系統記憶體不足的情況下,paused狀态的Activity是有可被系統殺掉的。隻是不明白,如果它被幹掉了,界面上的顯示又會變成什麼模樣?看來下回有必要研究一下這種情況了。
3.幾個事件的配對可以比較清楚地了解它們的關系。Create與Destroy配成一對,叫entrie lifetime,在建立時配置設定資源,則在銷毀時釋放資源;往上一點還有Start與Stop一對,叫visible lifetime,表達的是可見與非可見這麼一個過程;最頂上的就是Resume和Pause這一對了,叫foregroundlifetime,表達的了是否處于激活狀态的過程。
4.是以,我們實作的Activity派生類,要重載兩個重要的方法:onCreate()進行初始化操作,onPause()儲存目前操作的結果。
除了Activity Lifecycle以外,Android還有一個Process Lifecycle的說明:
在記憶體不足的時候,Android是會主動清理門戶的,那它又是如何判斷哪個process是可以清掉的呢?文檔中也提到了它的重要性排序:
1.最容易被清掉的是empty process,空程序是指那些沒有Activity與之綁定,也沒有任何應用程式元件(如Services或者IntentReceiver)與之綁定的程序,也就是說在這個process中沒有任何activity或者service之類的東西,它們僅僅是作為一個cache,在啟動新的Activity時可以提高速度。它們是會被優先清掉的。是以建議,我們的背景操作,最好是作成Service的形式,也就是說應該在Activity中啟動一個Service去執行這些操作。
2.接下來就是background activity了,也就是被stop掉了那些activity所處的process,那些不可見的Activity被清掉的确是安全的,系統維持着一個LRU清單,多個處于background的activity都在這裡面,系統可以根據LRU清單判斷哪些activity是可以被清掉的,以及其中哪一個應該是最先被清掉。不過,文檔中提到在這個已被清掉的Activity又被重新建立的時候,它的onCreate會被調用,參數就是onFreeze時的那個Bundle。不過這裡有一點不明白的是,難道這個Activity被killed時,Android會幫它保留着這個Bundle嗎?
3.然後就輪到service process了,這是一個與Service綁定的程序,由startService方法啟動。雖然它們不為使用者所見,但一般是在處理一些長時間的操作(例如MP3的播放),系統會保護它,除非真的沒有記憶體可用了。
4.接着又輪到那些visible activity了,或者說visible process。前面也談到這個情況,被Paused的Activity也是有可能會被系統清掉,不過相對來說,它已經是處于一個比較安全的位置了。
5.最安全應該就是那個foreground activity了,不到迫不得已它是不會被清掉的。這種process不僅包括resume之後的activity,也包括那些onReceiveIntent之後的IntentReceiver執行個體。
在Android Application的生命周期的讨論中,文檔也提到了一些需要注意的事項:因為Android應用程式的生存期并不是由應用本身直接控制的,而是由Android系統平台進行管理的,是以,對于我們開發者而言,需要了解不同的元件Activity、Service和IntentReceiver的生命,切記的是:如果元件的選擇不當,很有可能系統會殺掉一個正在進行重要工作的程序。
來源:http://www.sf.org.cn/Android/lumen/20978.html
Android學習筆記(4)-學習Intent的使用
剛看到Intent的時候,我的确有點困惑:從字面上來說,它表示一種意圖和目的;從使用上看,它似乎總是用于Activity之間的切換;而從它所在包android.content來看,它似乎與内容有關。是以,我想或許可以這樣了解它: Intent類綁定一次操作,它負責攜帶這次操作所需要的資料以及操作的類型等。
如果是這樣的話,是否可以将它與事件處理聯想起來?即一個Intent類似于一個Event。從Intent的兩個最重要的成員操作類型(Action)和資料(Data)來看,似乎是有道理的。文檔中說,Intent的Action的取值主要是一些定義好了的常量,例如PICK_ACTION,VIEW_ACTION,EDIT_ACTION之類的,而Data則是一個ContentURI類型的變量,這一點,我們前面提到過。
而且文檔中說Intent分為兩大類,顯性的(Explicit )和隐性的(Implicit)。在前面的例子中,我們在兩個Activity之間跳轉時初步使用了Intent類,當時是用setClass來設定 Intent的發起方與接收方,它被稱為顯性的Intent,而隐性的Intent則不需要用setClass或setComponent來指定事件處理器,利用AndroidMenifest.xml中的配置就可以由平台定位事件的消費者。
一般來說,intent要定位事件的目的地,無外乎需要以下幾個資訊:
1.種類(category),比如我們常見的 LAUNCHER_CATEGORY 就是表示這是一類應用程式。
2.類型(type),在前面的例子中沒用過,表示資料的類型,這是隐性Intent定位目标的重要依據。
3.元件(component),前面的例子中用的是setClass,不過也可以用setComponent來設定intent跳轉的前後兩個類執行個體。
4.附加資料(extras),在ContentURI之外還可以附加一些資訊,它是Bundle類型的對象。
Implicit Intent的使用相對有點麻煩,我們來做一個例子。首先,我們需要增加一個類:HelloThreeProvider,它必須實作于ConentProvider接口,是以代碼如下:
public class HelloThreeProvider extends ContentProvider ...{
public boolean onCreate() ...{
return true;
}
public int delete(ContentURI url, String where, String[] whereArgs) ...{
return 0;
}
public ContentURI insert(ContentURI url, ContentValues initialValues)...{
return url;
}
public Cursor query(ContentURI url, String[] projection, String selection,
String[] selectionArgs, String groupBy, String having, String sort) ...{
return null;
}
public int update(ContentURI url, ContentValues values, String where, String[] whereArgs) ...{
return 0;
}
public String getType(ContentURI url) ...{
return "vnd.sharetop.hello.three/vnd.hello.three";
}
}
這裡面有一堆方法要實作,因為它們都是ContentProvider中的abstract方法,但是今天的例子中它們多半沒有什麼用處,隻是一個getType方法我們讓它不管什麼url都傳回一個表示Intent所攜帶的資料類型是我們定義的一個長字串:vnd.sharetop.hello.three/vnd.hello.three。
然後,在AndroidMenifest.xml中我們将上面這個HelloThreeProvider類加入應用程式:
<application android:icon="@drawable/icon">
<provider class="HelloThreeProvider" android:authorities="cn.sharetop.android.hello" />
<activity class="HelloThree" android:label="@string/app_name">
<intent-filter>
<action android:value="android.intent.action.MAIN" />
<category android:value="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity class="HelloThreeB" android:label="bbb">
<intent-filter>
<action android:value="android.intent.action.VIEW" />
<category android:value="android.intent.category.DEFAULT" />
<type android:value="vnd.sharetop.hello.three/vnd.hello.three" />
</intent-filter>
</activity>
</application>
相對于前面的例子,主要修改了HelloThreeB的配置,包括增加了一個<category>标簽表示這是一個一般性的activity而已。增加了<action>标簽,定義它負責處理VIEW_ACTION類型的操作。增加了<type>标簽給出一個資料類型的定義串vnd.sharetop.hello.three/vnd.hello.three。最主要的是在<application>下增加的那個<provider>标簽,有個authorities屬性,我們給的值是cn.sharetop.android.hello,待一會我們再說它的用處。
最後就是修改以前的跳轉代碼如下:
Intent intent = new Intent();
intent.setData(new ContentURI("content://cn.sharetop.android.hello/one"));
intent.setAction(intent.VIEW_ACTION);
startActivity(intent);
現在我們的setData裡的東西可與以前不一樣的,是吧?注意到它的格式了嗎?content://是個協定頭,固定這樣寫就行了。然後就是那個authorities中定義的串了,再後面就是我們自定義的東西了,我這裡很簡單的寫個one,其它還可以更長一點,如one/101之類的。它負責去關聯上那個provider類。另外,增加了setAction的調用設定操作為VIEW_ACTION,與Menifest中的<action>又挂上了。Android平台負責根據Intent的Data資訊中的authorities,找到ContentProvider,然後getType,用type和intent中的Action兩個資訊,再找到可以處理這個intent的消費者。
OK,編譯運作。
其實,如果是在一個應用内部,這種隐性的intent實在有點别扭,個人覺得,這種松藕合的實作方法,隻适用于那些較大的系統或者多個不同的應用之間的調用,可手機上又有什麼“較大”的系統呢?無非是可以與不同來源的多個應用之間友善地互操作而已,那麼會是什麼樣的場景呢?比如,給QQ好友發送gmail郵件,用GoogleMap查找QQ好友所在的位置?看上去挺不錯的。
關于這個ContentProvider,其實還有話說,它主要是的那些看似資料庫操作的方法我們都沒真正去實作呢。不過今天就到這裡了,等下回再去研究吧。
來源:http://www.sf.org.cn/Android/lumen/20979.html
Android學習筆記(5)-關于ListActivity的簡單體驗
今天學習點輕松的内容吧,看看android.app包裡的幾個類。首先是這個在平台自的例子中被廣泛使用的ListActivity。這個類其實就是一個含有一個ListView元件的Activity類。也就是說,如果我們直接在一個普通的Activity中自己加一個ListView也是完全可以取代這個ListActivity的,隻是它更友善而已,友善到什麼程度呢?來做個例子瞧瞧。
public class HelloTwoB extends ListActivity...{
public void onCreate(Bundle icicle) ...{
super.onCreate(icicle);
setTheme(android.R.style.Theme_Dark);
setContentView(R.layout.mainb);
List<String> items = fillArray();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.list_row,items);
this.setListAdapter(adapter);
}
private List<String> fillArray() ...{
List<String> items = new ArrayList<String>();
items.add("日曜日");
items.add("月曜日");
items.add("火曜日");
items.add("水曜日");
items.add("木曜日");
items.add("金曜日");
items.add("土曜日");
return items;
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id)
...{
TextView txt = (TextView)this.findViewById(R.id.text);
txt.setText("あすは "+l.getSelectedItem().toString()+"です。");
}
}
的确可以簡單到隻需準備一個List對象并借助Adapter就可以構造出一個清單。重載onListItemClick方法可以響應選擇事件,利用第一個參數可以通路到這個ListView執行個體以得到選中的條目資訊。這裡有一點要說明的,就是如果更簡單的話,其實連那個setContentView都可以不要了,Android也會自動幫我們構造出一個全屏的清單。但是本例中我們需要一個TextView來顯示選中的條目,是以我們需要一個layout.mainb描述一下這個清單視窗。
<?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 id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text=""
/>
<ListView id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:drawSelectorOnTop="false"
/>
</LinearLayout>
這裡需要注意的是那個ListView的ID,是系統自定義的android:list,不是我們随便取的,否則系統會說找不到它想要的listview了。然後,在這個listview之外,我們又增加了一個TextView,用來顯示選中的條目。
再來說說這裡用到的ArrayAdapter,它的構造函數中第二個參數是一個資源ID,ArrayAdapter的API文檔中說是要求用一個包含TextView的layout檔案,平台用它來顯示每個選擇條目的樣式,這裡的取值是R.layout.list_row,是以,我們還有一個list_row.xml檔案來描述這個布局,相當簡單。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView id="@+id/item"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView id="@+id/item2"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
從ArrayAdapter上溯到BaseAdapter,發現還有幾個同源的Adapter也應該可以使用,象SimpleAdapter和CursorAdapter,還是做個例子來實驗一下吧。
先看看SimpleAdapter,說是simple卻不simple。
首先看看這個fillMaps方法,基本上就明白這個simpleAdapter是怎麼回事了,在有些場合它還是挺有用的,可以為每個條目綁定一個值:
private List<HashMap<String, String>> fillMaps()
...{
List<HashMap<String, String>> items = new ArrayList<HashMap<String,String>>();
HashMap<String,String> i = new HashMap<String,String>();
i.put("name","日曜日");
i.put("key", "SUN");
items.add(i);
HashMap<String,String> i1 = new HashMap<String,String>();
i1.put("name","月曜日");
i1.put("key", "MON");
items.add(i1);
HashMap<String,String> i2 = new HashMap<String,String>();
i2.put("name","火曜日");
i2.put("key", "TUE");
items.add(i2);
HashMap<String,String> i3 = new HashMap<String,String>();
i3.put("name","水曜日");
i3.put("key", "WED");
items.add(i3);
HashMap<String,String> i4= new HashMap<String,String>();
i4.put("name","木曜日");
i4.put("key", "THU");
items.add(i4);
HashMap<String,String> i5 = new HashMap<String,String>();
i5.put("name","金曜日");
i5.put("key", "FRI");
items.add(i5);
HashMap<String,String> i6 = new HashMap<String,String>();
i6.put("name","土曜日");
i.put("key", "SAT");
items.add(i6);
return items;
}
然後,在HelloTwoB中的onCreate函數中,修改代碼,有幾個不同:items的元素是HashMap執行個體,這是一點變化,然後構造函數除了要求items以外,還要求提供一個string[]來說明用hash表中的哪個字段顯示在清單中,而後是一個資源ID的數組。我的代碼是這樣的:
//SimpleAdapter demo
List<HashMap<String, String>> items = fillMaps();
SimpleAdapter adapter=new SimpleAdapter(this,items,R.layout.list_row,new String[]...{"name"},new int[]...{R.id.item});
編譯跑一下可以看到結果了,是吧?隻是顯示的文字不太對,再改一下:
protected void onListItemClick(ListView l, View v, int position, long id)
...{
TextView txt = (TextView)this.findViewById(R.id.text);
txt.setText("あすは "+((HashMap)l.obtainItem(position)).get("key").toString()+"です。");
}
這樣就好多了,其實一般情況下我們都是用ListView中的obtainItem取得目前選中的條目,然後轉成List中的對應類型來使用的。
上面的例子中隻顯示name對應的值,其實你也可以試一下這樣:
SimpleAdapter adapter=new SimpleAdapter(this,items,R.layout.list_row,new String[]...{"name","key"},new int[]...{R.id.item,R.id.item2});
看看是什麼效果。
再看看那個CursorAdapter吧,它的清單中元素要求是Cursor,這東西與DB有關,不過最簡單的DB就是通訊簿。先從Contacts.People入手吧,同樣修改代碼:
//CursorAdapter demo
Cursor mCursor = this.getContentResolver().query(Contacts.People.CONTENT_URI, null, null, null, null);
SimpleCursorAdapter adapter=new SimpleCursorAdapter(this,R.layout.list_row,mCursor,new String[]...{Contacts.People.NAME},new int[]...{R.id.item});
因為單純的CursorAdapter是抽象類,是以我用的是它的子類SimpleCursorAdapter,很好了解,先用ContentResolver查詢通訊簿得到一個遊标,然後告訴SimpleCursorAdapter要用其中的People.NAME作為顯示項來構造出一個adapter即可。
現在的onListItemClick也不一樣了,如下:
protected void onListItemClick(ListView l, View v, int position, long id)
...{
TextView txt = (TextView)this.findViewById(R.id.text);
Cursor c = (Cursor)l.obtainItem(position);
txt.setText("SEL = "+c.getString(c.getColumnIndex(Contacts.People.NUMBER)));
}
這裡同樣是先用obtainItem取到遊标,然後用從記錄中取出想要的字段顯示即可。在做這個例子時,因為權限的問題我們還得修改一下AndroidManifest.xml檔案,讓我們的應用可以通路到通訊簿:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.sharetop.android.hello.two">
<uses-permission id="android.permission.READ_CONTACTS" />
<application android:icon="@drawable/icon">
... ...
來源:http://www.sf.org.cn/Android/lumen/20980.html
Android學習筆記(6)—關于Dialog的簡單體驗
繼續android.app中的幾個類的學習,今天的内容是那幾個Dialog的體驗。
注意到android.app包下除了Dialog(可用于制作複雜的對話框)以外,還包括了幾個系統定義好的對話框類,如DatePickerDialog、TimePickerDialog及AlertDialog。
其中AlertDialog我上回用過一次,基本上就那樣子了,今天看看另外兩個對話框的使用吧。
首先是DatePickerDialog類,修改代碼如下:
public class HelloTwoC extends Activity implements OnClickListener, OnDateSetListener ...{
public HelloTwoC() ...{
super();
}
public void onCreate(Bundle icicle) ...{
super.onCreate(icicle);
setTheme(android.R.style.Theme_Dark);
setContentView(R.layout.mainc);
Button btn = (Button)findViewById(R.id.date);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) ...{
Calendar d = Calendar.getInstance(Locale.CHINA);
d.setTime(new Date());
DatePickerDialog dlg=new DatePickerDialog(this,this,d.get(Calendar.YEAR),d.get(Calendar.MONTH),d.get(Calendar.DAY_OF_MONTH),d.get(Calendar.DAY_OF_WEEK));
dlg.show();
}
@Override
public void dateSet(DatePicker dp, int y, int m, int d) ...{
TextView txt = (TextView)findViewById(R.id.text);
txt.setText(Integer.toString(y)+"-"+Integer.toString(m)+"-"+Integer.toString(d));
}
}
很簡單的,無非是需要一個OnDateSetListener接口的實作而已,在它裡面的dateSet方法中就可以得到選擇的日期了。而TimePickerDialog與DatePickerDialog使用如出一轍,就不多說了。
看看另一個ProgressDialog的用法吧,這個類與AlertDialog一樣包含了多個static的方法,是以使用起來是非常友善的。比如說,如果我們需要用它來表示一個長時間的操作,很簡單的用一句話就可以了:
ProgressDialog.show(this,null, "operation running...",true,true);
URL:http://www.sf.org.cn/Android/lumen/20981.html
Android學習筆記(7)—關于Service和Notification的體驗
大略地看了一下android.app下的Service類,覺得它與Activity非常相似,隻是要注意幾個地方:
1.生命周期,Service的從onCreate()->onStart(int,Bundle)->onDestroy()顯得更為簡單。但是它的onStart是帶參數的,第一個ID可用來辨別這個service,第二個參數顯示是用來傳遞資料的了。比較Activity,傳遞資料的Bundle是在onCreate就帶進入的。
2.Service的啟動由Context.startService開始,其實Activity或者Service都是Context的派生類。結束于Context.stopService()或者它自己的stopSelf()。
3.Service還有一個與Activity不一樣的是它可以由另一個Context去綁定一個已存在的Service。就是這個方法Context.bindService(),被綁定的Service要求是已經onCreate了但可以沒有onStart。在Service類中有個抽象方法getBinder()可以得到這個IBinder對象。關于這方面的細節,以後再看,這裡隻做個記錄罷。
4.與Service有關的還有一個安全的問題,可以在AndroidManifest.xml中用<uses-permission>标簽來聲明一個Service的通路權限,關于Android的安全問題也留待以後再解決吧。
我一直相信一種水到渠成的學習方法,先從最簡單的東西入手,就不會覺得學習很枯燥了。
下面來做個例子。
修改AndroidManifest.xml檔案,增加一個Activity和一個Service:
<activity class=".HelloTwoD" android:label="hello_two_d">
</activity>
<service class=".HelloTwoDService" />
HelloTwoD.java的代碼比較簡單,如下:
public class HelloTwoD extends Activity implements OnClickListener
...{
public HelloTwoD()
...{
super();
}
public void onCreate(Bundle icicle) ...{
super.onCreate(icicle);
setTheme(android.R.style.Theme_Dark);
setContentView(R.layout.maind);
Button btn = (Button)findViewById(R.id.btnTest);
btn.setOnClickListener(this);
}
@Override
public void onClick(View arg0) ...{
// 用一個顯式的Intent來啟動服務
Intent i = new Intent();
i.setClass(this, HelloTwoDService.class);
//帶上我的名字
Bundle b= new Bundle();
b.putString("name", "sharetop");
this.startService(i,b);
}
}
當然要啟動這個HelloTwoD,也需要在我最初的那個HelloTwo中加一點代碼(我就不羅嗦了)。再看看那個HelloTwoDService是如何實作的:
public class HelloTwoDService extends Service ...{
public Timer timer;
public final String TAG="HelloTwoDService_TAG";
public void onCreate() ...{
super.onCreate();
Log.d(TAG,"onCreate");
timer = new Timer(true);
}
@Override
public IBinder getBinder() ...{
// TODO Auto-generated method stub
return null;
}
public void onStart(int startId, Bundle arg)
...{
//看看startId是什麼内容
if(arg!=null)
Log.d(TAG,"onStart "+Integer.valueOf(startId).toString()+" from "+arg.getString("name"));
else
Log.d(TAG,"onStart with null Bundle");
timer.schedule(new TimerTask()...{
public void run()...{
//表示一下我的存在
Log.d(TAG,"say from a timer.");
//停掉自己這個服務
HelloTwoDService.this.stopSelf();
}
},5000);
}
public void onDestroy()
...{
Log.d(TAG,"onDestroy");
}
}
這裡我用一個定時器timer來延時5秒鐘顯示消息,否則立即就顯示出來覺得不象一個背景服務了。用日志輸出那個onStart中的startId看看,原來隻是一個辨別而已。
下面來個簡單的NotificationManager吧,看了看API文檔,覺得最簡單地恐怕就是那個NotificationManager.notifyWithText()了,修改上面的run方法如下:
timer.schedule(new TimerTask()...{
public void run()...{
NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.notifyWithText(1001, "わたしはSHARETOPです。", NotificationManager.LENGTH_LONG, null);
HelloTwoDService.this.stopSelf();
}
},5000);
再試試看效果。太簡單了,Notification主要是用于背景服務用來通知前台,是以,Android提供了三類不同的通知方式,notifyWithText可以簡單地顯示一個字串,而notifyWithView稍複雜點,可以有一個view來構造這個顯示資訊框,而最靈活的就是那個notify(int id, Notification notification)了,參數notification是類Notification的執行個體。
修改一下剛才的那個run方法,如下:
timer.schedule(new TimerTask()...{
public void run()...{
NotificationManager manager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Notification nf = new Notification(R.drawable.icon,"這是資訊的較長的描述",null,"資訊的标題",null);
manager.notify(0,nf);
HelloTwoDService.this.stopSelf();
}
},5000);
這裡建立一個Notification的執行個體nf,構造函數的第一個參數是那個顯示在狀态欄(也就是Android手機上面的那一條顯示信号強度、電池電量等資訊的位置)的圖示。後面可以有
一個标題和點選以後的詳細資訊,這是字串形式,還可以有一個Intent用來表示點選後可以發生一個跳轉行為。
URL:http://www.sf.org.cn/Android/lumen/20982.html
Android學習筆記(8) — GridView與Image View
簡單一點吧,就瞧瞧那個Grid的效果,Android提供了一個Grid View,不過從Epidemic中看來,它似乎與PC上的GRID差别還是挺大的,更像那個IconView的感覺。不知道Android中如何實作表格界面?雖然在移動終端上,表格一般不會有誰使用,大家似乎更傾向于使用List View,而Android對于ListView則有更簡單的實作List Activity。
廢話不說,還是自己寫幾句代碼來實驗一下。
<Grid View id="@+id/grid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip"
android:verticalSpacing="10"
android:horizontalSpacing="10"
android:numColumns="auto_fit"
android:columnWidth="60"
android:stretchMode="columnWidth"
android:gravity="center"
/>
從描述檔案中的這些屬性來看,與表格非常類似,除了padding和spacing以外,它還多了那個gravity,這裡是center表示單元格中的内容居中放,在類Grid View中也提供了方法setGravity(int)來實作這個效果。
接着,我們沿用以前那個fillMaps方法來構造SimpleAdapter,以前将這個adapter賦給List Activity,現在同樣的Adapter,卻是賦給了Grid View,效果又會是怎樣呢?
List<HashMap<String, String>> items = fillMaps();
Grid View grd=(Grid View)this.findViewById(R.id.grid);
SimpleAdapter adapter=new SimpleAdapter(this,items,R.layout.list_row,new String[]...{"name"},new int[]...{R.id.item});
grd.setAdapter(adapter);
我覺得Grid View并不象表格,倒更象Icon View,下面試試用圖像作為Grid View的内容。現在,不能用簡單Adapter了,得自己弄一個ImageAdapter,就讓它衍生于BaseAdapter類吧。
public class ImageAdapter extends BaseAdapter ...{
//這是資源ID的數組
private Integer[] mThumbIds = ...{
R.drawable.a,R.drawable.b,R.drawable.c,
R.drawable.d,R.drawable.e,R.drawable.f,
R.drawable.g,R.drawable.h,R.drawable.i
};
public ImageAdapter(Context c) ...{
mContext = c;
}
public int getCount() ...{
return mThumbIds.length;
}
public Object getItem(int position) ...{
return position;
}
public long getItemId(int position) ...{
return position;
}
public View getView(int position, View convertView, ViewGroup parent) ...{
ImageView i = new Image View(mContext);
//設定圖像源于資源ID。
i.setImageResource(mThumbIds[position]);
i.setAdjustViewBounds(true);
i.setBackground(android.R.drawable.picture_frame);
return i;
}
private Context mContext;
}
很簡單,隻要重載幾個方法就可以了,關鍵是那個getView方法,它負責建構出每個單元格中的對象執行個體。這裡我們構造的是一個Image View執行個體。
然後就是同樣的将這個Adapter賦給Grid View即可,大家可以看看效果,注意在做這個例子前,先放幾個小圖檔到res/drawable目錄下,buildproject一下就可以得到那個R.drawable.a了(這裡的a是圖像檔案名,如a.png)。
在getView方法中我們使用了ImageView類,這又是一個widget。除了上面用到的幾個方法以外,還有以下幾個方法值得注意:
與圖像來源有關的方法,我們隻用了資源檔案的方式。
//不同的圖像來源
public void setImageBitmap(Bitmap bm)
public void setImageDrawable(Drawable drawable)
public void setImageResource(int resid)
public void setImageURI(ContentURI uri)
圖像效果的操作。
//顔色過濾
public void setColorFilter(int color, Mode mode)
//矩陣變換
public void setImageMatrix(Matrix matrix)
//透明度
public void setAlpha(int alpha)
具體的使用可以參考API,動手試一下就差不多了。
URL:http://www.sf.org.cn/Android/lumen/20983.html
Android學習筆記(9)-開始做一個數獨遊戲[上]
不想再寫Hello123了,今天開始做一個數獨小遊戲,因為這個遊戲比較簡單應該容易上手,就作為我學習Android之後的第一個程式比較合适。
初步的設計是隻有一個界面(如下圖),然後用綠色字型表示題目中有的固定的數字,黃色字型顯示玩家輸入的數字,而紅色字型則是程式判斷輸入錯誤後的顯示。另外模式分為三種:普通寫入、标記(玩家用藍色小方塊标記目前單元格可以輸入的數字)、排除模式(玩家指定數字,遊戲自動判斷一下這個數字肯定不能輸入的單元格,将它反相顯示出來)。
準備工作就是做一張背景圖(棋盤)和三套數字小圖檔(紅、綠、黃)即可。
首先建立工程sudo,程式主體類 MainActivity以後,再修改一下那個main.xml檔案,去掉TextView标簽即可。因為我們會自己定義一個View,是以不再需要它了。程式不大,是以不打算做過多的類,下面把幾個類的分工描述一下:
1、MainActivity,主體類,負責處理鍵盤事件和維護一個題庫。
2、MainView,顯示類,負責維護并顯示目前棋局,它的核心在于它的onDraw函數。
3、GridCell和Question兩個實體類,分别描述了棋盤單元格的資訊和題目資訊。
4、Helper類,助手類,封裝一些函數,如讀寫記錄、自動解題函數等。在MainActivity中的onCreate中,加幾句話就可以讓遊戲全屏顯示了。如下:
setTheme(android.R.style.Theme_Dark);
requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.NO_STATUS_BAR_FLAG,WindowManager.LayoutParams.NO_STATUS_BAR_FLAG);
主要來看看MainView類的代碼吧,它的onDraw負責顯示目前棋局,涉及到的API主要是android.graphics中的Canvas和Paint。一是顯示圖檔的方法,因為圖檔來源于資源,是以顯示它的代碼如:
Bitmap bmp = BitmapFactory.decodeResource(this.getResources(),R.drawable.grid);
canvas.drawBitmap(bmp, 0, 0, null);
這是顯示背景,如果是數字呢,如何将數字1與R.drawable.a1資源關聯呢?
private int[] thumbNormal=new int[]...{0,
R.drawable.a1,R.drawable.a2,R.drawable.a3,R.drawable.a4,R.drawable.a5,
R.drawable.a6,R.drawable.a7,R.drawable.a8,R.drawable.a9
};
然後就簡單地加載即可了。
Bitmap b = BitmapFactory.decodeResource(this.getResources(),this.thumbNormal[this.grid[i].value]);
canvas.drawBitmap(b, xx, yy, null);
二是顯示文本的方法,剛才顯示圖像的drawBitmap中最後一個參數直接給了null,因為我們實在沒有什麼效果需要給圖像的,但是文本則不同,我們用Paint來控制文本的樣式。
Paint paintText=new Paint();
paintText.setFlags(Paint.ANTI_ALIAS_FLAG);
paintText.setColor(Color.WHITE);
... ...
canvas.drawText(Long.toString(this.ti.code), xx, yy, paintText);
三是畫一下框的方法,同樣是用Paint來做的。
Paint paintRect = new Paint();
paintRect.setColor(Color.RED);
paintRect.setStrokeWidth(2);
paintRect.setStyle(Style.STROKE);
Rect r=new Rect();
r.left=this.curCol*CELL_WIDTH+GRID_X;
r.top=this.curRow*CELL_WIDTH+GRID_Y;
r.bottom=r.top+CELL_WIDTH;
r.right=r.left+CELL_WIDTH;
canvas.drawRect(r, paintRect);
如果不setStyle為Style.STROKE,則預設為填充模式。
四是反相顯示的方法,更簡單了,就是一句話了:
Paint paintHint=new Paint();
paintHint.setXfermode(new PixelXorXfermode(Color.WHITE));
URL:http://www.sf.org.cn/Android/lumen/20984.html
Android學習筆記(10)-開始做一個數獨遊戲[中]
繼續,今天讨論的是記錄檔案的讀寫。因為原來在Brew平台上實作的數獨将題庫是一個二進制檔案,是以在Android就直接拿那個檔案來用了。
計劃實作兩個函數,先是LoadTiList(),加載題庫,先裝題庫檔案放在資源裡,然後從資源裡加載它作為一個DataInputStream即可。代碼也沒幾行,如下:
public static boolean LoadTiList(MainActivity me) ...{
DataInputStream in = null;
try
...{
in = new DataInputStream(me.getResources().openRawResource(R.raw.ti));
byte[] bufC4=new byte[4];
byte[] bufC81=new byte[81];
//總個數
in.read(bufC4,0,4);
int len=((int)bufC4[3]<<24)+((int)bufC4[2]<<16)+((int)bufC4[1]<<8)+(int)bufC4[0]; for(int i=0;i<len;i++)
...{
Question ti = new Question();
//代碼
in.read(bufC4,0,4);
ti.code=(long) (((long)bufC4[3]<<24)+((long)bufC4[2]<<16)+((long)bufC4[1]<<8)+(long)bufC4[0]);
//時間
in.read(bufC4,0,4);
SharedPreferences sp = me.getPreferences(Context.MODE_WORLD_READABLE);
ti.time=sp.getLong(Long.toString(ti.code), 0);
//資料
in.read(bufC81,0,81);
for(int j=0;j<81;j++)ti.data[j]=bufC81[j];
me.tiList.add(ti);
}
in.close();
}
catch(Exception ex)...{
return false;
}
finally...{
try...{in.close();}catch(Exception e)...{}
}
return true;
}
這裡最麻煩的是因為java裡沒有unsigned類型,是以會溢出,比較郁悶,這個沒有解決,隻能是生成題庫檔案裡注意一下了,不能與brew平台共用那個題庫檔案了。
二是儲存記錄,在brew平台我直接用一個檔案搞定,讀寫它,但是android不能這樣了,因為ti.dat是從資源中加載的,是以隻能是靜态的,不可修改,那記錄隻能放入preferences中了,代碼如下:
public static boolean SaveTiList(MainActivity me)
...{
try
...{
SharedPreferences sp=me.getPreferences(Context.MODE_WORLD_WRITEABLE);
Question ti = me.gridView.ti;
sp.edit().putLong(Long.toString(ti.code),ti.time);
sp.edit().commit();
}
catch(Exception ex)...{
return false;
}
return true;
}
SharePreferences可以按key-value來儲存記錄,是以key用題目的code,則value就是解它所用的時間了。
Android不能直接通路app目錄下的檔案,是以不能夠象brew那樣将資料檔案放在程式目錄下供它讀寫,而在Activity中提供的兩個函數 openFileOutput和openFileInput,雖可用來讀寫檔案,但是總是不太友善。
另外,用SQLite其實也不友善,因為手機中弄這些東西,事實上用處不大。
URL: http://www.sf.org.cn/Android/lumen/20985.html
Android學習筆記(11)-開始做一個數獨遊戲[下]
繼續,最後再讨論一下定時器的實作。
本來很簡單的一件事,直接用java.util.timer應該就夠用了,但是發現在它的task中無法去invalidate我們的MainView,很郁悶。這一點的處理說明 Android還是相對線程安全的。
折騰良久,明白了非得再做一個Handler,才能線上程中操作界面元素。是以,代碼比brew複雜了一點。
先還是用Timer和TimerTask來做,如下:
public TimerHandler timerHandler;
public Timer timer;
public MyTimerTask task;
... ...
timer=new Timer(true);
task=new MyTimerTask(this);
... ...
那個MyTimerTask是MainActivity的一個内嵌類,實作如下:
private class MyTimerTask extends TimerTask
...{
private MainActivity me;
private int a=0;
public MyTimerTask(MainActivity p)...{
me=p;
}
public void run()...{
me.gridView.time++;
Log.d("MyTask",Integer.toString(me.gridView.time));
timerHandler.sendEmptyMessage(0);
}
}
這裡做兩件事,一是将gridView中的time加一,二是發送一個消息通知timerHandler。原來我在這裡直接讓MainView去重新整理螢幕,發現不行,是以就改成這樣處理了。
然後就是如何實作TimerHandler類的,也不複雜,就是讓它去重新整理一下螢幕即可。
public class TimerHandler extends Handler ...{
private MainView me;
public TimerHandler(MainView m)...{
me=m;
}
@Override
public void handleMessage(Message msg) ...{
Log.d("Ti",msg.toString());
me.invalidate();
}
}
如此一來,就順了。
在MainView中的onDraw,根據目前的time值顯示成00:00:00的格式即可。
另外,發現Android的模拟器運算速度不如BREW的模拟器,相當的慢。
URL: http://www.sf.org.cn/Android/lumen/20986.html
Android學習筆記(12)-開始做一個數獨遊戲[補充]
再補充一點吧,如果需要給遊戲加上背景音樂,其實也是非常容易的事情。因為Android提供了一個MediaPlayer類可以友善的播放音樂檔案。
android.media.MediaPlayer類沒有構造函數,一般是用它的靜态方法create生成執行個體,簡單地告訴它音樂檔案的資源ID即可(支援mp3/wav/midi等)。
首先,我們得建立一個Service,就叫MediaService吧,它的代碼如下:
Android/UploadFiles_8448/200804/20080419152842539.gif"align=top>Android/UploadFiles_8448/200804/20080419152843671.gif"align=top>public class MediaService extends Service implements MediaPlayer.OnErrorListener,MediaPlayer.OnCompletionListener,MediaPlayer.OnPreparedListener ...{
... ...
private MediaPlayer player;
@Override
protected void onDestroy() ...{
// TODO Auto-generated method stub
super.onDestroy();
if(player!=null)...{
player.stop();
player.release();
}
}
@Override
protected void onStart(int startId, Bundle arguments) ...{
// TODO Auto-generated method stub
Log.d("Media","onStart");
player=MediaPlayer.create(this.getApplication(),R.raw.tonhua);
if(player!=null)...{
player.setAudioStreamType(AudioSystem.STREAM_MUSIC);
player.setOnCompletionListener(this);
player.setOnPreparedListener(this);
player.setOnErrorListener(this);
player.prepareAsync();
}
}
@Override
public void onCompletion(MediaPlayer arg0) ...{
// TODO Auto-generated method stub
Log.d("Media","finished.");
}
@Override
public void onPrepared(MediaPlayer arg0) ...{
// TODO Auto-generated method stub
Log.d("Media","prepared.");
player.start();
}
@Override
public void onError(MediaPlayer arg0,int what, int extra) ...{
Log.d("Media","onError");
player.stop();
}
}
這個服務主要就是用一個MediaPlayer去播放資源中的tonghua(一首MP3音樂)。次序一般是先create出這個執行個體,然後prepare一下(如果是檔案直接prepare,如果是流則最好異步parepareAsync),接着就可以start了,同步可以直接start,異步則必須放到onPrepared中再start。
在MainActivity中啟動這個服務即可。
mediaServiceIntent= new Intent();
mediaServiceIntent.setClass(this, MediaService.class);
this.startService(mediaServiceIntent, new Bundle());
目前,在Activity停止時也别忘了将這個Service停掉,而在Service停止時關掉MediaPlayer。
在模拟器上試了,效果不是太好,聲音有點斷斷續續,不知道是不是我的解碼器的問題(Vista系統)。
URL: http://www.sf.org.cn/Android/lumen/20987.html
消息機制,異步和多線程
有了framework後,我們不用面對赤裸裸的OS API,做一些重複而繁雜的事情。但天下沒有免費的午餐,我們還是需要學會高效正确的使用不同的framework,很多處理某一特定問題的手法在不同的framework中,用起來都會有所不同的。
在Android中,下層是Linux的核,但上層的java做的framework把這一切封裝的密不透風。以消息處理為例,在MFC中,我們可以用PreTranslateMessage等東東自由處理消息,在C#中,Anders Hejlsberg老大說了,他為我們通向底層開了一扇“救生窗”,但很遺憾,在Android中,這扇窗戶也被關閉了(至少我現在沒發現...)。
在Android中,你想處理一些消息(比如:Keydown之類的...),你必須尋找Activity為你提供的一些重載函數(比如 onKeyDown之類的...)或者是各式各樣的listener(比如OnKeyDownListner之類的...)。這樣做的好處是顯而易見的,越多的自由就會有越多的危險和越多的晦澀,條條框框畫好了,用起來省心看起來省腦,這是一個設計良好的framework應該提供的享受。對于我目前的工程而言,我沒有什麼BT的需求在目前API下做不到的,google的設計ms還是很nice的。
但世界是殘酷的,有的時候我們還是必須有機制提供消息的分發和處理的,因為有的工作是不能通過直接調用來同步處理的,同時也不能通過Activity中内嵌的消息分發和接口設定來做到,比如說事件的定時觸法,異步的循環事件的處理,高耗時的工作等等。在Android中,它提供了一些蠻有意思的方式來做這件事情(不好意思,我見不多識不廣,我沒見過類似玩法,有見過的提個醒 && 嘴下超生^_^),它有一個android.os.Handler的類,這個類接受一個Looper參數,顧名思義,這是一個封裝過的,表征消息循環的類。預設情況下,Handler接受的是目前線程下的消息循環執行個體,也就是說一個消息循環可以被目前線程中的多個對象來分發,來處理(在UI線程中,系統已經有一個Activity來處理了,你可以再起若幹個Handler來處理...)。在執行個體化一個 handlerInstance之後,你可以通過sendMessage等消息發送機制來發送消息,通過重載handleMessage等函數來分發消息。但是!該handlerInstance能夠接受到的消息,隻有通過handlerInstance.obtainMessage構造出來的消息(這種說法是不确切的,你也可以手動new一個Message,然後配置成該handlerInstance可以處理的,我沒有跟進去分析其識别機制,有興趣的自己玩吧^_^)。也就是說A, B, C, D都可以來處理同一線程内的消息分發,但各自都隻能處理屬于自己的那一份消息,這抹殺了B想偷偷進入A領地,越俎代庖做一些非份之事的可能(從理論上看,B還是有可能把消息僞裝的和A他們家的一樣,我沒有嘗試挑戰一下google的智商,有BT需求的自行研究^_^)。這樣做,不但兼顧了靈活性,也確定了安全性,用起來也會簡單,我的地盤我做主,不用當心傷及無辜,左擁右抱是一件很開心的事情。。。
很顯然,消息發送者不局限于自己線程,否者隻能做一些定時,延時之類的事情,豈不十分無趣。在執行個體化Handler的時候,Looper可以是任意線程的,隻要有Handler的指針,任何線程也都可以sendMessage(這種構造方式也很有意思,你可以在A線程裡面傳B線程的Looper來構造 Handler,也可以在B線程裡構造,這給記憶體管理的方法帶來很大的變數...)。但有條規則肯定是不能破壞的,就是非UI線程,是不能觸碰UI類的。在不同平台上有很多解決方式(如果你有多的不能再多的興趣,可以看一下很久很久以前我寫的一個,不SB不要錢)。我特意好好跟了一下android中的AsyncQueryHandler類,來了解google官方的解決方案。
AsyncQueryHandler是Handler的子類,文檔上說,如果處理ContentProvider相關的内容,不用需要自行定義一套東西,而可以簡單的使用async方式。我想指代的就應該是AsyncQueryHandler類。該類是一個典型的模闆類,為ContentProvider的增删改查提供了很好的接口,提供了一個解決架構,final了一些方法,置空了一些方法。通過派生,執行個體化一些方法(不是每個對 ContentProvider的處理多需要全部做增删改查,我想這也是該類預設置空一些方法而不是抽象一些方法的原因),來達到這個目的。在内部,該類隐藏了多線程處理的細節,當你使用時,你會感覺異常便利。以query為例,你可以這麼來用:
// 定義一個handler,采用的是匿名類的方式,隻處理query,是以隻重寫了onQueryComplete函數:
queryHandler = newAsyncQueryHandler(this.getContentResolver()){
// 傳入的是一個ContentResolver執行個體,是以必須在OnCreate後執行個體化該Handler類
@Override
protected void onQueryComplete(int token,Object cookie, Cursor cursor) {
// 在這裡你可以獲得一個cursor和你傳入的附加的token和cookie。
// 該方法在目前線程下(如果傳入的是預設的Looper話),可以自由設定UI資訊
}
};
// 調用時隻需要調用startQuery(int token, Object cookie, ContentURI uri, String[]projection, String selection, String[] selectionArgs, String sortOrder)函數即可:
queryHandler.startQuery(token, cookie, uri,projection, selection, selectionArgs, sortBy);
可見,該類的使用是多麼簡單(其實作可不會很容易,因為我嘗試做了一次造車輪的工作*_*),比直接用Handler簡單無數倍。但讓我倍感孤獨的是,不知道是沒人做異步的ContentProvider通路,還是這個類使用太過于弱智(這個使用方法可是我摸索了半天的啊,難道我真的如此的弱@[email protected]),抑或是大家都各有高招,從SDK到網上,沒有任何關于該類的有點用的說明。而我又恰巧悲傷的發現,這個類其實有很多的問題,比如他吃掉異常,有錯誤時隻是簡單的傳回null指針(這個其實不能怪他,你可以看看這裡...);當你傳一個null的ContentResolver進去的時候,沒有任何異常,隻是莫名其妙的丢棄所有消息,使你陷入苦苦的等待而不知其因;更憤慨的是,他的token傳遞竟然有Bug(難道還是我使用不對&_&),從startXX傳入的token,到了onXXComplete裡面一律變成1,而文檔上明明寫着兩個是一個東西(我的解決方法是用cookie做token,這個不會丢*_*)。不過我暫時還沒有遺棄它的打算,雖然沒人理睬,雖然有一堆問題,雖然我按圖索骥造了個新輪子,但為了節省剩下的一些無聊的工作,我決定苟且偷生了。。。
還是習慣性跑題了,其實,我是想通過我對這個類的無數次Debugger跟進,說說它的多線程異步處理的解決政策的。他的基本政策如下:
1. 當你執行個體化一個AsyncQueryHandler類時(包括其子類...),它會單件構造一個線程(後面會詳述...),這個線程裡面會建構一個消息循環。
2. 獲得該消息循環的指針,用它做參數執行個體化另一個Handler類,該類為内部類。至此,就有了兩個線程,各自有一個Handler來處理消息。
3. 當調用onXXX的時候,在XXX函數内部會将請求封裝成一個内部的參數類,将其作為消息的參數,将此消息發送至另一個線程。
4. 在該線程的Handler中,接受該消息,并分析傳入的參數,用初始化時傳入的ContentResolver進行XXX操作,并傳回Cursor或其他傳回值。
5. 構造一個消息,将上述傳回值以及其他相關内容綁定在該消息上,發送回主線程。
6. 主線程預設的AsyncQueryHandler類的handleMessage方法(可自定義,但由于都是内部類,基本沒有意義...)會分析該消息,并轉發給對應的onXXXComplete方法。
7. 使用者重寫的onXXXComplete方法開始工作。
這就是它偷偷摸摸做過的事情,基本還是很好了解的。我唯一好奇的是它的線程管理方式,我猜測他是用的單件模式。第一個AsyncQueryHandler的執行個體化會導緻建立一個線程,從此該線程成為不死老處男,所有的ContentResolver相關的工作,都由該線程統一完成。個人覺得這種解決方式很贊。本來這個線程的生命周期就很難估量,并且,當你有一個ContentProvider的請求的時候,判斷你會做更多的類似操作并不過分。就算錯了,花費的也隻是一個不死的線程(與程序同生死共存亡...),換來的卻是簡單的生命周期管理和無數次線程生死開銷的節約。同時另外一個很重要的問題,他并會涉及到單件中資料同步的問題,每個類都有各自的Handler類,彼此互不幹擾,分發可以分别進行。當多個資料請求的時候,在同一個ContentResolver上進行的可能微乎其微,這就避免了堵塞。總而言之,這套解決辦法和Android的整體設計算是天作之合了。
是以建議,如果你有什麼非ContentProvider操作,卻需要異步多線程執行的話,模拟一套,是個不錯的政策,當然,具體情況具體分析,生搬硬套是學不好馬列主義的。。。
URL: http://www.sf.org.cn/Android/lumen/21075.html
顯示控件使用
Android的界面顯示同樣也是基于控件的。通常是用View(包括ViewGroup)控件配上XML的樣式來做的。具體細節不想說了,可以參考 Samples裡的ApiDemos/View,和View的Doc,以及Implementinga UI這篇Doc。其他還有很多,感覺算是SDK講述的最多的内容。
從控件的使用上,和網頁的設計類似,盡量用parent_width之類的抽象長度,用Theme來做風格,抽取所有的字串等資訊做本地化設計。相關内容參看Implementinga UI就好。
一類比較重要的是資料綁定控件。如果做過ASP.Net會從中看到很多類似的地方。一個支援資料綁定的控件,比如List View。可以通過一個 ListAdapter綁定到一個資料源上。ListAdapter是一個抽象類,主要的實作類包括SimpleAdapter和 SimpleCursorAdapter。前者是綁定一個靜态的Array,後者是綁定一個動态的Cursor。Cursor前面說過,是一個指向資料源的随機疊代器,将View綁定到Cursor通常要設定這樣幾個參數。一個是每一行的樣式,稱作Row Layout,其實就是一個普通的Layout的XML檔案。還有就是一個列和現實控件的對應關系。那個控件顯示哪個列的值,這是需要配置的。為了定制一個良好的資料顯示控件,最簡單你可以定制很PP的Row Layout,複雜一點就是可以重載綁定控件View,或者是擴充卡ListAdapter。如果是一個資料顯示密集的應用,且你對UI有些追求,這個工作估計是必不可少的。
一個主要用于顯示資料内容的Activity,可以選擇派生自List Activity。它提供了一個具有List View 的Layout,還有simple_list_item_1,simple_list_item_2, two_line_list_item等預設的Row Layout,還有一些比較不錯的API,和可供響應選擇Item的事件。可以滿足你比較基礎的需求。如果你覺得隻有一個List View的界面太突兀,你可以為這個List Activity指定一個Layout,需要注意的是,你需要提供一個id為@android:id/list的ListView控件,避免Activity在内部偷偷尋找該控件的時候失敗。
除了這些要求,做好UI還有注意易用性和效率。快捷鍵是一個比較不錯的選擇,在 Activity中調用setDefaultkeyMode(SHORTCUT_DEFAULT_KEYS),可以開啟快捷鍵模式,然後你可以将菜單綁定到指定快捷鍵上就OK了。個人覺得Tip也是一個比較重要的東西,但目前觀察看來,這個東西隻能夠自己提供了。界面的動态性有時候是不可避免的,比如說菜單就是一個需要經常根據光标位置提供不同的選項。這個東西Android很人道的考慮到了,你可以參看NodeList這個Sample。它采取的應該是一個靜态模拟動态的方式,這樣有助于提高速度。你也可以利用ViewInflate,動态從一個XML建立一個控件。成本據Doc說很大,不到萬不得已不要使用。
URL: http://www.sf.org.cn/Android/lumen/21073.html
Intent消息傳遞
在前面寫Android的ContentProvider時候,可以看到那是基于觀察者模式的一個消息傳遞方法。每一個Cursor、ContentResolver做為一個小的注冊中心,相關觀察者可以在這個中心注冊,更新消息由注冊中心分發給各個觀察者。而在MFC或Winform中,都會形成一個消息網,讓消息在網中流動,被各節點使用、吃掉或者在出口死掉。
相比之下,我個人覺得基于Intent的Android核心消息傳遞機制是有所不同的。它應該會有一個全局性的注冊中心,這個注冊中心是隐性的,整個Android系統中就那麼一個。所有的消息接收者,都被隐形的注冊到這個中心。包括Activity,Service和IntentReceiver。其實說隐形注冊是不确切的,所有注冊都還是我們手動告訴注冊中心的,隻是與傳統的方式不一樣,我們通常不是通過代碼,而是通過配置檔案來做。在應用的Manifest中,我們會為一些Activity或Service添加上Intent-filter,或在配置檔案中添加<receiver></receiver>項。這其實就相當于向系統的注冊中心,注冊了相關的Intent-filter和receiver(這個事情完全可以通過代碼來做,隻是這樣就失去了修改的靈活性)。
當程式有一個消息希望發出去的時候,它需要将消息封裝成一個Intent,并發送。這時候,應該是有一個統一的中心(恩,有可能Android底層實作的時候不是,但簡單這樣看是沒問題的...)接受到這個消息,并對它進行解析、判定消息類型(這個步驟降低了耦合...),然後檢查注冊了相比對的filter或receiver,并建立或喚醒接收者,将消息分發給它。這樣做有很多好處。雖然這種傳遞有的時候不如點對點的傳遞快(這有些需要速度的地方,我們看到Android會通過直接通信來做),但有時候又因為它隻經過一跳(姑且這麼叫吧...),比複雜的流動又要更快。更重要的是,它耦合性低,在手機平台這種程式元件多變的條件下使用十分适合。并且它可以很容易實作消息的精确或模糊比對,彈性很大。(我個人曾想在開發一個C++二次平台的時候引入這樣的機制,但在C++中,建立一套完整的資料marshal機制不容易,相比之下,用java來做會簡單很多...)
恩,廢話說了很多,具體講講Android中Intent的使用。當你有一個消息需要傳遞,如果你明确知道你需要哪個Activity或者其他Class來響應的話,你可以指定這個類來接受該消息,這被稱為顯性發送。你需要将Intent的class屬性設定成目标。這種情況很常見,比如startActivity的時候,會清楚目前Activity完了應該是哪個Activity,那就明确的發送這個消息。
但是,有的時候你并不确定你的消息是需要具體哪個類來執行,而隻是知道接收者該符合哪些條件。比如你隻需要有一個接收者能顯示使用者所選的資料,而不想制定某個具體的方法,這時候你就需要用到隐形發送(傳統上,我們可能會考慮用多态,但顯然這種方式更為靈活...)。在Android中,你可以為Intent指定一個action,表示你這個指令需要處理的事情。系統為我們定義了很多Action類型,這些類型使系統與我們通信的語言(比如在Activity裡面加一個Main的filter,該activity就會做成該應用的入口點),當然你也可以用于你自己的應用之間的通信(同樣當然,也可以自定義...)。強烈建議,在自己程式接收或發出一個系統action的時候,要名副其實。比如你響應一個view動作,做的确實edit的勾當,你發送一個pick消息,其實你想讓别人做edit的事,這樣都會造成混亂。當然隻有Action有時候是不夠的,在Android中我們還可以指定catalog資訊和type/data資訊,比如所有的顯示資料的Activity,可能都會響應View action。但很多與我們需要顯示的資料類型不一樣,可以加一個type資訊,明确的指出我們需要顯示的資料類型,甚至還可以加上一個catalog資訊,指明隻有你隻有按的是“中鍵”并發出這樣的消息才響應。
從上面可以看出,Android的Intent可以添加上class, action, data/type, catalog等消息,注冊中心會根據這些資訊幫你找到符合的接收者。其中class是點對點的訓示,一旦指明,其他資訊都被忽略。Intent中還可以添加key/value的資料,發送方和接收方需要保持統一的key資訊和value類型資訊,這種資料的marshal在java裡做,是不費什麼力氣的。
Android的Intent發送,可以分成單點傳播和廣播兩種。廣播的接收者是所有注冊了的符合條件的IntentReceiver。在單點傳播的情況下,即使有很多符合條件的接收者,也隻要有一個出來處理這個消息就好(恩,個人看法,沒找到确切條款或抉擇的算法,本來想實驗一下,沒來得及...),這樣的情況很容易了解,當你需要修改某個資料的時候,你肯定不會希望有十個編輯器輪流讓你來處理。當廣播不是這樣,一個receiver沒有辦法阻止其他receiver進行對廣播事件的處理。這種情況也很容易了解,比如時鐘改變了,鬧鐘、備忘錄等很多程式都需要分别進行處理。在自己的程式的使用中,應該厘清楚差別,合理的使用。
URL: http://www.sf.org.cn/Android/lumen/21072.html
ContentProvider資料模型概述
Android的資料(包括files, database等...)都是屬于應用程式自身,其他程式無法直接進行操作。是以,為了使其他程式能夠操作資料,在Android中,可以通過做成 ContentProvider提供資料操作的接口。其實對本應用而言,也可以将底層資料封裝成ContentProvider,這樣可以有效的屏蔽底層操作的細節,并且是程式保持良好的擴充性和開放性。
ContentProvider,顧名思義,就是資料内容的供應者。在Android中它是一個資料源,屏蔽了具體底層資料源的細節,在ContentProvider内部你可以用Android支援的任何手段進行資料的存儲和操作,可能比較常用的方式是基于Android的SQLite資料庫(恩,文檔中和示例代碼都是以此為例)。無論如何,ContentProvider是一個重要的資料源,可以預見無論是使用和定制ContentProvider都會很多。于是花了點時間仔細看了看。
資料庫操作
從我目前掌握的知識來看,SQLite比較輕量(沒有存儲過程之類的繁雜手段),用起來也比較簡單。執行個體化一個SQLiteDatabase類對象,通過它的APIs可以搞定大部分的操作。從sample中看,Android中對db的使用有一種比較簡單的模式,即派生一個 ContentProviderDatabaseHelper類來進行SQLiteDatabase對象執行個體的擷取工作。基本上, ContentProviderDatabaseHelper類扮演了一個singleton的角色,提供單一的執行個體化入口點,并屏蔽了資料庫建立、打開更新等細節。在ContentProvider中隻需要調用ContentProviderDatabaseHelper的openDatabase方法擷取SQLiteDatabase的執行個體就好,而不需要進行資料庫狀态的判斷。
URI
像進行資料庫操作需要用SQL一樣,對ContentProivder進行增删改查等操作都是通過一種特定模式的URI來進行的(ig:content: //provider/item/id),URI的能力與URL類似,具體細節可以檢視SDK。建立自己的ContentProvider,隻需要派生 ContentProivder類并實作insert, delete, update等抽象函數即可。在這些接口中比較特殊的是getType(uri)。根據傳入的uri,該方法按照MIME格式傳回一個字元串(==!沒聽過的詭異格式...)唯一辨別該uri的類型。所謂uri的類型,就是描述這個uri所進行的操作的種類,比如content://xx/a與 content://xx/a/1不是一個類型(前者是多值操作,後者是單值),但content://xx/a/1和content://xx/a/2 就會是一個類型(隻是id号不同而已)。
在ContentProvider通常都會執行個體化一個ContentURIPraser來輔助解析和操作傳入的URI。你需要事先(在static域内)為該ContentURIPraser建立一個uri的文法樹,之後就可以簡單調用 ContentURIPraser類的相關方法進行uri類型判斷(match方法),擷取加載在uri中的參數等操作。但我看來,這隻是在使用上簡化了相關操作(不然就需要自己做人肉解析了...),但并沒有改變類型判定的模式。你依然需要用switch...case...對uri的類型進行判斷,并進行相關後續的操作。從模式來看,這樣無疑是具有強烈的壞味道,類似的switch...case...代碼要出現N此,每次一個 ContentProvider做uri類型的增減都會需要周遊修改每一個switch...case...,當然,如果你使用模式(政策模式...)進行改造對手機程式來說無疑是崩潰似的(類型膨脹,效率降低...),是以,隻能是忍一忍了(恩,還好不會擴散到别的類中,維護性上不會有殺人性的麻煩...)。
增删改查
ContentProvider 和所有資料源一樣,向外提供增删改查操作接口,這些都是基于uri的指令。進行insert操作的時候,你需要傳入一個uri和 ContentValues。uri的作用基本就限于指明增減條目的類型(從資料庫層面來看就是table名),ContentValues是一個 key/value表的封裝,提供友善的API進行插入資料類型和資料值的設定和擷取。在資料庫層面上來看,這應該是column name與value的對應。但為了屏蔽ContentProvider使用者涉及到具體資料庫的細節,在Android的示例中,用了一個小小的模式。它為每一個表建一個基于BaseColumn類的派生類(其實完全可以不派生自BaseColumn,特别當你的表不基于預設的自動id做主鍵的時候),這個類通常包括一個描述該表的ContentURI對象和形如 public static final TITLE = "title"這樣的column到類資料的對應。從改變上角度來看,你可以修改column的名字而不需要更改使用者上層代碼,增加了靈活性。 insert方法如果成功會傳回一個uri,該uri會在原有的uri基礎上增加有一個rowid。對于為什麼使用row id而不是key id我想破了腦袋。到最後,我發現我傻了,因為ContentProvider不一定需要使用資料庫,使用資料庫對應的表也可以沒有主鍵,隻有row id,才能在任何底層媒體下做索引辨別。
但,基于row id在删除和修改操作是會造成一定的混亂。删除和修改操作類似。删除操作需要傳入一個uri,一個where字串,一組where的參數(做條件判定...),而修改操作會多一個ContentValues做更新值。着兩個操作的uri都支援在末尾添加一個row id。于是混亂就出現了。當在where參數中指明了key id,而在uri中提供了row id,并且row id和keyid所指函數不一緻的時候,你聽誰的?示例代碼中的做法是完全無視row id(無語...),如此野蠻的方式我估計也隻能在示例中出現,在實際中該如何用,恩,我也不知道。幸運的是,我看了下上層對 ContentProvider的删除操作,其實都不會直接進行,而是通過調用Cursor的delete方法進行,在這前提下,我想Cursor會處理好這些東西吧。
最後一個操作是查詢操作,可以想見,查詢的參數是最多的,包括uri和一組條件參數。條件參數類型和标準的sql類似,包括 sort, projection 之類的。從這些參數到sql語句的生成,可以尋求QueryBuilder類的幫助,它提供了一組操作接口,簡化了參數到sql的生成工作,哪怕你不懂 sql都完全沒有問題(這話說的我自己都覺得有點懸...)。查詢傳回一個Cursor。Cursor是一個支援随機讀寫的指針,不僅如此,它還提供了友善的删除和修改的API,是上層對ContentProvider進行操作一個重要對象,需要仔細掌握(Cursor還可以綁定到view上,直接送顯,并與使用者進行互動,真是程式越往上,封裝越好,工作越機械沒有複雜性了...)。
資料模型
在與界面打交道的Cursor、ContentResolver等資料操作層中,大量采用觀察者模式建立資料層與顯示層的聯系。一個顯示層的視圖,可以做成某一種觀察者注冊到Cursor或ContentResolver等資料中間層中,在實作底層ContentProvider中,我們需要特别注意在對資料進行修改操作(包括增删改...)後,調用相應類型的notify函數,幫助表層對象進行重新整理(還有一種重新整理方式是從一個view發起的)。可以看到 Android的整體資料顯示架構有點像MVC的方式(貧瘠了...叫不出名)。Cursor、ContentResolver相當于控制層,資料層和顯示層的互動通過控制層來掌管,而且控制層很穩定不需要特别定制,通常工作隻在定制資料層和顯示層空間,還是比較友善和清晰的。
一個設計問題
現在有個設計問題,比如我要擴充一個已有的ContentProvider(第三方提供),我是建立一個ContentProvider,隻保留第三方 ContentProvider的key資訊,并為其添加更多的資訊,在表層維護這兩個ContentProvider的聯系好;還是建議一個 ContentProvider,以第三方的ContentProvider做一部分底層資料源,像表層提供一個ContentProvider好。
前者無疑在實作上簡單一些,如果第三方改變,靈活性也更好,隻是需要仔細維護表層的相關代碼。後者實作上需要付出大量的苦力勞動,當表層使用會簡單多了。我舉棋不定,期待你的意見。。。
自定義ContentProvider的語義
ContentProvider中,最重要的就是query操作。query根據輸入傳回一個符合條件的Cursor。這就可能出現以下幾種情況:1. 查詢成功,包含幾個正确的結果;2. 查詢失敗,沒有符合的結果;3. 輸入錯誤, 觸發了某個異常;4. 沒能查詢到結果,但無法确定是輸入錯誤還是查詢失敗。第一種情況是我們最需要的,當然是需要正确維系的,而最後一種情況在大部分應用中應該不會出現(但在我的應用中會的*_#),而第二種第三種是比較常見的。
經過我的測試,系統的ContentProvider維持這樣的語義:如果是情況2,傳回正常的Cursor,并且,其count為0,相當于empty cursor;如果是情況3,不抛出任何異常,傳回null的Cursor。這樣的話明明白白寫出來是很好了解的,但由于沒有官方的文檔說明,在自定義的時候經常會誤用。比如在某些情況下,用null表征查詢失敗,用抛出異常來描述錯誤的輸入。
傳回empty cursor,如果是通過databasecursor自然會有db幫你維護,但是如果傳回ArrayListCursor,MergeCursor或其他自定義的Cursor,就需要自己維系了。ArrayListCursor可以通過new ArrayListCursor(Columns, new ArrayList(){})來提供。其中Columns一定不為null。MergeCursor不能以new MergeCursor(new Cursor[]{})來建立,而需要通過newMergeCursor(new Cursor[]{aEmptyCursor, ...}來維系(其實很好了解,我呆了...)。自定義的Cursor也一定要提供生成empty cursor的方式。
如果将ContentProvider作為一個單獨的module來了解,不通過異常而是通過null來傳回MS是有好處的。在module的出口吃掉所有異常,雖然不能提供足夠的資訊(異常資訊全部寫入日志),但可能會使上層使用更簡單。但在Android中,我并沒有感覺到這一點。作為ContentProvider的上層函數,ListActivity.managedQuery、ListView.setListAdapter等,根本不能處理一個null的Cursor,在ListView中這會觸發一個異常。更無語的是,當你把一個null Cursor設定為manage的後。它不會立即抛異常,而是在OnFreeze等生命周期函數的時候,因無法處理null Cursor而抛出一個異常。這使得你根本無法在當地catch該異常,換句話,ListActivity的manageCursor根本是個無法使用的函數。你必須用getContext().query()獲得Cursor,然後判定該Cursor是否null,在進行startManagingCursor進行綁定。這遠不如直接用異常進行錯誤路徑的處理來的統一和友善。
當然,有些東西我們是不能改變的,隻能去适應。對于自定義的cursor, ContentProvider,最重要的,是在無人造錯誤輸入的情況下傳回empty cursor,而不是null。至于使用null響應還是異常響應上,我個人覺得還是和系統同步為好,雖然别扭,但至少統一不容易有歧義。
此外,ContentProvider還有很多細緻的語義。比如傳回的Cursor需要綁定一個URI,以便自動響應更新。自定義的更新需要支援deleteRow等操作語義等等。
PS:而上層的ListView,更是陷阱重重。首先綁定到ListView的Cursor必須有_id項,否則會有異常抛出。如果做過.net的開發,這一點是可以想到的,但是,這種問題應該在文檔中寫明。另外,在ListView中,如果你不綁定一個資料源,你一定不能在layout中添加涉及内容的屬性。比如android:height="wrap_content",這會在onMeasure的時候抛出異常。