天天看點

Android 應用程式基礎

 Android應用程式是用java語言寫的,通過aapt工具把應用程式所需要的任何資料、資源檔案打包成apk檔案,這個檔案是一個将應用安裝到手機上的一個載體。

         有很多方式,每個android應用程式存在于不同的世界:

(1)       預設的,每個應用在他自己的Linux程序中運作,當應用中的任何代碼需要執行時android就啟動相應的程序,當不需要執行時并且系統資源被其他應用請求時android就關閉相應的程序。

(2)       每個程序都有他自己的虛拟機對象(VM),是以應用程式代碼與其他的應用運作是彼此隔離的。

(3)       預設的,每個應用被配置設定一個唯一的Linux user ID,都被設定權限以便應用程式的檔案隻對使用者可見或者隻對應用自己可見。

安排兩個應用程式共享一個user ID是可能的,這種情況下他們彼此之間是可以看見對方的檔案的,為了保護系統資源,擁有相同ID的應用也能被安排運作在一個相同的Linux程序中,共享相同的VM。

1、    應用元件(Application Components)

Android一個核心的特點就是一個應用能使用另一個應用的元素(如果另一個應用允許的話),你的應用不需要包含你用到的另一個應用的代碼也不需要你連接配接這些代碼,相反的,隻是當應用需要這些代碼時,就啟動另一個應用相應的代碼(不是讓另一個應用全部啟動)

為了這個能工作,當一個應用的任何部分被需要時系統必須能啟動這個應用程序,并且将這個部分執行個體化成java對象,是以,和其他大多數系統不同的是,android應用程式沒有一個單獨的程式入口(例如:沒有main()函數),相反的,android應用有必要的元件以便當需要時系統能執行個體化并運作它,android中有四種元件:

(1)       Activity  

一個Activity是一個可見的使用者可以使用的使用者界面,如果一個應用中有多個Activity,雖然彼此結合形成一個應用在一起工作,但是每個Activity是彼此獨立的,每個都是Activity的一個子類。

一個應用程式可能由一個或多個Activity組成,這些Activity這麼樣顯示,需要多少個Activity,依賴于這個應用的設計者,一般的,有一個Activity應該被标記成當這個應用啟動時第一個呈現出來給使用者的。

每個Activity預設的被給予一個視窗來繪制,一般的,這個視窗占滿整個螢幕,但是他可以比螢幕小并且浮在另一個視窗的上面。

一個視窗中的可見的内容是由一些具有層次關系的view組成的,都是繼承自View類的,每個view都控制一個視窗中的特定的矩形框,parent view 包含children view群組織children view的布局,leaf view(那些在繼承層次最底層的view)繪制在他們所控制的矩形框中,并且對使用者的動作做出直接的回應,是以view就是Activity和使用者互動的地方,android有很多已經做好的view你可以使用,包括buttons,text fields,scroll bars,menu items,check boxes等等

一個view hierarchy是通過Activity.setContentView()方法被放到一個Activity的window中的,content view是view hierarchy中最頂端的那個view。

(2)       Services

一個service不是一個使用者可見的元件,在不确定的一段時間内運作在背景,每個service都繼承自Service類。

你可以連接配接(connect)或者綁定(bind)到一個正在運作的service(如果這個service還沒運作的話就啟動它),當連接配接到service後,你可以通過一個service暴露出來的接口和這個service交流,對music service來說,這個接口可以是允許使用者暫停,後退,停止,重新播放。

和Activity或者其他元件一樣,service運作在這個應用程序的主線程中,是以他不會阻塞其他的元件或者使用者界面,他們經常為那些耗時長的任務單獨開一個線程。

(3)       Broadcast receivers

一個broadcast receiver這樣一個元件,他隻是接收廣播并作出反應,在系統中有很多已有的廣播,比如反應時區變化(timezone)的,電池變化(battery)的,使用者修改了系統語言時的廣播,應用程式也可以自己定義廣播,比如定義這樣一個廣播,讓其他的應用知道某些資料已經下載下傳完畢了可以使用了。

一個應用可以有任意多個broadcast receiver來對他所關心的廣播進行監聽并作出反應。所有的receiver都繼承自BroadcastReceiver類。

BroadcastReceiver不顯示在使用者界面上,然而,他們可以啟動一個Activity來作為他們接收到的資訊一種反應,或者他們可以使用NotificationManager來提示使用者,Notifications可以通過不同的方式獲得使用者的注意,比如點亮呼吸燈,震動電話,播放一個聲音等等,他們一般放一個圖示在狀态欄上,來讓使用者可以打開獲得這些資訊。

(4)       Content providers

Content providers是一個應用程式資料的集合,來讓其他的應用可以通路這些資料,這些資料可以被存在檔案系統中,SQLite資料庫中,或者其他可以存資料的地方,Content providers是一個基本的方法集合來使其他的應用可以獲得和存儲這些資料,然而應用不直接調用這些方法,而是使用一個ContentResolver對象來調用這些方法,一個ContentResolver可以和任何的Content providers交流,他和provider協作來管理系統中任何程序間的通信。

無論何時一個請求都應該由一個特定的元件來處理,android系統來確定包含這個元件的應用程序運作,如果需要就啟動它,如果需要就為這個元件創造一個執行個體,確定這個元件的一個适當的執行個體可以被得到。

2、    啟動元件:intent

當有一個來自于content resolver的請求指向Content provider時,content provider啟動,其他的三個元件(Activity,service,broadcast receiver)是通過一個叫做intent的異步的消息來啟動的,一個intent持有一個message的内容,對Activity和service來說,他是一個被要求的動作(action)和在該動作上的資料的URI,對broadcast receiver來說,intent對象是一個被廣播的動作。

針對每種元件分别有對應的方法來啟動它:

(1)       一個Activity是通過傳遞一個Intent對象到Context.startActivity()或者Activity.startActivityForResult()來啟動的(或者去做一些新的任務),被啟動的這個Activity可以通過getIntent()來獲得導緻他啟動的那個intent的。

(2)       一個service是通過傳遞一個Intent對象到Context.startService()來啟動的(或者給一些新的指令給正在運作的service),android調用service的onStart()方法,并且把Intent對象傳遞給他,同樣的,一個Intent可以傳遞到Context.bindService()方法裡來建立一個介于正在運作的service和調用他的元件之間的連接配接,這個service通過onBind()方法來接收這個Intent對象,(如果這個service還沒有運作,bindservice()能選擇性的啟動它),在後面的部分,關于綁定service的更多詳細的資訊請檢視遠端調用。

(3)       一個應用可以通過傳遞一個Intent對象給像Context.sendBroadcast(), Context.sendOrderedBroadcast(), Context.sendStickyBroadcast()這樣的方法來開始一個廣播,android通過調用對應的onReceive()方法将intent傳遞給所有對這個廣播感興趣的broadcast receiver。

3、    關閉元件(Shutting down components)

當對來自于content resolver的請求作出回應時content provider就啟動了,當有一個感興趣的broadcast message被廣播時,broadcast receiver啟動,是以我們需要知道怎麼關閉這些元件。

(1)       Activity可以通過調用它自己的finish()方法來關閉,一個Activity也可以通過調用finishActivity()來關閉另一個Activity(這個Activity是通過調用startActivityForResult()來啟動的)。

(2)       一個service可以通過調用自己的stopSelf(),或者Context.stopService()來關閉。

當元件不再使用時或者android為了更多元件能運作而回收記憶體時,android系統是關閉這些元件的,在後面的部分,可以在元件的生命周期中看到更多更詳細的介紹。

4、    Activities and Tasks

一個Activity可以啟動另一個Activity,即使這個Activity是定義在另一個應用裡的,比如說,你想展示給使用者一條街的地圖,現在已經有一個Activity可以做這件事,那麼現在你需要做的就是将你請求的資訊放進一個Intent對象裡,并且通過startActivity()傳遞給他,這個地圖就可以顯示出來了,但使用者按下BACK鍵時,你的Activity又重新出現在螢幕上。

對使用者來說,顯示地圖的Activity和你的Activity好像在一個應用中的,即使是他們是定義在不用的應用中的,運作在各自的應用程序中,android将兩個Activity放進一個task裡,一個task是一組彼此聯系的Activity,被安排在一個堆棧中,堆棧中的根Activity就是開辟這個task的,一般的,他是使用者選擇應用後首先啟動的那個Activity,堆棧頂部的Activity是目前正在運作的Activity,當一個Activity啟動另一個Activity時,新的Activity被壓進堆棧中,成為運作的Activity,當使用者按下BACK鍵,目前的Activity彈出堆棧,先前的Activity恢複成為運作的Activity。

一個task就是一組Activity的堆棧,不是在manifest檔案裡的一個類,一個元素,是以沒有方法來為一個task裡的Activity獨立的設定值,對task設定值是在root Activity裡設定的。

一個task裡的所有Activity組成一個單元,整個task(整個Activity堆棧)可以在前台,也可以在背景(應用程式的切換就是task的前背景的切換),假設,目前的task有四個Activity在堆棧裡,使用者按下HOME鍵,去開啟另一個應用(實際上是一個新的task),那麼目前的task就退到背景運作,新開啟的應用的root Activity此時就顯示出來了,然後,過了一段時間,使用者回到主界面,又重新選擇了以前的那個應用(先前的那個task),那麼先前的那個task此時又回到了前台了,當使用者按下BACK鍵時,螢幕不是顯示剛剛關閉的那個應用,而是移除回到前台的這個task堆棧棧頂Activity,将下一個Activity顯示出來。

剛才描述的情況是Activity和task預設的行為,但是有很多的方法來對幾乎所有的方面進行修改,如Activity和task的聯系。task裡Activity的行為,是受啟動它的Intent對象的flag和在manifest檔案中的Activity的屬性集合共同影響的。

Flag:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

FLAG_ACTIVITY_SINGLE_TOP

<activity>屬性:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

5、    Affinities and new tasks

預設的,一個應用裡的所有Activity都有聯系,所有都是屬于一個task的,然而,可以通過<activity>下的taskAffinity屬性來為每個Activity單獨的設定屬性關系,定義在不同應用中的Activity可以共享一種關系(affinity),或者定義在同一個應用中的Activity可以配置設定不同的關系(affinity)。這種關系在兩種情況下生效,當啟動Activity的 Intent對象包含有FLAG_ACTIVITY_NEW_TASK标志,一個Activity的allowTaskReparenting屬性設定為true。

一個Activity調用startActivity()啟動一個新的Activity時,新的Activity會壓入到相同的task中的,如果傳遞給startactivity()的Intent對象含有FLAG_ACTIVITY_NEW_TASK标志,系統就會尋找一個新的task來裝這個新的Activity,然而,也不總是這麼做,如果已經有一個task和這個新的的Activity有相同的關系,那麼就把這個新的Activity放進這個task裡,如果沒有,就啟動一個新的task。

allowTaskReparenting屬性

如果一個Activity的allowTaskReparenting屬性設定為true,這個Activity就可以從啟動時的那個task移動到一個和他有關系的目前在前台的一個task裡,比如,假設現在有一個天氣預報的Activity被定義在一個旅行的應用裡,他和這個應用裡的其他Activity有相同的關系(預設的關系),并且他允許reparenting,現在你自己應用有一個Activity啟動這個天氣預報的Activity,那麼天氣預報Activity就會移動到你的Activity所在的task裡,當旅行的應用又回到前台時,天氣預報Activity重新回到以前的那個task并顯示。(個人觀點:如果說沒有設定這個屬性,或者這個屬性設定為false,那麼一個應用裡的Activity調用另一個應用裡的Activity時,系統是為另一個應用裡的Activity建立一個執行個體,然後放到同一個task裡,但是如果設定了allowTaskReparenting為true,那麼另一個應用裡的Activity是可以在不同的task間來回移動的,那個task在前台就移動到那個task裡)

6、    啟動方式

<activity>下的launchMode屬性可以設定四種啟動方式:

"standard" (the default mode)

"singleTop"

"singleTask"

"singleInstance"

這些不同的方式可以從下面的四點來說:

(1)       對一個Intent作出回應時哪個task應該去持有這個Activity。

對standard和singleTop方式來說,新的Activity和通過startActivity調用他的Activity處在同一個task中,如果調用時Intent對象裡含有FLAG_ACTIVITY_NEW_TASK标志,那麼就像前面講的那樣的尋找一個新的task。

相反的,singTask和singleInstance方式,總是标志Activity為task的root Activity,他們不會進入到其他的task中。

(2)       一個Activity是否可以有多個執行個體。

一個standard或者singleTop屬性的Activity可以執行個體化多次,他們可以屬于多個不同的task。

相反的,singleTask或者singleInstance屬性的Activity隻能有一個執行個體(單例)。

(3)       執行個體是否能允許在task裡有其他的Activity。

一個singleInstance屬性的Activity單獨的在他自己的task裡,并且這個task裡隻能有他自己一個Activity,如果他啟動了另一個Activity,那個Activity會根據啟動模式來啟動并裝進一個不同的task裡。其他的方面,singleInstance和singleTask一樣的。

其他三個方式允許有多個Activity在一個task裡,一個singleTask屬性的Activity總是一個task裡的root Activity,但是他可以啟動另外的Activity并且将這個新的Activity裝進同一個task裡,standard和singleTop屬性的Activity可以出現在task的任何地方。

(4)       一個類(Activity)的對象是否可以被啟動來處理一個新的Intent。

對預設的standard方式,會執行個體化一個對象來處理每一個新的Intent,每個執行個體處理一個新的Intent,對singleTop方式,如果一個已經存在的執行個體是在task的棧頂,那麼就重用這個執行個體來處理這個新的Intent,如果這個執行個體不在棧頂,那就不複用他,而是重新建立一個執行個體來處理這個新的Intent并且将這個執行個體壓入堆棧。

例如現在有一個task堆棧ABCD,A是root Activity,D是棧頂Activity,現在有一個啟動D的Intent來了,如果D是預設的standard方法,那麼就會建立一個新的執行個體來處理這個Intent,是以這個堆棧就變為ABCDD,然而如果D是singleTop方式,這個已經存在的棧頂的D就會來處理這個Intent,是以堆棧還是ABCD。D此時調用onNewIntent(),此時D可以調用getIntent()來獲得最初的Intent,或者調用setIntent()來更新這個Intent。

如果現在有一個Intent來啟動B,不管B是standard還是singleTop(因為現在B不在棧頂),都會建立一個新的執行個體,是以堆棧變為ABCDB

在一個task裡,對singleTask和singleInstance屬性的Activity隻能有一個執行個體。是以這僅有的一個會來處理是以的Intent,一個singleInstance屬性Activity總在棧頂(因為task裡就隻有他一個Activity),是以他會處理是以的Intent,但是一個singleTask屬性的Activity必須是task的root Activity(也就是必須在棧底),不能确定他的上面是否還有其他的Activity,如果沒有,就可以處理,如果還有其他的Activity,那麼如果現在有一個Intent來啟動這個singleTask屬性的Activity,這個Intent将會被丢掉(即使是這個Intent被丢掉,他的到來還是會導緻這個task回到前台)。

當建立一個類(Activity)的執行個體來處理一個新的Intent時,使用者可以按下BACK鍵回到上一個Activity,但是如果是用已經存在的棧頂的Activity來處理Intent的話,按下BACK鍵是不能回到以前的狀态的(沒處理這個Intent之前)。

7、    清理堆棧

當使用者離開一個task一段時間後,系統就會清理掉task裡出了rootActivity以外的Activity,如果使用者又回來了,顯示的是rootActivity,就像是使用者離開又回來,是放棄以前的東西,開始新的東西。

上面說的是預設的情況,有一些Activity的屬性可以用來控制和修改這些行為。

如果一個task裡的root Activity的alwaysRetainTaskState屬性設定為true,那麼前面描述的預設情況就不會出現了,task即使過了一段時間也會一直保留所有的Activity。

如果一個task裡的root Activity的clearTaskOnLaunch屬性設定為true,和alwaysRetainTaskState相反,即使是一瞬間的離開,系統馬上就會清理掉task裡出rootActivity以外的所有Activity。

這個屬性和clearTaskOnLaunch一樣,但是他是對一個Activity起作用,不是整個task,他能引起所有的Activity離開,包括root Activity,當這個屬性設定為true,隻是當使用者使用這個應用時Activity才在task裡,一旦使用者離開後重新回來,顯示的不是目前的界面。

還有其他的方法來從task裡強制移動Activity,如果一個Intent對象裡包含FLAG_ACTIVITY_CLEAR_TOP标志,并且目标task裡已經一個在自己task裡可以處理Intent的Activity(就是處理這個Intent無需執行個體化另外一個Activity),那麼在這個Activity之上的所有Activity将被清除,能處理這個Intent的Activity就移到棧頂來處理這個Intent,例如ABCD堆棧,含有FLAG_ACTIVITY_CLEAR_TOP标志的Intent來啟動B,那麼清除CD,B到達棧頂來響應Intent,此時是AB,如果B設定了standard屬性,那麼還是清楚CD,然後再建立一個執行個體來響應Intent,此時是ABB,因為standard屬性的Activity總是建立一個新的執行個體來響應新的Intent。

8、    程序和線程(Processes and Threads)

當一個應用的第一個元件需要運作時,android系統就為這個元件啟動一個隻有一個線程的Linux程序,預設的,應用的所有元件都運作這個程序中的這個線程中。

但是,你可以安排元件運作在其他的程序中,并且為你的任意的程序增加若幹線程。

1、 程序

元件運作的程序是在manifest檔案裡控制的,四大元件都一個process屬性可以指定程序來運作,這些屬性可以被設定為了每個元件都可以運作在他自己的程序中,或者幾個元件共享一個程序,或者不共享,如果兩個應用共享一個Linux user ID并且有相同的權限,那麼就可以使這兩個應用中的元件運作在相同的程序中,<application>也有process屬性,用來指定對所有元件的屬性。

所有的元件都在指定的程序中的主線程中執行個體化,系統調用這些元件就是從主線程裡發出的,其他的線程将不會對每個元件再執行個體化,所有作為調用的回應的這些方法,比如說View.onKeyDown()還是元件的生命周期函數等等都是運作在這個主線程中的,這就意味着當系統調用這個元件時,這個元件不能長時間的阻塞線程(比如說網絡操作,循環計算),因為這樣會阻塞這個程序中的其他元件,你可以将很耗時的任務分到其他的線程中。

當記憶體不足或者有其他更緊急的程序要求時,Android系統可能關閉一個程序,運作在這個程序中的應用元件是以被銷毀,當使用者又重新回來時,程序才被重新啟動。

至于究竟要停止哪個程序,android系統是通過衡量哪個程序對使用者來說更重要來實作的

2、 線程

你可以限制你的應用運作在一個程序中,但是有的時候你需要新開一個線程在背景運作,使用者界面需要随時對使用者的要求做出反應,是以一些很耗時的工作應該重新啟動一個線程來做,以免阻塞主程序。

Android系統提供了一系列友善的類來管理線程(Looper,Handler,HandlerThread)

3、 遠端調用(Remote procedure calls)

Android系統有一個輕量級的遠端調用機制(RPC)-----一個方法在本地調用,但是在遠端執行(在另外一個程序裡),傳回給調用端的所有結果都必須的系統能了解的,将資料從本地程序和位址空間傳遞到遠端的程序和位址空間,并在遠端重新裝配,傳回值的時候傳輸方向相反,android系統會去做這些傳輸的工作,讓你能夠集中精力來定義你的RPC

一個RPC接口隻能包含方法,預設的,即使是沒有值傳回,所有的方法都是同步執行的,就是說本地方法一直會阻塞直到遠端的方法執行完畢)。

簡單的說,這個遠端調用的機制是這樣工作的:

首先你需要用IDL(interface definition language)聲明你的RPC接口,然後android系統會使用aidl工具來形成一個java接口,并且這個java接口是本地程序和遠端程序都可以獲得的,這個java接口包含了兩個内部類,請看下圖:

Android 應用程式基礎

這兩個内部類有管理遠端調用(你用IDL聲明的接口)的是以代碼,兩個内部類都實作IBinder接口,一個是在本地(内部)使用,這個你可以不用自己寫代碼,另外一個叫做Stub,繼承自Binder類的,包含所有完成程序間通信(IPC)的代碼,他包含你在RPC接口中聲明的所有方法,你應該繼續繼承Stub類來實作這些方法。

一般的,遠端程序應該由一個service來管理(因為一個service能通知系統關于這個程序和他連接配接到的其他程序)。

9、    程序的生命周期(Processes and lifecycles)

Android系統總是盡最大的努力來維持一個應用的程序,但系統的記憶體不足時就可能需要關閉一些舊的程序了,但是決定關閉哪個程序呢,android系統把是以的程序放進一個重要性樹裡,最低重要性的程序将會被停止,系統有5種重要性等級,重要性從高到低如下:

(1)、前台程序。一個前台程序是目前執行使用者請求的程序,如果有如下的一種情形的那麼他就是前台程序:

 a、這個程序裡運作着一個正在和使用者互動的Activity(這個Activity的onResume()方法被調用)。

b、這個程序裡有綁定到目前正在和使用者互動的Activity的一個service

c、這個程序裡有一個service對象,這個service對象執行了至少一個他生命周期的函數(onCreate(), onStart(), or onDestroy()).

d、這個程序裡有一個執行了onReceive()方法的broadcastreceiver對象

隻有一定數量的前台程序在任何時間都存在,他們隻有在最後的時刻被停止---系統的記憶體太少了而不能運作這些僅有的前台程序了),一般的,在那個時刻,手機會重新設定記憶體頁的狀态,是以停止一些前台的程序是為了保持對使用者操作的快速響應。

(2)       可見程序。一個可見程序一個沒有任何前台顯示的元件,但是仍然可以影響到使用者目前螢幕所看見的東西,如果有如下的一種情形那麼他就是可見程序。

a、 這個程序裡一個Activity,但是這個Activity目前不是在前台顯示,但是仍然對使用者是可見的(這個Activity的onPause()方法被調用),比如說一個Activity調用一個dialog,那麼這個dialog是目前顯示的元件,這個Activity不是在前台顯示,但是對使用者是可見的。

b、 這個程序裡有一個綁定到一個可見Activity(如上所述的Activity)的service

             一個可見程序是極端重要的,隻有在為了顯示所有前台程序時,即顯示前台程序都不夠時,才會停止可見程序。

                (3)、服務程序。一個服務程序是一個通過startService()啟動的但是沒有在前兩個分類中的程序,雖然服務程序不是使用者直接能看見的,但是他也總是做一些使用者很關心的事(如在背景播放mp3,從網絡上下載下傳東西),是以系統會一直保持服務程序運作,除非記憶體不足以運作服務程序,前台程序,可見程序。

        (4)背景程序。一個背景程序是運作一個目前對使用者是不可見的Activity(這個Activity的onStop()被調用),這些程序對使用者體驗沒有什麼直接的影響,當記憶體不足以運作前台程序,可見程序,服務程序時,可以随時停止背景程序,通常有很多的背景程序在運作,系統會把這些背景程序放進一個LRU中(最近使用隊列),最近使用的就最後停止。

(5)       空程序。一個空程序就是程序裡沒有任何活動的應用元件,維持這種程序的唯一原因就是作為一種緩存,當一個元件需要啟動時加快啟動的速度,系統為了平衡程序緩存和核心緩存會停止這些空的程序。

Android系統會取一個程序裡的是以元件的最高重要性來安排程序的重要性,比如說,一個程序裡有一個service和一個可見的Activity,那麼這個程序會被安排成一個可見程序,而不是服務程序。

另外,一個程序的重要性有可能會因為其他程序的依賴而升高,一個程序不能比他所服務的程序的重要性低,比如有程序A裡的service綁定到了程序B的元件上,那麼程序A的重要性至少和程序B的一樣,或者更高。

因為一個服務程序的重要性比運作一個背景Activity的程序高,是以,當一個Activity做一些長時間運作的任務時,最好啟動一個service來做,而不是放到一個線程裡去做,特别是這個任務的時間可能比Activity運作的時間還長的時候,比如在背景播放音樂,或者上傳一張圖檔到網上,使用一個service保證了這個任務至少是服務程序的重要性,broadcast receiver也是一樣,長時間運作的任務也最好是放到一個service裡,而不是放到一個線程裡。