顯示具有 android developer 標籤的文章。 顯示所有文章
顯示具有 android developer 標籤的文章。 顯示所有文章

2011年1月9日 星期日

號稱全球最簿,屏幕最光亮的 LG Optimus Black


雙核 NVIDIA Tegra 2  無疑是個大賣點,可是太多同類型產品使用的話,消費者就反而不太理會,那廠商只好著手在屏幕的改進,在 CES 2011 會場上,各大廠都展示自己的秘密武器,差不多所有新款 Android 手機都採用新技術的顯示屏幕,包括 Motorola 的 960 x 540 LCD、Samsung 的 Super AMOLED Plus、Sony Ecrission 的 Reality Display 面板+ Mobile BRAVIA  Engine,而幫 Apple iPhone 4 製造Retina Display IPS 面板的 LG 也推出號稱全球最光亮、較慳電的 NOVA 屏幕,唯獨 HTC 仍停濟不前。
LG 在 CES 上展示了三款新手機,其中 Optimus Black 就採用上文提及過的 NOVA 屏幕,特別之處是提供 700nit 光亮度,較一般的 LCD,甚至比大家認為很光的 Super AMOLED 更光亮,LG 稱之為世上最光亮的屏幕,令手機提供給用戶最明亮、最清晰的閱讀體驗,亦可在陽光強烈的戶外也能維持可見度。除此之外,LG 還表示 NOVA 能夠節能 15% ,更比起傳統 LCD 及 AMOLED 節省高達 50% 的耗電量,尤其在純白畫面的情況下,更比 AMOLED 足足省電兩倍。光看文字介紹,已經覺得 NOVA 很好很強大,期望盡快有跟其他屏幕面板的比試報告。
除了 NOVA 屏幕外,LG Optimus Black 另一賣點是機身纖薄,厚度僅 9.2mm,最薄之處只有 6mm,不過最厲害的地方,電池容量仍然維持正常的 1500mAh 水平,沒有因為機身太薄而減少,可見 LG 在手機設計造工十分出色。最後要介紹的是 Wi-Fi Direct 無線傳輸技術,LG Optimus Black 是全球首部原生支援此技術的智能手機,可讓手機沒有 Router 或 Hotspot 的環境下,透過 Wi-Fi 直接在手機之間傳輸檔案,表面上與藍芽技術很相似,不過 Wi-Fi Direct 的傳輸距離及速度較佳。其餘規格筆者都已經整理好,另外,還收集多段 Optimus Black 的影片,跳轉可以觀看。
Optimus Black 重點規格如下:
  • 雙核處理器,暫時不知是否用 NVIDIA Tegra 2
  • 4吋 NOVA 電容或觸控屏幕,自家 IPS 面板,解像度為 800×480,1600萬色
  • 螢幕採用 Power of light 技術
  • Android 2.2,可升級到 Android 2.3
  • 500萬像素相機鏡頭,支援 1080p 影像拍攝
  • 200萬像素前置鏡頭
  • 自家 Optimus UI 2.0 介面,Gesture UI
  • MicroSD 卡擴充,最高支援 32GB
  • 1500mAh 電池
  • Wi-Fi 802.11 b/g/n
  • 支援 Wi-Fi Direct 無線傳輸技術
  • 121 x 63.5 x 9.2 mm ,最薄之處只有 6mm
  • 重 109g

Source:http://www.android-hk.com/news/lg-optimus-black/

2010年12月23日 星期四

Android版Google Maps 5.0可離線瀏覽

Android版Google Map 5.0地圖改採向量繪圖方式製作,全球一百多個城市中還以3D模型方式建立,用雙指放大縮小幾乎不受限制,還可傾斜不同角度或旋轉方向觀看地圖。

Google為Android版Google Map 5.0推出新功能。新版本改採向量繪製的地圖,支援羅盤模式及離線地圖功能;同時也加入了3D模型。

新版地圖改採向量繪圖方式製作,全球一百多個城市中還以3D模型方式建立,用雙指放大縮小幾乎不受限制,還可傾斜不同角度或旋轉方向觀看地圖。新增的羅盤模式搭配電子羅盤與定位,即可隨著使用者旋轉、移動地圖。由於向量的資料量僅為以往點陣圖模式的百分之一,新版可以將使用者查閱過的地圖儲存在手機當中,因此當手機無法連線時仍可以查閱地圖、導航或做路徑規劃。

該版軟體Google先前介紹Android 2.3時就已經出現,目前可以在Android Market中下載。Android 1.6及更新版本即可安裝使用,但Android 2.0或更新版本才能使用3D、離線等功能,部分功能需要搭配衛星定位、電子羅盤、多點觸控等才能使用。

Source:http://www.ithome.com.tw/itadm/article.php?c=65092

2010年12月22日 星期三

Android Market 類別更新

相信大部份用戶都已經把手機中的 Android Market 更新至 2.2.7 版本,但不知有沒有發現到其實有部份類別亦已作出修改,甚至新增了一些新類別?作為普通用戶,是次更新固然能夠讓大家方便快捷地尋找心水軟件;而對開發 者,就更加需要留意有沒有軟件要註冊至另一個類別之中,增加曝光度。Android Market 類別更新如下:
應用程式新增類別:
  • 圖書與參考資源(Books & Reference)
  • 商業(Business)
  • 教育(Education)
  • 醫療(Medical)
  • 音樂與音效(Music & Audio)
  • 個人化(Personalization)
  • 攝影(Photography)
  • 交通(Transportation)
  • 天氣(Weather)
應用程式修改類別:
  • 健康(Health)→健康塑身(Health & Fitness)
  • 旅行(Travel)→旅行與地方資訊(Travel & Local)
  • 程式庫(Software Libraries)→程式庫與試用程式(Libraries & Demo)
應用程式刪除類別:
  • 參考資源(Reference)
遊戲新增類別:
  • 運動(Sports Games)
via: androidpolice

Source: http://www.android-hk.com/news/android-market-category-update/

內容為王的時代

The era of content is king
你覺得一台 Casio 的計算機和個人電腦相比,差異點有哪些?
這個問題,應該很好回答,隨便都可以找到差異點。但是如果我將個人電腦上的軟體都移除,只剩下 "小算盤" 這個軟體,並且將個人電腦上可自行安裝軟體的功能也關閉。此時,你覺得這台個人電腦和 Casio 計算機的差異在哪?我猜你的回答可能只剩下,螢幕比較大,外型比較漂亮還有價錢比較貴。是嗎?
我相信這你很清楚這計算機與個人電腦相比的例子。我再問你,你覺得 功能性手機 (feature phone)智慧型手機 (smart phone) 相比,他們的差異點又有哪些?
我想多數人的回答都是,智慧型手機可以讓我自行安裝與移除應用程式。這答案絕對不是比較大台或是比較貴就是智慧型手機,更不是有 Apple, Symbian, Microsoft 或 Android 加持的就叫智慧型手機。
沒錯!智慧型手機的重點就在可讓使用者自行安裝其所需要的應用程式。手機只是個載體,在那上面執行的應用才是智慧的來源。舉例來說, 手機就像是人的軀體,裡面的 CPU 是人的大腦。但是光這樣,人還是無法變聰明,你必須要接受教育,讀很多書,才會有智慧。而每個人所讀的書,就像是手機上的應用。所以即使你我都是用 Nexus One 的 Android 手機,但因為每個人的需求不同,安裝的應用也會不同。用了一陣子之後,每台 Nexus One 手機,都會是獨一無二的,除非你只用內建的應用。
不過僅有 "可自行安裝應用" 這點還是不夠的。Windows Mobile 或 Symbian 之類的智慧型手機,好幾年前就有了,問問曾經用過這類智慧型手機的朋友,會自行安裝應用的有多少人?我想這答案應該很清楚,大家都不常自行安裝應用。為什 麼?因為他們不知道要去哪裡下載應用,也就是說他們不曉得書店在哪裡,要去哪裡買書。所以,這些人都將一台兩萬多元的智慧型手機,當 0 元功能性手機在使用。
我想一個智慧型手機的重點應該是,有個單一的入口點,讓使用者找到與安裝應用程式。iPhone 與 Android 手機透過內建的 Market 應用,解決了這個不知道去哪裡下載應用的問題。一個智慧型手機如果沒有了 Market 應用,就算他有 Android 的加持,哪和功能型手機有甚麼兩樣?聯發科依照其在功能性手機時代的成功模式,就算推出了 Android 解決方案,但是沒有了 Market 應用,那 MTK Android 手機和功能性手機的差別在哪?
有 Market 應用還不夠。一個 Market 如果不能像是一個甚麼都有的量販店,只能像是你家旁邊的小雜貨店,那有和沒有這個 Marekt,對使用者而言又有何差別?電信商推出的自家 Market,裡面的應用如果沒有數萬個。對使用者而言,如果手機已內建官方的 Market (如 AppStore 和 Android Market),還需要這個電信商的 Market 嗎?除非你自家的 Market 有獨到的特色。
我並不是說山寨智慧型手機 (沒有內建 Market 應用的智慧型手機),和山寨 Market (沒有貶低的意思,純粹是指非官方的 Markets) 沒有出路。單打獨鬥,我還沒看到成功的機會點。只有互相合作,才有成功的可能。這怎麼說?
會成功的山寨 Market,我個人覺得只有兩種。一種是專門做盜版的市場,那就真的是山寨 Market 了。不過,這只有在特定地區才有生存的空間。另一種會成功的山寨 Market,就是在手機內建應用市場上,能占有最高的市占率。要寫個 Market 應用,不是一件很困難的工作,困難的是如何能擁有數萬個應用,並能持續地更新和成長。如果 Market 的經營者,還想靠寄送 email 的方式,邀請開發者來免費支援的話。我可以說,這方式一點都不可行。我曾經有一個月,收到數十封這樣的 emails,他們的下場全是進了垃圾桶。我會這樣做,多數的開發者也是如此。
這道理,其實 Market 經營者也知道。只不過,他們還是以舊時代 (通路是王) 的思維在經營 Market。他們不知道的是,一個以內容 (應用及應用開發者) 為王的時代,已經悄悄開始了。
山寨 Market 必須結合山寨智慧型手機,才有機會。
對山寨智慧型手機製造商來說,只有內建 Market 應用,才能彰顯這智慧型手機的價值。山寨手機沒機會內建官方的 Market,如果能內建一個內容豐富的山寨 Market ,也算達到目的。對山寨 Market 經營者而言,只要能讓所有的山寨智慧型手機,都內建你的 Markete 應用。你根本不用去一一拜託眾多的應用開發者,只要你的 Market 有一定的市佔率,開發者自然會將應用放上來。要如何才能讓所有的山寨手機內建你的山寨 Market?我想這唯一的控球權,還是掌握在提供山寨手機平台解決方案提供者的那一方。
看完了手機,pad 與電子書也都有同樣的問題。pad 與電子書在硬體功能上,我覺得兩岸的廠商一定可以把他們發揮到極致,這是無庸置疑的。可是一個沒有 Market 的 pad,對使用者而言,這一台 pad 和 Casio 的計算機相比有何兩樣?在深圳,多數的 pads 多是執行 Android 系統。Google 短期內看來也不會將 Android Market 授權給 pad 廠商。所以這些 pad 和山寨智慧型手機都有相同的問題。沒有 Market 和沒有內容是相同的,沒有新應用,使用者很快就玩膩了。就電子書來說,一個只能下載有限書籍的電子書,他能在使用者的手上能用多久?台灣如果沒有一個獨立 的書籍內容發行商來整合眾家的出版社,我不覺得這電子書會有市場。
山寨智慧型手機、pad 與電子書,他們的問題,最終都會在內容上。只有手上握有內容的才是老大。
一個以內容為王的時代,已經悄悄開始了,這是個趨勢。最後的結果會是如何,我也不知道。不過,這世界就是因為有不斷的改變,才變得有趣。就讓我們一起拭目以待,一起走過。

Source:http://ysl-paradise.blogspot.com/2010/08/blog-post.html

2010年12月20日 星期一

Android Camera

在Camcorder中和Camera一樣有"setCameraParameters()"這個function用來設定Camera module的各種參數。
但Camcorder中使用此fuction需特別注意到...
由於Camcorder相對於Camera多了使用一個重要類別:MediaRecorder (這是Android system中用來錄音錄影的class api)。

他有幾個重要的life cycle...
1. MediaRecorder.prepare() (在initializeRecorder()中)
在此前設定好各種參數,不只包含Camera module的,也包含MediaRecorder本身的(如影片大小、長度、encorder....)。
2. MediaRecorder.start() (在startRecording()中)
開始錄影。
3. MediaRecorder.stop() (在stopRecordering()中)
停止錄影。
4. MediaRecorder.reset()及MediaRecorder.release()
停止錄影後,以及離開Camcorder ap時。

因此,在prepare() 前就必須把Camera的參數設定好,
那使用者若有改變如white balance等features的設定時,
如果直接使用setCameraParameters()的話,即會得到"set parameter failed!"的exception。

正確的使用方式必須先將已initialized的MediaRecorder先release後,
然後使用Camera.lock(),再setCameraParameters(),然後設定完參數再Camera.unlock(),
最後再重新初始化一個新的MediaRecorder(即再call initializeRecorder())。
這才是正確的使用流程,這裡請特別注意!!

相關的api請見:
MediaRecorder   http://developer.android.com/reference/android/media/MediaRecorder.html
Camera(內有lock()及unlock()) http://developer.android.com/reference/android/hardware/Camera.html

2010年12月19日 星期日

未來的趨勢LBS(Location-Based Service)

基於位置的服務(Location-Based Service,LBS)又稱適地性服務移動定位服務位置服務,它是透過移動運營商無線電通訊網路(如GSM網、CDMA網)或外部定位方式(如GPS)獲取移動終端使用者的位置訊息(地理座標)。在GIS平台的支援下,為使用者提供相應服務的一種增值業務。
基於位置的服務可以被應用與不同的領域,例如:健康、工作、個人生活等。此服務可以用來辨認一個人或物的位置,例如發現最近的提款機或朋友同事的目前的位置,也能透過客戶目前所在的位置提供直接的手機廣告,並包括過人話的天氣訊息提供,甚至提供在地化的遊戲。

資料來源:wikipedia

2010年12月18日 星期六

將要執行很久的程式碼,放在 Service 中執行

Run your time-consuming jobs in Service
在你研讀 Android 文件時,應該會注意到,千萬不能將要執行很久的程式碼放在 Main ( or UI) Thread 中執行,要不然一定會發生 ANR (Android is Not Responding) 錯誤。要避免 ANR 錯誤,對於所有可能會執行超過 5 秒的工作,例如網路或資料庫的存取、音樂的播放等等,你都要自行建立一個新的 Thread 物件,並將該費時的工作放在 Thread.run() 中執行。
其實,光將費時的程式碼放在 Thread.run() 中執行,還是不夠的,他只解決一半的問題。如果你不知道我為什麼會這麼說的,請先讀一下 記得要在程式中,處理鍵盤開啟或關閉的動作 這篇。
讀完後,你知道原因了嗎?當使用者打開或關閉鍵盤時,使用者看到的只是螢幕畫面的旋轉。但是,從程式面來看,Activity 已經經歷了一次生死輪迴。Activity 先被殺掉,再重新建立一個新的。那就代表,這個新建立的 Activity,再也不能和原先還正在執行工作的 Thread ,相互溝通,傳遞資料。
用個例子來說明,你可能會更清楚些。例如,你的程式正在執行一個下載檔案的工作。為了讓使用者清楚,目前的下載的進度。你應該會用個 Progress Dialog,顯示目前已經下載的百分比。如果就在此時,使用者打開鍵盤,你猜會發生什麼事?我想你程式上,原來那個顯示下載進度的 Progress Dialog,應該會不見了。負責下載的 Thread,可能還正在執行中(如果你沒在 onStop() 或 onDestroy() 中,停止 Thread 的執行)。可是,從你的 UI 的呈現,使用者會認為剛剛的那個下載的工作,已經被停下來。
要怎麼解決這個問題?我會建議你,不僅要將費時的工作放在 Thread.run() 中執行,還要將這個 Thread 放在 Service 中執行。因 為,當螢幕要旋轉時,系統只會殺掉 Activity Stack 中,最上面的那一個 Activity,並不會對扮演執行 background task 的 Service 採取任何的動作。Service 和 Thread 還是可以安穩地繼續執行下載的工作,而被系統重起的 Activity,也可以透過 Service 提供的 remote interface,與 Service 進行 IPC (interprocess communication),以獲取目前下載的進度與執行情形。
想更進一步瞭解實作的你,我建議你先研讀 Android 中 Music 這個程式的原始碼。另外想要知道如何與 Service 進行 IPC 的你,請閱讀 Android 文件中的 <sdk_path>/docs/reference/aidl.html 文件,我覺得這篇寫得還算清楚詳細,值得一讀。

資料來源:
http://ysl-paradise.blogspot.com/2009/01/service.html

AsyncTask 不是萬能的

Don't only use AsyncTask for your download task
要進入 Android 應用開發之門,真的一點都不難,不就是 Java 嘛。這是我最常聽到,剛跨過入門檻開發者告訴我的一句話。
這句話其實是沒有錯的,Android 應用開發的入門檻,比起其他平台,已經低了許多。要跨過這門檻,的確是輕鬆容易許多。不過,如果要從入門到進階,邁向下一個關卡,你第一個要了解的是 Activity 的生命週期Process 的生命週期,而且是要『透 . 徹 . 了 . 解』。這一步很重要,卻被許多開發者輕忽了。
尤其這 Activity 的生命週期,實際上的行為會比你從文件上看到的說明還來的複雜。也因此,我發現有不少,即使已有多個 Android 應用開發經驗的開發者,在開發 Android 應用時,還是栽了不少跟斗。這第一個問題,就出在這些開發者,還是沒達到我說的『透徹了解』境界。因此,今年的進階應用開發課程,特地將這一部分加入進來,希望對想踏入進階之門的開發者,有所助益。
好了,如果你已經有一段 Android 應用的開發經驗,那應該知道在 Main-thread(UI-thread) 中,你不能執行一件需時 5 秒以上的工作,例如網路或資料庫的存取、音樂的播放等等。要不然你的應用就會產生 ANR 錯誤。要解決這個 ANR 錯誤,唯一的方法就是自行建立一個新的 Thread 物件,並將該費時的工作放在 Thread.run() 中執行。關於如何解決 ANR 的細節,我建議你先讀 Painless threading,這是一篇值得一讀的好文章。
在這篇文章中,介紹了從 Android 1.5 才加入的 AsyncTask。AsyncTask 很好用,同時我也建議你研究他的 原始碼。AsyncTask 就是太好用了,有些開發者就認為,單用 AsyncTask 就能解決他的問題。其實,我早在請將要執行很久的程式碼,放在 Service 中執行這 篇中,就已提過。單將費時的程式碼放在 Thread.run() 中執行,還是不夠的,你只解決一半的問題。不過,多數開發者不是便宜行事,就是不相信我說的。這些便宜行事的開發者,就是在賭這系統強制殺掉你應用的機率 有多少;而那些不相信我說的,就是因為他沒有透徹了解 Activity/Process 的生命週期。
完整解決費時工作的方法,不僅要將費時的工作放在 Thread.run() 中執行,還要將這個 Thread 放在 Service 中執行。
你要知道 Android 的四大元件,Activity, BroadcastReceiver, Servcice and ContentProvider,除了 ContentProvide 外,全都是在 main-thread 中執行。而這些元件中,就只有 Service 的生命週期是最持續(長久)的。Activity 只要執行到 onPause(),BroadcastReceiver 只要離開 onReceiver(),系統隨時會殺掉這些元件,而且機率還很高。Service 當然也是會被系統砍掉,只不過它的優先順序,排在較低等級。自然被系統砍掉的機率就低很多。你還可以更進一步利用 Serivce.startForeground() 降低你被系統殺掉的優先順序。關於這部分,你要熟讀 Process 的生命週期What is a Service?
其實為了減輕開發者的負擔, Android 1.5 已經加了 IntentService 這個新類別。如果你要寫個用到網路的應用,用這個 IntentService 才是你的完美解決方案。
要使用這個 IntentService 其實很簡單,你只要繼承這個 IntentService 並將該項費時的工作,移到 onHandleIntent() 中即可。onHandleIntent() 是被 non-UI thread 所喚起的。因此在這裏面你可以放心地去執行你的下載工作。
下次,我們來研究一下這個 IntentService 的原始碼,看他是如何做的。
參考資料:

深入研究 IntentService 原始碼

IntentService Source Code Internals
小心,AsyncTask 不是萬能的 中,我提到 IntentService 是你在實現下載功能時,最好用的武器之一。
這次,我們來深入研究 IntentService 的原始碼,看他是如何用短短幾行代碼,來解決這個問題。
  1. public abstract class IntentService extends Service {  
  2.   ...  
  3. }  
從 class 的宣告中,你可以知道 IntentService 就是一般的 Service。
接著他定義一個 ServiceHandler。一般我們在創建 Handler 時,都是用預設的建構式。預設的 Handler 建構式,是依附在當前的 thread 身上。所以一般開發者都以為 Handler 一定是依附在 main-thread 上,這是不對的觀念。
  1. private final class ServiceHandler extends Handler {  
  2.   public ServiceHandler(Looper looper) {  
  3.     super(looper);  
  4.   }  
  5.   ...  
  6. }  
這個 ServiceHandler,是依附在一個 non-UI thread 的 Looper 上。Looper 就是訊息迴圈 (message loop),這是 Android UI 在處理各式訊息時,最重要的元件之一。而 ServiceHandler 建構式中所傳入的 Looper 物件,是在 onCreate() 中所建立的。
  1. @Override  
  2. public void onCreate() {  
  3.   super.onCreate();  
  4.   HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");  
  5.   thread.start();  
  6.   
  7.   mServiceLooper = thread.getLooper();  
  8.   mServiceHandler = new ServiceHandler(mServiceLooper);  
  9. }  
看到了吧,mServiceLooper 就是 thread 物件中的 Looper 物件。這個 thread 物件就是一個 HandlerThread,他是一個 non-UI thread。
所以現在這個 Service,一個帶有 message loop(mServiceLooper) 的 non-UI thread (mServiceHandler) 正一直在背後執行著,監看是否有任何傳入的 message。
接著,每當你呼叫 startService(new Intent(this, MyIntentService.class)) 啟動 IntentService 時,其實他只是很簡單地送出一個 message 到 mServiceHandler 上。
  1. @Override  
  2. public void onStart(Intent intent, int startId) {  
  3.   Message msg = mServiceHandler.obtainMessage();  
  4.   msg.arg1 = startId;  
  5.   msg.obj = intent;  
  6.   mServiceHandler.sendMessage(msg);  
  7. }  
我們知道這個 message 最終會由 Handler.handleMessage() 來處理。由於整個 mServiceHandler 是在 non-UI thread 中執行。當他在 handleMessage() 中呼叫你的 onHandleIntent() 時,你的 onHandleIntent() 自然也是在 non-UI thread 中執行。
  1. private final class ServiceHandler extends Handler {  
  2.   ...  
  3.   @Override  
  4.   public void handleMessage(Message msg) {  
  5.     onHandleIntent((Intent)msg.obj);  
  6.     stopSelf(msg.arg1);  
  7.   }  
  8. }  
onHandleIntent() 執行結束後,接著呼叫 stopSelf(int)。由於 IntentService 是設計用來可以在一個 Service 中服務多項工作,因此在這裡,不可以呼叫 stopSelf()。
如果你呼叫 startService() 多次,每一次的呼叫都會被轉成一個 message,並放在 mServiceLooper 的 message queue 中,等待被服務。一個 message 所對應的工作被完成後,才會繼續服務下一個工作。所以,這些等待被服務的工作,並不是一起並行 (Concurrent) 的,而是循序執行。
當所有的工作都處裡完時,也是 Service 該結束的時候。
  1. @Override  
  2. public void onDestroy() {  
  3.   mServiceLooper.quit();  
  4. }  
在 onDestroy() 中,唯一要做的事,就是要將 mServiceLooper 停下來。
從研究這個 IntentService 的原始碼,我們可以學到如何運用簡單的 pattern (Service + Handler + HandlerThread),幫我們更簡易與有系統地,完成我們所想要做的事。
最後我們先前說過,在 IntentServcie 中等待被服務的工作,並不會被一起並行,而是循序執行。如果你今天想要這些等待被服務的工作,能夠一起被並行,在研讀完這個 IntentService 的原始碼後,你自己知不知道如何寫個可支援並行工作的 IntentService?
提示:可以用 Service + AsyncTask 的組合。

資料來源:
http://ysl-paradise.blogspot.com/2010/10/intentservice.html

簡單建立網頁 WebView

如果要繼續體驗 View 的樂趣,那麼「WebView」這個 View 無疑是最佳人選。android.webkit.WebView 是使用「WebKit」技術的 View,主要的用途是「顯示網頁」。使用 WebView,我們可以在 Android 應用程式裡顯示自已的 HTML 文件,或是線上的網頁。
接下來請依照以下步驟,建立我們的第二個 Android 應用程式「Hello Web」。
建立新專案: HelloWeb 建立一個新的 Android 專案,如圖1。
hello_web_1.png
圖1: 建立 Hello Web 專案 並且撰寫 HelloWeb.java 程式如下:
package com.moko.web;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;

import com.moko.web.R;

public class HelloWeb extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final String mimetype = "text/html";
        final String encoding = "utf-8";
        
        WebView wv;
        
        wv = (WebView) findViewById(R.id.wv);
        wv.loadData("<img src=\"http://www.google.com.tw/intl/en_com/images/logo_plain.png\" />", mimetype, encoding);
    }
}
上述程式碼採用 XML layout 方式來安排 UI,因此接下來的工作就是編輯 XML layout 檔案。
規劃 UI: main.xml 編輯 main.xml 來規劃「Hello Web」的 UI 如下:
<?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="wrap_content"
    >
<WebView 
 android:layout_width="fill_parent" 
 android:layout_height="wrap_content" 
 android:id="@+id/wv" 
 />
</LinearLayout>
在這裡我們定義了「WebView」標籤,並且指定「WebView」的 ID 為「wv」。透過指定「ID」屬性給 View 的方式,便能「讓 Android 應用程式在執行時期(run-time)找到指定的 View 物件」。
使用 View 的 ID 屬性: findViewByID 怎麼在執行時期,找到「XML layout」安排好的 View 呢?看到 HelloWeb.java 的程式片斷如下:
final String mimetype = "text/html";
        final String encoding = "utf-8";
        
        WebView wv;
        
        wv = (WebView) findViewById(R.id.wv);
        wv.loadData("<img src=\"http://www.google.com.tw/intl/en_com/images/logo_plain.png\" />", mimetype, encoding);
呼叫 findViewByID() 方法,即可在執行時期「動態取得 View 物件」。當我們在 main.xml 裡加入 WebView 標籤,「存檔」後,R.java 資源索引檔也會跟著更新,我們可透過「R.id.wv」來索引到 WebView 物件。以下是 R.java 的內容:
package com.moko.web;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class id {
        public static final int wv=0x7f050000;
    }
    public static final class layout {
        public static final int main=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040001;
        public static final int hello=0x7f040000;
    }
}
取得 WebView 物件後,呼叫 WebView 的 loadData() 方法,將 HTML 內容載入到 WebView 物件裡,並顯示在 Activity 上。loadData() 的參數如下:
  • 第一個參數:HTML 內容
  • 第二個參數:MimeType 類型,指定為 text/html,即 HTML 類型文件
  • 第三個參數:文字編碼方法,指定為 utf-8(Unicode)
Hello Web 範例程式所載入的 HTML 文件是一個 <IMG> 標籤,因此我們在視窗上所看到的內容就是一張圖檔,如下圖(使用真正的 Google Phone 做測試)。
hello_web_2.png
圖: Hello Web 執行結果。
原文地址:http://www.jollen.org/blog/2009/01/jollen-android-programming-6.html

Android 開發中使用 SQLite 數據庫

SQLite 介紹
SQLite 一個非常流行的嵌入式數據庫,它支援 SQL 語言,並且只利用很少的內存就有很好的性能。此外它還是開源的,任何人都可以使用它。許多開源項目((Mozilla, PHP, Python)都使用了 SQLite.
SQLite 由以下幾個組件組成︰SQL 編譯器、內核、後端以及附件。SQLite 透過利用虛擬機和虛擬數據庫引擎(VDBE),使調試、修改和擴展 SQLite 的內核變得更加方便。

 SQLite 內部結構
图 1. SQLite 内部结构

SQLite 基本上符合 SQL-92 標準,和其他的主要 SQL 數據庫沒什麼區別。它的優點就是高效,Android 營運時環境包含了完整的 SQLite。
SQLite 和其他數據庫最大的不同就是對數據類型的支援,創建一個表時,可以在 CREATE TABLE 語句中指定某列的數據類型,但是你可以把任何數據類型放入任何列中。當某個值插入數據庫時,SQLite 將檢查它的類型。如果該類型與關聯的列不匹配,則 SQLite 會嘗試將該值轉換成該列的類型。如果不能轉換,則該值將作為其本身具有的類型存儲。比如可以把一個字元串(String)放入 INTEGER 列。SQLite 稱這為“弱類型”(manifest typing.)。
此外,SQLite 不支援一些標準的 SQL 功能,特別是外鍵約束(FOREIGN KEY constrains),巢狀 transcaction 和 RIGHT OUTER JOIN 和 FULL OUTER JOIN, 還有一些 ALTER TABLE 功能。
除了上述功能外,SQLite 是一個完整的 SQL 系統,擁有完整的正反器,交易等等。

Android 集成了 SQLite 數據庫
Android 在營運時(run-time)集成了 SQLite,所以每個 Android 應用程式都可以使用 SQLite 數據庫。對於熟悉 SQL 的開發人員來時,在 Android 開發中使用 SQLite 相當簡單。但是,由於 JDBC 會消耗太多的系統資源,所以 JDBC 對於手機這種內存受限設備來說並不合適。因此,Android 提供了一些新的 API 來使用 SQLite 數據庫,Android 開發中,程式員需要學使用這些 API。
數據庫存儲在 data/< 項目檔案夾 >/databases/ 下。

Android 開發中使用 SQLite 數據庫
Activites 可以透過 Content Provider 或者 Service 訪問一個數據庫。下面會詳細講解如果創建數據庫,添加數據和查詢數據庫。
創建數據庫
Android 不自動提供數據庫。在 Android 應用程式中使用 SQLite,必須自己創建數據庫,然後創建表、索引,填充數據。Android 提供了 SQLiteOpenHelper 幫助你創建一個數據庫,你只要繼承 SQLiteOpenHelper 類,就可以輕鬆的創建數據庫。SQLiteOpenHelper 類根據開發應用程式的需要,封裝了創建和更新數據庫使用的邏輯。SQLiteOpenHelper 的子類,至少需要實現三個方法︰
  • 構造函數,調用父類 SQLiteOpenHelper 的構造函數。這個方法需要四個參數︰上下文環境(例如,一個 Activity),數據庫名字,一個可選的游標工廠(通常是 Null),一個代表你正在使用的數據庫模型版本的整數。
  • onCreate()方法,它需要一個 SQLiteDatabase 對象作為參數,根據需要對這個對象填充表和初始化數據。
  • onUpgrage() 方法,它需要三個參數,一個 SQLiteDatabase 對象,一個舊的版本號和一個新的版本號,這樣你就可以清楚如何把一個數據庫從舊的模型轉變到新的模型。
下面示例代碼展示了如何繼承 SQLiteOpenHelper 創建數據庫︰
public class DatabaseHelper extends SQLiteOpenHelper {     
  DatabaseHelper(Context context, String name, CursorFactory cursorFactory, int version) 
  {     
    super(context, name, cursorFactory, version);     
     }     
     
     @Override    
     public void onCreate(SQLiteDatabase db) {     
         // TODO 創建數據庫後,對數據庫的操作     
     }     
     
     @Override    
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {     
         // TODO 更改數據庫版本的操作     
     }     
     
 @Override    
 public void onOpen(SQLiteDatabase db) {     
         super.onOpen(db);       
         // TODO 每次成功打開數據庫後首先被執行     
     }     
 }     

接下來討論具體如何創建表、插入數據、刪除表等等。調用 getReadableDatabase() 或 getWriteableDatabase() 方法,你可以得到 SQLiteDatabase 實例,具體調用那個方法,取決於你是否需要改變數據庫的內容︰
db=(new DatabaseHelper(getContext())).getWritableDatabase(); 
 return (db == null) ? false : true; 

上面這段代碼會返回一個 SQLiteDatabase 類的實例,使用這個對象,你就可以查詢或者修改數據庫。
當你完成了對數據庫的操作(例如你的 Activity 已經關閉),需要調用 SQLiteDatabase 的 Close() 方法來釋放掉數據庫連接。
創建表和索引
為了創建表和索引,需要調用 SQLiteDatabase 的 execSQL() 方法來執行 DDL 語句。如果沒有異常,這個方法沒有返回值。
例如,你可以執行如下代碼︰
db.execSQL("CREATE TABLE mytable (_id INTEGER PRIMARY KEY   
        AUTOINCREMENT, title TEXT, value REAL);"); 

這條語句會創建一個名為 mytable 的表,表有一個列名為 _id,並且是主鍵,這列的值是會自動增長的整數(例如,當你插入一行時,SQLite 會給這列自動賦值),另外還有兩列︰title( 字符 ) 和 value( 浮點數 )。 SQLite 會自動為主鍵列創建索引。
通常情況下,第一次創建數據庫時創建了表和索引。如果你不需要改變表的 schema,不需要刪除表和索引 . 刪除表和索引,需要使用 execSQL() 方法調用 DROP INDEX 和 DROP TABLE 語句。
給表添加數據
上面的代碼,已經創建了數據庫和表,現下需要給表添加數據。有兩種方法可以給表添加數據。
像上面創建表一樣,你可以使用 execSQL() 方法執行 INSERT, UPDATE, DELETE 等語句來更新表的數據。execSQL() 方法適用於所有不返回結果的 SQL 語句。例如︰
db.execSQL("INSERT INTO widgets (name, inventory)"+ 
"VALUES ('Sprocket', 5)"); 

另一種方法是使用 SQLiteDatabase 對象的 insert(), update(), delete() 方法。這些方法把 SQL 語句的一部分作為參數。示例如下︰
ContentValues cv=new ContentValues(); 
 cv.put(Constants.TITLE, "example title"); 
 cv.put(Constants.VALUE, SensorManager.GRAVITY_DEATH_STAR_I); 
 db.insert("mytable", getNullColumnHack(), cv); 

update()方法有四個參數,分別是表名,表示列名和值的 ContentValues 對象,可選的 WHERE 條件和可選的填充 WHERE 語句的字元串,這些字元串會替換 WHERE 條件中的“?”標記。update() 根據條件,更新指定列的值,所以用 execSQL() 方法可以達到同樣的目的。
WHERE 條件和其參數和用過的其他 SQL APIs 類似。例如︰
String[] parms=new String[] {"this is a string"}; 
 db.update("widgets", replacements, "name=?", parms); 

delete() 方法的使用和 update() 類似,使用表名,可選的 WHERE 條件和相應的填充 WHERE 條件的字元串。
查詢數據庫
類似 INSERT, UPDATE, DELETE,有兩種方法使用 SELECT 從 SQLite 數據庫檢索數據。
1 .使用 rawQuery() 直接調用 SELECT 語句;
使用 query() 方法構建一個查詢。
  • Raw Queries 正如 API 名字,rawQuery() 是最簡單的解決方法。透過這個方法你就可以調用 SQL SELECT 語句。例如︰
    Cursor c=db.rawQuery( 
         "SELECT name FROM sqlite_master WHERE type='table' AND name='mytable'", null); 

    在上面例子中,我們查詢 SQLite 系統表(sqlite_master)檢查 table 表是否存在。返回值是一個 cursor 對象,這個對象的方法可以迭代查詢結果。
    如果查詢是動態的,使用這個方法就會非常複雜。例如,當你需要查詢的列在程式編譯的時候不能確定,這時候使用 query() 方法會方便很多。
  • Regular Queries query() 方法用 SELECT 語句段構建查詢。SELECT 語句內容作為 query() 方法的參數,比如︰要查詢的表名,要獲取的字段名,WHERE 條件,包含可選的位置參數,去替代 WHERE 條件中位置參數的值,GROUP BY 條件,HAVING 條件。
    除了表名,其他參數可以是 null。所以,以前的代碼段可以可寫成︰
    String[] columns={"ID", "inventory"}; 
     String[] parms={"snicklefritz"}; 
     Cursor result=db.query("widgets", columns, "name=?",parms, null, null, null); 
使用游標
不管你如何執行查詢,都會返回一個 Cursor,這是 Android 的 SQLite 數據庫游標,使用游標,你可以︰
透過使用 getCount() 方法得到結果集中有多少記錄;
透過 moveToFirst(), moveToNext(), 和 isAfterLast() 方法遍歷所有記錄;
透過 getColumnNames() 得到字段名;
透過 getColumnIndex() 轉換成字段號;
透過 getString(),getInt() 等方法得到給定字段當前記錄的值;
透過 requery() 方法重新執行查詢得到游標;
透過 close() 方法釋放游標資源;
例如,下面代碼遍歷 mytable 表
Cursor result=db.rawQuery("SELECT ID, name, inventory FROM mytable"); 
    result.moveToFirst(); 
    while (!result.isAfterLast()) { 
        int id=result.getInt(0); 
        String name=result.getString(1); 
        int inventory=result.getInt(2); 
        // do something useful with these 
        result.moveToNext(); 
      } 
      result.close(); 


在 Android 中使用 SQLite 數據庫管理工具
在其他數據庫上作開發,一般都使用工具來檢查和處理數據庫的內容,而不是僅僅使用數據庫的 API。使用 Android 類比器,有兩種可供選擇的方法來管理數據庫。
首先,類比器綁定了 sqlite3 控制台程式,可以使用 adb shell 命令來調用他。只要你進入了類比器的 shell,在數據庫的路徑執行 sqlite3 命令就可以了。數據庫檔案一般存放在︰
/data/data/your.app.package/databases/your-db-name
如果你喜歡使用更友好的工具,你可以把數據庫拷貝到你的開發機上,使用 SQLite-aware 客戶端來操作它。這樣的話,你在一個數據庫的拷貝上操作,如果你想要你的修改能反映到設備上,你需要把數據庫備份回去。
把數據庫從設備上考出來,你可以使用 adb pull 命令(或者在 IDE 上做相應操作)。存儲一個修改過的數據庫到設備上,使用 adb push 命令。
一個最方便的 SQLite 客戶端是 FireFox SQLite Manager 擴展,它可以跨所有平台使用。




如果你想要開發 Android 應用程式,一定需要在 Android 上存儲數據,使用 SQLite 數據庫是一種非常好的選擇。本文介紹了如何在 Android 應用程式中使用 SQLite 數據庫 ,主要介紹了在 Android 應用程式中使用 SQLite 創建數據庫和表、添加數據、更新和檢索數據,還介紹了比較常用的 SQLite 管理工具,透過閱讀本文,你可以在 Android 中輕鬆操作 SQLite 數據庫。

參考資料

自己動手做一個跑馬燈

In the newest release, Android 1.1 R1, android.widget.TextView begins to support marquee feature.
It provides a value, marquee, for android:ellipsize, and a new attribute, android:marqueeRepeatLimit.

However you could not make the TextView scrolling if you only set these 2 attributes.
According to my testing, at least the following 3 attributes shall be set:
android:ellipsize="marquee"
android:focusable="true"
android:singleLine="true"
And the TextView shall be focused while running.

Moreover, I found 2 limitations in current marquee implementation:
  1. Never scroll if the TextView does not get the focuse;
  2. The scrolling is odd because the round of scrolling begins from the left side while end at the right side. It looks like the animation is reset.
So far, I think my implementation of ScrollTextView is better for the marquee feature. :)

data source:http://bear-polka.blogspot.com/2009/03/marquee-feature-of-textview-in-android.html

幫圖片加上影像特效 - 負片與灰階效果

Create simple effects for your images (III) - Negative & Grayscale Effects
前兩次提過,簡單地利用 Matrix ,就可以輕易做出 陰影特效鏡像特效
這次,我將向大家分享,如何利用另一種 Matrix - ColorMatrix,來做出負片與灰階效果。
最上面那張圖的上半部,是原來的影像,一個美麗的地球。下半部的左邊,就是這地球影像的負片效果。右邊,則是灰階效果的呈現。
負片效果
負片效果的演算法,很簡單。就像底下這樣。
  1. R' = 255 - R;  
  2. G' = 255 - G;  
  3. B' = 255 - B;  
算法很簡單,可是在 Android 中要如何做到呢?難不成要用 Bitmap.getPixel()Bitmap.setPixel(),逐一針對每個像素來改他的顏色值?!
當然不是!這樣的做法,太沒效率,速度一定很慢。在 Android 中,針對這樣的需求,你可以透過 ColorMatrixColorFilter 來完成。在建構一個 ColorMatrixColorFilter 時,你得傳給他一個 ColorMatrix。因此,重點就在這個 ColorMatrix。
ColorMatrix 文件一開頭的註釋,就已經將他的功能講得很清楚。
  1. //5x4 matrix for transforming the color+alpha components of a Bitmap.  
  2. //The matrix is stored in a single array, and its treated as follows:  
  3. //[ a, b, c, d, e,  
  4. //  f, g, h, i, j,  
  5. //  k, l, m, n, o,  
  6. //  p, q, r, s, t ]  
  7. //  
  8. //When applied to a color [r, g, b, a], the resulting color is computed as  
  9. //(after clamping)  
  10. // R' = a*R + b*G + c*B + d*A + e;  
  11. // G' = f*R + g*G + h*B + i*A + j;  
  12. // B' = k*R + l*G + m*B + n*A + o;  
  13. // A' = p*R + q*G + r*B + s*A + t;  
讓我們看一下,這負片效果的原始碼,你會更加清楚。
  1. Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);    
  2. ColorMatrix cm = new ColorMatrix(new float[] {  
  3.   -1f,  0f,  0f, 0f, 255f,  
  4.    0f, -1f,  0f, 0f, 255f,  
  5.    0f,  0f, -1f, 0f, 255f,  
  6.    0f,  0f,  0f, 1f,   0f});  
  7. paint.setColorFilter(new ColorMatrixColorFilter(cm));  
  8. canvas.drawBitmap(m_bmp, 00, paint);  
將一個地球的圖片設給 m_bmp,透過上面這個程式範例,畫出來的樣子,就是一張具有負片效果的圖。
灰階效果
負片會了,灰階應該也很簡單了吧!我告訴你,要完成灰階效果,更簡單。ColorMatrix 都幫你將算法寫好了。你只要呼叫 ColorMatrix.setSaturation(0) 即可。
等等,學東西,可不能這樣就含混帶過。別忘了,Android 的原始碼都給你了,我們得利用他追根究柢,一探究竟才行。
打開 ColorMatrix.java 的原始碼,翻到 setSaturation()。這個函式的實現如下:
  1. public void setSaturation(float sat) {  
  2.   reset();  
  3.   float[] m = mArray;  
  4.   
  5.   final float invSat = 1 - sat;  
  6.   final float R = 0.213f * invSat;  
  7.   final float G = 0.715f * invSat;  
  8.   final float B = 0.072f * invSat;  
  9.   
  10.   m[0] = R + sat; m[1] = G;       m[2] = B;  
  11.   m[5] = R;       m[6] = G + sat; m[7] = B;  
  12.   m[10] = R;      m[11] = G;      m[12] = B + sat;  
  13. }  
透過 android.graphics.ColorFilter 與 android.graphics.ColorMatrix,你還可以玩出更多的效果,別忘了 ApiDemos 的範例程式中,也有用到。
很多好東西,就在你身旁,千萬別捨近求遠了。

資料來源:
http://ysl-paradise.blogspot.com/2010/01/iii.html

幫圖片加上影像特效 - 3D 投射效果

Create simple effects for your images (IV) - 3D Projection
在 Android SDK 中有兩種 Camera classes。一個是 android.hardware.Camera,這是用來操控相機功能的類別。另一個是 android.graphics.Camera,這是什麼東西?今天我們就要來研究一下,這個類別能幫我們做些什麼事。
android.graphics.Camera 是一個可以讓你將 2D 物件在 3D 空間中移動,並將在其移動後的結果,畫在螢幕上的類別。
聽起來很有趣,是嗎?例如,最上面那張美女圖,你可以看出來,他是將原本的圖,以 Y 軸為軸心,旋轉約 30 度後,所畫出來的結果。
打開 android.graphics.Camera 的說明文件,你會發現,這個文件中只有下面這些 APIs 列表,什麼說明都沒有。
  1. //Public Constructors   
  2.   Camera()  
  3. //Public Methods  
  4.   void  applyToCanvas(Canvas canvas)  
  5.   float  dotWithNormal(float dx, float dy, float dz)  
  6.   void  getMatrix(Matrix matrix)  
  7.   void  restore()  
  8.   void  rotateX(float deg)  
  9.   void  rotateY(float deg)  
  10.   void  rotateZ(float deg)  
  11.   void  save()  
  12.   void  translate(float x, float y, float z)  
不過,從這些 APIs 的名稱與參數,你應該可以猜出一些端倪。沒錯,這個 Camera class,其實是個 helper class。他提供一些 APIs ,讓你控制要如何在 3D 空間中移動,最後再產生出合適的 Matrix ,讓你套用到 Canvas 的座標體系上。關於 Matrix,我在 幫圖片加上影像特效 (I) - 陰影特效 已經介紹過他的強大,還不知道的,先讀一下這篇。
用個例子來解釋這個 Camera 類別的用法,是最適當的。底下這個簡單的程式片段的結果,就是最上面那張圖。其中的 m_bmp 就是原本的美女圖。
  1. camera = new Camera();  
  2. camera.translate(0050);  
  3. camera.rotateY(30);  
  4. Matrix matrix3D = new Matrix();  
  5. camera.getMatrix(matrix3D);  
  6.   
  7. canvas.save();  
  8. canvas.translate(100100);  
  9. canvas.concat(matrix3D);  
  10.   
  11. canvas.drawBitmap(m_bmp, 00null);  
  12. canvas.restore();  
從這個範例程式中,我們知道,你可以利用 Camera.translate() 及 Camera.rotateXXX() 等 APIs 在 3D 空間中移動。其實,這些 APIs 就是在操作 Matrix 的內容。最後,你只要呼叫 Camera.getMatrix() 即可將這 Matrix 的內容抓出,並套到 Canvas 上。
在 Android ,要做出 3D 的效果,你當然可以用 OpenGL 的函式。不過,如果你需要更快的顯示速度,或者只是要做個簡單的 3D 特效,那千萬不要忘了這個 android.graphics.Camera。

資料來源:
http://ysl-paradise.blogspot.com/2010/03/iv-3d.html

Android 原始碼徹底研究

Android Source Code Internals
閱讀及追蹤 Android 的原始碼,是提昇你 Android 程式設計功力最好的方式之一。
可惜的是,在現今講求速食主義的年代,凡事都講求效率,大家都想直接找到解決問題的答案,而不想從打好基礎開始。別說是閱讀 Android 原始碼,連 Java 都還不會,就想一步登天的,也不在少數。以致於我看過不少,捨本逐末,事倍功半的例子。雖然你能找到解決的方法,可是在未能融會貫通的情況下,總是知其 然,而不知其所以然。下次遇到類似的問題,當然還是無法自己獨立解決。
要跨入 Android 的設計殿堂,第一步是容易的。你只要會用 Eclipse 及 ADT 提供的 wizard,再加上一本範例大全在手,要寫個簡單的程式應該都不是問題。可是以這樣的經驗與訓練,要真正想要寫一個 "好" 程式,還是有很大的距離。如果你真的想要學寫一個好程式,一窺 這殿堂的奧妙之美,以我的經驗來說,還是得按部就班來,先具備一定的基礎,仍然是先決的要素。
我會建議你至少要具備以下的基礎:
  • Java core language 和 Object-Orient programming
  • 熟讀 SDK 中的 The Developer's Guide 和練習 tutorial
  • 至少要瀏覽 Android framework APIs Reference 一遍
  • 自己出些題目,練習 Android 程式
  • 在寫程式的過程中,有問題時,不要急著問,先要求自己從 The Developer's Guide 和 API Reference 中找答案
如果你已經有 OOP (C++, C# 等) 的程式設計經驗,要具備我上頭講的這些基礎,要花多久的時間?
我想為自己留三個月時間,應該就綽綽有餘。
在這些基礎都具備的情況下,想要再進一步提升你的程式設計功力,除了要想辦法讓自己多增加些程式設計經驗外,閱讀及追蹤別人寫的程式碼,更是最好的方法。不過網路上,每個人的程式功力高低不同,寫出來的程式也有好有差。讀到不良的程式,看不懂還不打緊,如果誤導你的觀念,那可就得不償失。因此,當你底子還不足以判斷好壞程式時,我會建議你直接閱讀 Android 的原始碼。根本不用捨近求遠,Android 原始碼,就是你學習 Android 程式設計時,最好的導師。
要在 Linux 中下載 Android 原始碼,應該不是問題。習慣在 Windows 下生活的你,我在 如何在 Windows 平台上下載 Android 的原始碼 中,有介紹如何在 Windows 中下載 Android 原始碼。
有了原始碼,應該從何看起,還是個問題。如果你一頭就栽進,Android framework 中的原始碼,在沒有目標與頭緒的情況下,很容易就迷失在眾多 framework 原始碼中。根據我自身的經驗,我會建議你先從 platform\packages\apps 開始,那裡面有 Android 一些內建的應用程式原始碼。像是網頁瀏覽器,計算機,鬧鐘,聯絡人,行事曆,媒體播放器,甚至於桌面程式都有。因為這些應用程式,你看得見,也摸得著,要 追蹤這些的原始碼,會比較有感覺些。甚至於你還可以當場做實驗,改程式,看結果。
為了幫想要更進一步提升 Android 程式設計功力的你,在接下來的日子,我會帶領大家導讀 Android 的原始碼。想要跟隨者,先照我講的,打好你應該具備的基礎;並下載一份 Android 原始碼,到你的電腦上。
下次,我們先從 Android 的鬧鐘 (Alarm Clock) 原始碼開始看起。

資料來源:
http://ysl-paradise.blogspot.com/2009/06/android.html

鬧鐘程式, App Widgets

Android Source Code Internals - Alarm Clock, App Widgets (2)
從 Android v1.5 開始,終於開放 APIs,讓第三方開發者可以寫些 widgets,讓使用者可以放在手機的桌面上。一般手機就有內建幾個小 widgets,其中的 "Analog Clock" widget,就是由 Alarm Clock 這隻鬧鐘程式所提供的。
這次我們就來研究一下,這個 Analog Clock widget 是如何寫出來的。
不過,如果你對如何寫 App Widgets 一點概念都沒有的話,底下有幾個很好的參考資料,一定要先讀過:
要寫個 App Widget,首先你要先用 XML 寫個 AppWidgetProviderInfo,而且這個檔案建議放在 res/xml 中。在鬧鐘程式中,你可以找到 res/xml/analog_appwidget.xml 這個檔案。它的內容如下:
  1. //res/xml/analog_appwidget.xml  
  2. <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"  
  3.   android:minWidth="146dip"  
  4.   android:minHeight="146dip"  
  5.   android:updatePeriodMillis="0"  
  6.   android:initialLayout="@layout/analog_appwidget" >  
  7. </appwidget-provider>  
由 minWidth 及 minHeight 這兩個屬性來看,就可以了解,這個 App Widget 要佔桌面 2x2 cells 大小的空間。
updatePeriodMillis 的值為 0?那這個時鐘是如何自己動的呢?這我在 Android 原始碼徹底研究系列 - 鬧鐘程式, AnalogClock (1) 一文中,就已經解釋過 Analog Clock 是如何自我更新的。還不清楚的,先讀一下這篇。
initialLayout 這個屬性,從名字看起來,就知道它是用來描述這個 App Widget 的畫面設計。由其值我們可以知道,這個 App Widget 的畫面設計,是在 res/layout/analog_appwidget.xml 中。它的內容如下:
  1. //res/layout/analog_appwidget.xml  
  2. <AnalogClock xmlns:android="http://schemas.android.com/apk/res/android"  
  3.   android:dial="@drawable/appwidget_clock_dial"  
  4.   android:hand_hour="@drawable/appwidget_clock_hour"  
  5.   android:hand_minute="@drawable/appwidget_clock_minute"  
  6.   android:layout_width="fill_parent"  
  7.   android:layout_height="fill_parent" />  
果然不出我們所料,這個 App Widget 的畫面設計,就只是一個簡單的 AnalogClock 。時鐘與時,分針圖檔就是分別透過 dial, hand_hour, hand_minute 這三個屬性來指定。
要注意的是,目前在 App Widget 的畫面設計檔中,你只能使用 FrameLayout, LinearLayout, RelativeLayout, AnalogClock, Button, Chronometer, ImageButton, ImageView, ProgressBar, TextView 等元件,其他元件都不能用。
寫好 AppWidgetProviderInfo XML 檔與 App Widget 畫面設計檔,別忘了,還要在 AndroidManifest.xml 中,宣告你的 App Widget Provider。
  1. //AndroidManifest.xml, #55~#60  
  2. <receiver android:name="AnalogAppWidgetProvider"   
  3.           android:label="@string/analog_gadget">  
  4.   <intent-filter>  
  5.     <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />  
  6.   </intent-filter>  
  7.   <meta-data android:name="android.appwidget.provider"   
  8.              android:resource="@xml/analog_appwidget" />  
  9. </receiver>  
在這裡,他指明了這個 App Widget Provider 是要由 AnalogAppWidgetProvider 這個類別來處理。這個類別的原始碼就是 AnalogAppWidgetProvider.java
  1. //#49~#65  
  2. public class AnalogAppWidgetProvider extends BroadcastReceiver {  
  3.   static final String TAG = "AnalogAppWidgetProvider";  
  4.   
  5.   public void onReceive(Context context, Intent intent) {  
  6.     String action = intent.getAction();  
  7.           
  8.     if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {  
  9.       RemoteViews views = new RemoteViews(context.getPackageName(),  
  10.                                           R.layout.analog_appwidget);  
  11.         
  12.       int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);  
  13.               
  14.       AppWidgetManager gm = AppWidgetManager.getInstance(context);  
  15.       gm.updateAppWidget(appWidgetIds, views);  
  16.     }  
  17.   }  
  18. }  
在這個類別中,主要的工作就是建立 App Widget,並透過 AppWidgetManager 發佈到桌面上。
鬧鐘程式所提供的 "Analog Clock" widget,其實功能很陽春,也沒有用到所有 App Widgets 提供的所有功能。不過,對於想要了解如何寫個 App Widget 的你,應該是有很大的幫助。如果你想要找個較為完整的 App Widget 範例,我建議你看一下 API Demos 這個範例程式。

資料來源:
http://ysl-paradise.blogspot.com/2009/08/android-app-widgets-2.html

計時器(Timer)

1. 由於若不是Main Thread則無法去變更畫面的Widget內容,需透過android.os.Handler來達到此效果。

2. MainActivity.java

01.package org.me.android_timer;
02.import android.app.Activity;
03.import android.os.Bundle;
04.import android.os.Handler;
05.import android.widget.TextView;
06. 
07.public class MainActivity extends Activity {
08.    private Long startTime;
09.    private Handler handler = new Handler();
10. 
11.    @Override
12.    public void onCreate(Bundle savedInstanceState) {
13.        super.onCreate(savedInstanceState);
14.        setContentView(R.layout.main);
15.        //取得目前時間
16.        startTime = System.currentTimeMillis();
17.        //設定定時要執行的方法
18.        handler.removeCallbacks(updateTimer);
19.        //設定Delay的時間
20.        handler.postDelayed(updateTimer, 1000);
21.    }
22. 
23.    //固定要執行的方法
24.    private Runnable updateTimer = new Runnable() {
25.        public void run() {
26.            final TextView time = (TextView) findViewById(R.id.timer);
27.            Long spentTime = System.currentTimeMillis() - startTime;
28.            //計算目前已過分鐘數
29.            Long minius = (spentTime/1000)/60;
30.            //計算目前已過秒數
31.            Long seconds = (spentTime/1000) % 60;
32.            time.setText(minius+":"+seconds);
33.            handler.postDelayed(this, 1000);
34.        }
35.    };
36.}


3. main.xml(Layout)
01.<?xml version="1.0" encoding="utf-8"?>
02.<AbsoluteLayout
03.    android:id="@+id/widget0"
04.    android:layout_width="fill_parent"
05.    android:layout_height="fill_parent"
07.    >
08.    <TextView
09.        android:id="@+id/timer"
10.        android:layout_width="wrap_content"
11.        android:layout_height="wrap_content"
12.        android:text="0:0"
13.        android:textSize="70sp"
14.        android:layout_x="90px"
15.        android:layout_y="160px"
16.        >
17.    </TextView>
18.</AbsoluteLayout>


4. 執行之後的畫面。

Android中使用Timer配合postInvalidate()刷新View


在一個沒有使用線程的小遊戲中想刷新一下時間進度,想到用Timer。於是寫了一段代碼︰

        nStartRoundTime = System.currentTimeMillis();
        nT1 = new Timer();
        nT1.schedule(new TimerTask(){ //計畫營運時間間隔
                public void run(){
                    refreshTimePaint(); //過3秒調用一下refreshTimePaint()
                }
              },0,3000);

    public void refreshTimePaint(){
        invalidate(); //使用invalidate();刷新
        System.out.println(System.currentTimeMillis());
        System.out.println(nGameState);
    }

    同時我也將System.currentTimeMillis()列印在View上。

營運一下,發現並不是預期那樣, System.out.println的結果在Log裡面都有變化,但是View卻沒有回應。 不但View上面沒有被刷新,甚至連原來的觸屏事件都沒有反映了。

去網上查了一下,得到的一些解釋有這些︰

The best thing is to  use Handler with delayed messages.
And Timer works fine, the problem is that a Timer runs in a separate thread,   and so you are trying to modify a view owned by another thread (the main   thread that originally created it).

What I think is happening is you're falling off the UI thread. There is a single "looper" thread which handles all screen updates. If you attempt to call "invalidate()" and you're not on this thread nothing will happen.

Try using "postInvalidate()" on your view instead. It'll let you update a view when you're not in the current UI thread.

於是把refreshTimePaint()的代碼改成︰

public void refreshTimePaint(){
        this.postInvalidate(); //使用postInvalidate();刷新
        System.out.println(System.currentTimeMillis());
        System.out.println(nGameState);
    }

這樣View就能自動刷新了~~~

這裡有幾個網頁做參考︰

http://stackoverflow.com/questions/522800/android-textview-timer

http://groups.google.com/group/a ... f5a3eaa823b7b?pli=1

http://groups.google.com/group/a ... sg/f5765705b8c59d66

資料來源: http://blog.csdn.net/lixinso