- Android應用程序開發與典型案例
- 鄭萌 趙常松等編著
- 1406字
- 2018-12-27 18:22:13
第4章 Android生命周期
經過上一章的學習,主要了解了Android應用程序設計的基礎知識,對Android程序的開發有了一定的了解。在此基礎上,本章將對Android系統的進程優先級的變化方式、Android系統的4大基本組件、Activity的生命周期中各個狀態的變化關系、Android應用程序的調試方法和工具進行學習。
4.1 程序生命周期
所謂的應用程序生命周期就是應用程序進程從創建到消亡的整個過程。在Android中,多數情況下每個程序都是在各自獨立的Linux進程中運行的。當一個程序或其某些部分被請求時,它的進程就“出生”了;當這個程序沒有必要再運行下去且系統需要回收這個進程的內存用于其他程序時,這個進程就“死亡”了。可以看出,Android程序的生命周期是由系統控制而非程序自身直接控制。這和編寫桌面應用程序時的思維有一些不同,一個桌面應用程序的進程也是在其他進程或用戶請求時被創建,但是往往是在程序自身收到關閉請求后執行一個特定的動作(如從main方法中return)而導致進程結束的。
簡而言之,程序的生命周期是在Android系統中進程從啟動到終止的所有階段,也就是Android程序啟動到停止的全過程,程序的生命周期是由Android系統進行調度和控制的。
但是,一個不容忽視的問題就是,手機的內存是有限的,隨著打開的應用程序數量的增多,隨之而來的可能會是應用程序響應時間過長或者系統假死的糟糕情況。所以,若將Android應用程序生命周期交由系統處理的話,那么在系統內存不足的情況下,便由Android系統舍車保帥,選擇性地來終止一些重要性較次的應用程序,以便回收內存供更重要的應用程序使用。

圖4-1 Android系統進程優先級
那么,系統是根據一個什么樣的重要性標準來終止Android應用程序的呢?
Android根據應用程序的組件及組件當前運行狀態將所有的進程按重要性程度從高到低劃分了五個優先級:前臺進程、可見進程、服務進程、后臺進程、空進程。
以下就按優先級由高到低的順序介紹Android系統中的進程。
1.前臺進程
前臺進程是顯示在屏幕最前端并與用戶正在交互的進程,是Android系統中最重要的進程,包含以下4種情況。
? 進程中的Activity正在與用戶進行交互。
? 進程服務被Activity調用,而且這個Activity正在與用戶進行交互。
? 進程服務正在執行聲明周期中的回調方法,如onCreate()、onStart()或onDestroy()
? 進程的BroadcastReceiver正在執行onReceive()方法。
Android系統在多個前臺進程同時運行時,可能會出現資源不足的情況,此時會清除部分前臺進程,保證主要的用戶界面能夠及時響應。
2.可見進程
可見進程指部分程序界面能夠被用戶看見,卻不在前臺與用戶交互,不響應界面事件(其onPause()方法已被調用)的進程。如果一個進程包含服務,且這個服務正在被用戶可見的Activity調用,此進程同樣被視為可見進程。
Android系統一般存在少量的可見進程,只有在特殊的情況下,Android系統才會為保證前臺進程的資源而清除可見進程。
3.服務進程
服務進程是指包含由startService()方法啟動服務的進程。它有以下特性:沒有用戶界面;在后臺長期運行。例如,后臺MP3播放器或后臺上傳下載數據的網絡服務。
Android系統除非不能保證前臺進程或可見進程所必要的資源,否則不強行清除服務進程。
4.后臺進程
后臺進程是指不包含任何已經啟動的服務,而且沒有任何用戶可見的Activity的進程。這些進程不直接影響用戶的體驗。
Android系統中一般存在數量較多的后臺進程,因此這些進程會被保存在一個列表中,以保證在系統資源緊張時,系統將優先清除用戶較長時間沒有見到的后臺進程。
5.空進程
空進程是不包含任何活躍組件的進程。一般保留這些進程,是為了將其作為一個緩存,在它所屬的應用組件下一次需要時,縮短啟動的時間。
空進程在系統資源緊張時會被首先清除,但為了提高Android系統應用程序的啟動速度,Android系統會將空進程保存在系統內存中,在用戶重新啟動該程序時,空進程會被重新使用。
問:除了以上的優先級外,還有其他因素決定進程的優先級嗎?
答:
? 進程的優先級取決于所有組件中的優先級最高的部分。
? 進程的優先級會根據與其他進程的依賴關系而變化。
4.2 Android組件
組件是可以調用的基本功能模塊。Android應用程序就是由組件組成的,Android系統有4個重要的組件,分別是Activity、Service、BroadcaseReceiver和ContentProvider。
Activity是Android程序的呈現層,顯示可視化的用戶界面,并接收與用戶交互所產生的界面事件。在界面上的呈現形式就是全屏窗體、非全屏懸浮窗體的對話框,與在桌面系統上的獨立事業,如辦公應用等類似。Activities是可執行的代碼塊,由用戶或者操作系統來進行初始實例化,并在他們被需求時致以運行。Activities可以與用戶、請求數據或者其他Activity、Service的服務通過query或Intent進行交互。大部分為Android編寫的可執行代碼將以Activity的形式執行。對于一個Android應用程序來說,可以包含一個或多個Activity,一般在程序啟動后會呈現一個Activity,用于提示用戶程序已經正常啟動。當它不積極運行時,Activity可以被操作系統終止以節省內存。
Service用于沒有用戶界面,但需要長時間在后臺運行的應用。它類似于桌面應用或者服務器操作系統上的服務或守護進程。Service是在后臺運行的可執行的代碼塊,從它被初始化一直運行到該程序關閉。一個Service的典型的例子是一個MP3播放器,盡管用戶已經使用其他應用程序,但仍然需要持續播放文件。你的應用程序可能需要在沒有用戶界面的情況下一直執行Service來實現后臺任務。
Broadcast和Intent Receivers對從其他的應用程序的服務請求做出一個全系統廣播的響應,這些廣播響應可能來自于Android系統本身或者是任何在其系統上運行的程序。BroadcaseReceiver是用來接受并響應廣播消息的組件。它不包含任何用戶界面,但可以通過啟動Activity或者Notification通知用戶接收到重要信息。
問:Notification如何提示用戶?
答:閃動背景燈、振動設備、發出聲音或在狀態欄上放置一個持久的圖標。
Activity或Service通過執行一個IntentReceiver為其他應用程序提供了訪問其功能的功能。Intent Receiver是一段可執行代碼塊,對其他Activity的數據或服務請求做出響應。請求的Activity(客戶端)生成一個Intent,把其添加至Android Framework中,來指出哪些應用程序(目標程序)接收并對其做出響應。Intent是Android的主要構成元素之一,它從現有的應用程序中創造新的應用程序。Intent實現了應用程序和其他的應用程序和服務交換所需信息的功能
ContentProvider是Android系統提供的一種標準的共享數據的機制,應用程序可以通過ContentProvider訪問其他應用程序的私有數據(私有數據可以是存儲在文件系統中的文件,也可以是SQLite中的數據庫)。Android系統內部也提供一些內置的ContentProvider,能夠為應用程序提供重要的數據信息。
所有Android組件都具有自己的生命周期,是從組件建立到組件銷毀的整個過程。在生命周期中,組件會在可見、不可見、活動、非活動等狀態中不斷變化。
4.3 Activity生命周期
Activity生命周期指Activity從啟動到銷毀的過程。Activity表現為4種狀態,分別是活動狀態、暫停狀態、停止狀態和非活動狀態。
? 活動狀態,Activity在用戶界面中處于最上層,完全能被用戶看到,能夠與用戶進行交互。
? 暫停狀態,Activity在界面上被部分遮擋,該Activity不再處于用戶界面的最上層,且不能夠與用戶進行交互;或者屏幕被鎖定。
? 停止狀態,Activity在界面上完全不能被用戶看到,也就是說這個Activity被其他Activity全部遮擋。
? 非活動狀態,不在以上3種狀態中的Activity則處于非活動狀態。
這四種狀態是可以相互轉換的,轉換關系圖如圖4-2所示。

圖4-2 Activity的4種狀態的轉換關系圖
圖4-3 解釋了狀態之間轉化的可能路徑。其中著色的橢圓表示活動的主要狀態,矩形表示當活動在狀態之間轉換時會被調用的回調方法。

圖4-3 Activity活動周期
Android調用以下的事件回調方法通知Activity從某一狀態轉變到另一狀態。
代碼清單4-1事件的回調方法
public class MyActivity extends Activity { protected void onCreate(Bundle savedInstanceState); protected void onStart(); protected void onRestart(); protected void onResume(); protected void onPause(); protected void onStop(); protected void onDestroy(); }
表4-1對各個事件回調方法做出說明。
表4-1 Activity 生命周期的事件回調方法

Activity事件回調方法的調用順序,如圖4-4所示。

圖4-4 Activity事件回調方法的調用順序
Activity的生命周期可分為全生命周期、可視生命周期和活動生命周期。每個生命周期中包含不同的事件回調方法。
4.3.1 全生命周期
全生命周期是從Activity建立到銷毀的全部過程,始于onCreate(),結束于onDestroy()。
使用者通常在onCreate()中初始化用戶界面,分配引用類變量,綁定數據控件,并創建服務和線程等Activity所能使用的全局資源和狀態,并在onDestroy()中釋放這些資源,并確保所有外部連接被關閉,例如,網絡或數據庫的聯系等;在一些極端的情況下,Android系統會不調用onDestroy()方法,而直接終止進程。
為了避免創造短期對象和增加垃圾收集的時間,以致對用戶體驗產生直接影響。如果你的Activity需要創建一些對象的話,最好在onCreate方法中創建,因為它在一個Actvity的完整生命周期中僅調用一次。
4.3.2 可視生命周期
可視生命周期是Activity在界面上從可見到不可見的過程,開始于onStart(),結束于onStop()。
? onStart()一般用來初始化或啟動與更新界面相關的資源。
? onStop()一般用來暫停或停止一切與更新用戶界面相關的線程、計時器和服務。
? onRestart()方法在onSart()前被調用,用來在Activity從不可見變為可見的過程中,進行一些特定的處理過程。
? onStart()和onStop()會被多次調用。
? onStart()和onStop()也經常被用來注冊和注銷BroadcastReceiver或者傳感器。
在onStart()和onStop()這兩個方法中間,Actvity對用戶將會是可見的,盡管它可能部分被遮擋著。在一個Activity完整的生命周期中可能會經過幾個Activity可見的生命周期,因為Activity可能會經常在前臺和后臺之間切換。在極端情況下,系統將銷毀掉一個Activity即使它在可見狀態并且不調用onStop方法。
4.3.3 活動生命周期
活動生命周期是Activity在屏幕的最上層,并能夠與用戶交互的階段,開始于onResume(),結束于onPause()。在Activity的狀態變換過程中onResume()和onPause()經常被調用,因此這兩個方法中應使用更為簡單、高效的代碼。
? onPause()是第一個被標識為“可終止”的方法。
? 在onPause()返回后,onStop()和onDestroy()隨時能被Android系統調用。
? onPause()常用來保存持久數據,如界面上用戶的輸入信息等。
當系統而不是用戶關閉一個活動來節省內存時,用戶可能希望返回到活動且是它之前的狀態。為了獲得活動被關閉之前的狀態,可以執行活動的onSaveInstanceState()方法。Android在活動容易被銷毀前調用這個方法,也就是調用onPause()之前。該方法的參數是一個Bundle對象,這個對象可以名值對記錄活動的動態狀態。當活動再次啟動時,Bundle同時被傳遞到onCreate()和調用onCreate()之后的方法onRestoreInstanceState()。
因為onSaveInstanceState()方法不總被調用,你應該僅使用onSaveInstanceState()來記錄活動的臨時狀態,而不是持久的數據。應該使用onPause()來存儲持久數據。
問:onPause()和onSaveInstanceState()這兩個函數都可以用來保存界面的用戶輸入數據,它們有什么區別呢?
答:
? onPause()一般用于保存持久性數據,并將數據保存在存儲設備上的文件系統或數據庫系統中的。
? onSaveInstanceState()主要用來保存動態的狀態信息,信息一般保存在Bundle中。
? Bundle是能夠保存多種格式數據的對象。
? onSaveInstanceState()保存在Bundle中的數據,系統在調用onRestoreInstanceState()和onCreate()時,會同樣利用Bundle將數據傳遞給函數。
當一個活動啟動另一個活動時,這兩個活動都經歷生命周期轉換。一個暫停或是停止,然而被啟動的活動則啟動。有時,這些活動可能需要協調。當這兩個活動在同一個進程中,生命周期的回調順序是明確界定的:調用當前活動的onPause()方法;然后,按序調用啟動活動的onCreate()、onStart()、onResume()方法;之后,如果該活動不需再在屏幕上可見,則調用它的onStop()方法。下面我們就來詳細學習一下關于Android如何管理多個Activity。
(1)Android用Activity Stack來管理多個Activity,因此,同一時刻只會有最頂上的Activity是處于active或者running狀態。其他的Activity都被壓在下面。
(2)如果非活動的Activity仍是可見的(如果上面壓著的是一個非全屏的Activity或透明的Activity),它是處于paused狀態的。在系統內存不足的情況下,paused狀態的Activity是有可能被系統銷毀掉的。
注意
因為Android應用程序的生存期并不是由應用本身直接控制的,而是由Android系統平臺進行管理的,所以,對于開發者而言,需要了解不同的組件Activity、Service和IntentReceiver的生命,切記:如果組件的選擇不當,系統很有可能會關閉一個正在進行重要工作的進程。
4.4 Activity啟動模式
Activity作為Android中重要一環,它有4種不同的啟動模式,類似于C語言中的局部變量、全局變量及靜態變量等。這4種啟動模式如下。
? standard:標準模式,調用startActivity()方法就會產生一個新的實例。
? singleTop:檢查是否已經存在了一個實例位于Activity Stack的頂部,如果存在就不產生新的實例,反之則調用Activity的newInstance()方法產生一個新實例。
? singleTask:在一個新的Task中產生這個實例,以后每次調用都會使用此實例,而避免產生新的實例。
? singleInstance:這個基本上跟singleTask一樣,只是有一點不同,那就是在這個模式下的Activity實例所處的Task中,只能有這一個Activity實例,而不能有其他的實例。
這些啟動模式在Android清單文件AndroidManifest.xml中,通過<activity>中的launchMode屬性進行設置,如代碼清單4-2所示。
代碼清單4-2 AndroidManifest.xml
<activity android:name=".Activity2" android:launchMode="singleTask"></activity>
也可以在Eclipse ADT圖形界面中編輯,如圖4-5所示。

圖4-5 設置Activity啟動模式
下面通過一個簡單的例子——LaunchMode_Test來對四種啟動模式進行簡要分析。在該例中涉及Fx_Main、Activity2及Activity3三個Activity。
下面介紹一下例子中涉及的三個Activity及其界面。
首先是Fx_Main,其界面如圖4-6所示。

圖4-6 Fx_Main的界面
在圖4-6所示的界面中,單擊“跳轉到AC2”按鈕之后,跳轉至Activity2,具體代碼如代碼清單4-3所示。
代碼清單4-3 Fx_Main.Activity
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Fx_Main extends Activity { /** Called when the activity is first created. */ private Button b; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView tv=(TextView)findViewById(R.id.TextView01); tv.setText("Main---->"+getTaskId()); Log.i("System.out", "Main---->"+this.toString()+"Task ID---->"+getTaskId()); b=(Button)findViewById(R.id.Button01); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Fx_Main.this,Activity2.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Fx_Main--->Destory"); } }
其次是Activity2,其界面如圖4-7所示。

圖4-7 Activity2的界面
在該界面中,單擊“跳回到Main”按鈕,則跳轉至Fx_Main,而單擊“跳到本頁面”則仍顯示Activity2的界面,單擊“跳到AC3”則跳轉到Activity3的界面,具體代碼如代碼清單4-4所示。
代碼清單4-4 Activity2.Activity
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Activity2 extends Activity { private Button b; private Button b2; private Button b3; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity2); b=(Button)findViewById(R.id.Button02); b2=(Button)findViewById(R.id.Button03); b3=(Button)findViewById(R.id.Button04); TextView tv=(TextView)findViewById(R.id.TextView02); tv.setText("Ac2---->"+getTaskId()); Log.i("System.out", "Ac2---->"+this.toString()+"Task ID---->"+getTaskId()); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Fx_Main.class); startActivity(i); }}); b2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Activity2.class); startActivity(i); }}); b3.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Activity3.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Ac2--->destory"); } }
最后是Activity3,其界面如圖4-8所示。

圖4-8 Activity3的界面
如圖4-8所示,單擊“返回Main”則跳轉至Fx_Main,單擊“返回AC2”,則跳轉到Activity2。具體代碼如代碼清單4-5所示。
代碼清單4-5 Activity3.Activity
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Activity3 extends Activity { private Button b; private Button b2; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity3); b=(Button)findViewById(R.id.Button03); b2=(Button)findViewById(R.id.Button04); TextView tv=(TextView)findViewById(R.id.TextView03); tv.setText("Ac3---->"+getTaskId()); Log.i("System.out", "Ac3---->"+this.toString()+"Task ID---->"+getTaskId()); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity3.this,Fx_Main.class); startActivity(i); }}); b2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity3.this,Activity2.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Ac3--->Destory"); } }
4.4.1 standard標準模式
在standard模式也就是默認模式下,不需要配置launchMode。此時的AndroidManifest.xml如代碼清單4-6所示。
代碼清單4-6 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
運行例子,從Fx_Main開始,一直點回到Activity2按鈕時,Log信息如圖4-9所示。

圖4-9 Standard啟動模式下Log信息
發現每次都創建了Activity2的新實例。standard的加載模式就是這樣的,Intent將發送給它新的Activity實例。
現在點擊Android設備的回退鍵,可以看到Log信息按照剛才創建Activity實例的倒序依次出現,類似退棧的操作,而剛才操作跳轉按鈕的過程是壓棧的操作。
4.4.2 singleTop
singleTop和standard模式,都會將Intent發送到新的實例(如果已經有了,singleTask模式和singleInstance模式不發送到新的實例)。不過,singleTop要求如果創建intent時棧頂已經有要創建Activity的實例,則將Intent發送給該實例,而不發送給新的實例。
還是用剛才的示例,只需將Activity2的launchMode改為singleTop,就能看到區別。修改后AndroidManifest.xml中代碼如代碼清單4-7所示。
代碼清單4-7 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2" android:launchMode="singleTop"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
運行Fx_Main,跳轉到Activity2---->Actvity2時會發現,單擊多少遍按鈕,都是相同的Activity2實例,因為該實例在棧頂,所以不會創建新的實例。如果回退,回到Fx_Main,將退出應用,如圖4-10所示。

圖4-10 singleTop模式下“跳轉到AC2”的Log信息
singleTop模式,可用來解決棧頂多個重復相同的Activity的問題。
如果是Fx_Main跳轉到Activity2,再跳轉到Fx_Main,行為就和standard一樣了,會在Activity2跳轉到Fx_Main時創建Fx_Main的新實例,因為當時的棧頂不是Activity2實例,如圖4-11所示。

圖4-11 singleTop模式下“跳轉到AC2”后“跳回到Main”的Log信息
4.4.3 singleTask
singleTask模式和后面的singleInstance模式都是只創建一個實例的。
當Intent到來,需要創建singleTask模式Activity時,系統會檢查棧里面是否已經有該Activity的實例。如果有直接將Intent發送給它(注意此時原在此Activity棧中上面的Activity將會被關閉)。
把Activity2的啟動模式改成singleTask,修改后AndroidManifest.xml中代碼如代碼清單4-8所示。
代碼清單4-8 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2" android:launchMode="singleTask"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
啟動Fx_Main,跳轉到Activity2---->Activity3---->Actvity2,此時看Log信息,如圖4-12所示。

圖4-12 singleTask啟動模式下Log信息
可見從AC3再跳轉到AC2時,因為AC2之前在棧中是存在的所以不生成新的AC2實例,而是在棧中找到此AC2,并將在AC2上面的AC3關閉,所以此時棧中只有Fx_Main和AC2,在AC2點返回會直接退到Fx_Main然后退出。
4.4.4 singleInstance
在singleInstance模式下,加載該Activity時如果沒有實例化,它會在創建新的Task后,實例化入棧,如果已經存在,則直接調用onNewIntent,該Activity的Task中不允許啟動其他的Activity,任何從該Activity啟動的其他Activity都將被放到其他Task中,先檢查是否有在應用的Task,沒有的話就創建。
在這里介紹一下Task(任務)的概念。按照字面意思,任務就是自己要實現的一個目的,而在Android中的Task的定義是一系列Activity的集合,即要達到自己最終要到的Actvity,之前所有經歷過的Actvity的集合。它可以是同一個應用內部的,也可以是兩個不同應用的。Task可以認為是一個棧,可放入多個Activity。比如,啟動一個應用,那么Android就創建了一個Task,然后啟動這個應用的入口Activity,就是intent-filter中配置為main和launch的那個。這個Activity是根(Root)Activity,可能會在它的界面調用其他Activity,這些Activity如果按照上面那3個模式,也會在這個棧(Task)中,只是實例化的策略不同而已。
把Activity2的啟動模式改成singleInstance,修改后AndroidManifest.xml中代碼如代碼清單4-9所示。
代碼清單4-9 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="feixun.com.jiang" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Fx_Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Activity2" android:label="@string/Ac2" android:launchMode="singleInstance"/ > <activity android:name=".Activity3" android:label="@string/Ac3/> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
然后進行測試,啟動Fx_Main---->Actvity2---->Actvity3然后看一下Log信息,如圖4-13所示。

圖4-13 singleInstance啟動模式下Log信息
可以看到Fx_Main以及Activity3的Task ID為9,而Actvity2的Task ID為10,此時在Actvity3單擊“返回”按鈕會發現先退到Fx_Main,繼續返回會回到Actvity2最后退出。從該過程可以看出:如果從其他應用程序調用singleInstance模式的Activity(Fx_Main),從該Activity開啟其他Activity(Activity2)時,會創建一個新的Task(Task ID為10的那個),實際上,如果包含該Activity(Activity2)的Task已經運行的話,他會在該運行的Task中重新創建。
經過上述的介紹,用下面的表格來進行一個簡單的總結,如表4-2所示。
表4-2 Activity4種啟動模式對比

4.5 程序調試
Android系統提供了兩種調試工具LogCat和DevTools,用于定位、分析及修復程序中出現的錯誤。
4.5.1 LogCat命令行工具
LogCat是可以顯示在Eclipse集成開發環境中的用來獲取系統日志信息的工具。它的主要功能就是能夠捕獲包括Dalvik虛擬機產生的信息、進程信息、ActivityManager信息、PackagerManager信息、Homeloader信息、WindowsManager信息、Android運行時信息和應用程序信息等可被捕獲的信息。
1.LogCat的使用方法
打開方式:選擇“Window”→“Show View ”→“Other”命令,打開Show View的選擇菜單,然后在Andoird → LogCat中選擇LogCat。打開LogCat后,它便顯示在Eclipse的下方區域,其界面如圖4-14所示。

圖4-14 LogCat界面
從圖中我們可以看到LogCat的右上方有5個不同的字母,這5個字母分別表示5種不同類型的日志信息,它們的級別依次增高,表示含義如下。
? V:詳細(Verbose)信息。
? D:調試(Debug)信息。
? I:通告(Info)信息。
? W:警告(Warn)信息。
? E:錯誤(Error)信息。
在LogCat中,用戶可以通過5個字母圖標選擇顯示的信息類型,級別高于所選類型的信息也會在LogCat中顯示,但級別低于所選類型的信息則不會被顯示。
同時,LogCat提供了“過濾”功能,在右上角的“+”號和“-”號,分別是添加和刪除過濾器。用戶可以根據日志信息的標簽(Tag)、產生日志的進程編號(Pid)或信息等級(Level),對顯示的日志內容進行過濾。
2.程序調試原理
? 引入android.util.Log包。
? 使用Log.v()、 Log.d()、 Log.i() 、Log.w() 和Log.e() 5個方法在程序中設置“日志點”。
? Log.v()用來記錄詳細信息。
? Log.d()用來記錄調試信息。
? Log.i()用來記錄通告信息。
? Log.w()用來記錄警告信息。
? Log.e()用來記錄錯誤信息。
? 當程序運行到“日志點”時,應用程序的日志信息便被發送到LogCat中。
? 判斷“日志點”信息與預期的內容是否一致。
? 進而判斷程序是否存在錯誤。
下面的例子演示了Log類的具體使用方法。
代碼清單4-10 LogCat.java
package com.example.LogCat; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class LogCat extends Activity { final static String TAG = "LOGCAT"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.v(TAG,"Verbose"); Log.d(TAG,"Debug"); Log.i(TAG,"Info"); Log.w(TAG,"Warn"); Log.e(TAG,"Error"); } }
在本段代碼中,程序第5行“import android.util.Log;”引入android.util.Log包;第8行定義標簽,標簽幫助用戶在LogCat中找到目標程序生成的日志信息,同時也能夠利用標簽對日志進行過濾;第14行記錄一個詳細信息,Log.v()方法的第一個參數是日志的標簽,第二個參數是實際的信息內容;第15~18行分別產生了調試信息、通告信息、警告信息和錯誤信息。
最終運行結果如圖4-15所示,從圖中還可以看出LogCat對不同類型的信息使用了不同的顏色加以區別。

圖4-15 LogCat工程的運行結果
3.添加過濾器
上文中提到LogCat提供了“過濾”功能,下面就來介紹一下LogCat是如何添加過濾器的。
首先,單擊右上角的“+”,在彈出的對話框中填入過濾器的名稱:LogcatFilter,設置過濾條件為“標簽=LOGCAT”即可,操作方法如圖4-16所示。

圖4-16 添加過濾器
經過上述過濾器過濾后,無論什么類型的日志信息,屬于哪一個進程,只要標簽為LogCat,都將顯示在LogcatFilter區域內。LogCat過濾后的輸入結果如圖4-17所示。

圖4-17 LogCat過濾后的輸入結果
4.5.2 DevTools開發調試工具
DevTools是用于調試和測試的工具,它包括了如下所示一系列各種用途的用戶小工具:Development Settings、Exception Browser、Google Login Service、Instrumentation、Media Scanner、Package Browser、Pointer Location、Raw Image Viewer、Running processes和Terminal Emulator。
如圖4-18 所示,為DevTools使用時的界面。由使用時的界面也可以看出其中的各個小工具。

圖4-18 DevTools的使用界面
以下著重講解Dev Tools的一些小工具。
1.Development Settings
Development Settings中包含了程序調試的相關選項,單擊功能前面的選擇框,出現綠色的“對號”表示功能啟用,模擬器會自動保存設置。
圖4-19顯示了Development Settings的運行界面。

圖4-19 Development Settings運行界面
下面就詳細介紹Development Settings中各個選項的含義,如表4-3所示。
表4-3 Development Settings中各選項的含義

2.Pointer Location
Pointer Location是屏幕點位置查看工具,能夠顯示觸摸點的X軸坐標和Y軸坐標,如圖4-20所示。

圖4-20 Pointer Location的使用畫面
3.Running processes
Running processes能夠查看在Android系統中正在運行的進程,并能查看進程的詳細信息,包括進程名稱和進程所調用的程序包。

圖4-21 Andoird模擬器默認情況下運行的進程和com.android.phone進程的詳細信息
4.Terminal Emulator
Terminal Emulator可以打開一個連接底層Linux系統的虛擬終端,但具有的權限較低,且不支持提升權限的su命令。如果需要使用root權限的命令,可以使用ADB工具。
圖4-22是Terminal Emulator運行時的畫面,輸入ls命令,顯示出根目錄下的所有文件夾。

圖4-22 Terminal Emulator運行時的畫面
4.6 本章小結
本章主要介紹了Android系統的進程優先級排序、不同優先級進程之間的變化方式,Android系統的4大基本組件及其用途,Activity的生命周期中各個狀態及狀態間的變化關系、Android應用程序的調試方法和工具。
關鍵知識點測評
1.以下有關Android系統進程優先級的說法,不正確的一個是( )。
A.前臺進程是Android系統中最重要的進程
B.空進程在系統資源緊張時會被首先清除
C.服務進程沒有用戶界面并且在后臺長期運行
D.Android系統中一般存在數量較多的可見進程
2.以下有關Android組件的敘述,正確的一個是( )。
A.Service是Android程序的呈現層
B.BroadcaseReceiver本身包含界面,用于通知用戶接收到重要信息
C.應用程序可以通過ContentProvider訪問其他應用程序的私有數據
D.不是所有的Android組件都具有自己的生命周期
3.以下有關Activity生命周期的描述,不正確的是( )。
A.Activity的狀態之間是可以相互轉換的
B.Activity的全生命周期是從Activity建立到銷毀的全部過程,始于onCreate(),結束于onDestroy()
C.活動生命周期是Activity在屏幕的最上層,并能夠與用戶交互的階段
D.onPause()函數在Android系統因資源不足終止Activity前調用
- The Complete Rust Programming Reference Guide
- Designing Machine Learning Systems with Python
- Python從小白到大牛
- Mastering Python High Performance
- FLL+WRO樂高機器人競賽教程:機械、巡線與PID
- JavaScript 程序設計案例教程
- KnockoutJS Starter
- Learning Laravel's Eloquent
- INSTANT Sinatra Starter
- Android驅動開發權威指南
- UML2面向對象分析與設計(第2版)
- IoT Projects with Bluetooth Low Energy
- 零基礎C#學習筆記
- 現代CPU性能分析與優化
- Java程序設計入門(第2版)