Android實例剖析筆記(三)
Activity的生命周期
Activity類中有許多onXXX形式的函數可以重載,比如onCreate,onStart,onStop,onPause,那么它們的調用順序到底是如何的呢?下面就通過一個實驗來進行分析。在做這個實驗之前,我們先得知道如何在Android中進行Log輸出的。我們要使用的是android.util.log類,這個類相當的簡單易用,因為它提供的全是一些靜態方法:
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是由我們定義的一個標識,一般可以用“類名_方法名“來定義。要在Eclipse中查看輸出的log信息,需要打開Logcat(WindowàShow ViewàotheràAndroidàLogCat即可打開)
實驗一
我們要做的實驗非常簡單,就是有兩個Activity(我這里分別叫做frmLogin和hello2),t它們各自有一個button,可以從第一個跳到第二個,也可以從第二個跳回到第一個。配置文件AndroidManifest.xml非常簡單,第二個activity并沒有多余的信息需要指定。
<activity android:name=".frmLogin"
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="hello2" android:label="@string/app_name">
</activity>
</application>
第一個activity的代碼如下:
{
private final static String TAG = "FrmLogin";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.v(TAG,"onCreate");
setContentView(R.layout.main);
this.setViewOneCommand();
}
public void setViewOneCommand()
{
Button btn = (Button)findViewById(R.id.btnGo);
btn.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
Intent intent = new Intent();
intent.setClass(frmLogin.this, hello2.class);
startActivity(intent);
finish();
}
});
Button btnExit=(Button)findViewById(R.id.btnExit);
btnExit.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
frmLogin.this.finish();
}
});
}
@Override
protected void onDestroy()
{
super.onDestroy();
Log.v(TAG,"onDestroy");
}
@Override
protected void onPause()
{
super.onPause();
Log.v(TAG,"onPause");
}
@Override
protected void onRestart()
{
super.onRestart();
Log.v(TAG,"onRestart");
}
@Override
protected void onResume()
{
super.onResume();
Log.v(TAG,"onResume");
}
@Override
protected void onStart()
{
super.onStart();
Log.v(TAG,"onStart");
}
@Override
protected void onStop()
{
super.onStop();
Log.v(TAG,"onStop");
}
}
我在每個onXXX方法中都加入了log方法,值得注意的一點是按鈕單擊事件處理函數中,在最后我調用了finish();待會我會將此行注釋掉進行對比實驗。第二個activity的代碼和第一個完全一樣,只是將setClass的兩個參數反一下,這樣就可以簡單地實現在兩個Activity界面中來回切換的功能了。下面開始實驗,第一個實驗室從第一個activity跳到第二個activity(此時第一個關閉),然后從第二個跳回第一個(此時第二個關閉)。運行后觀察LogCat,得到如下畫面:
然后來進行第二個實驗,對代碼進行調整,我們把第一個activity中的finish()注釋掉,從第一個activity跳到第二個(此時第一個沒有關閉),然后第二個直接關閉(則第一個會重新來到前端),結果如圖所示,可以看出調用了FrmLogin的onRestart而不是onStart,因為第一個activity只是stop,而并沒有被destory掉。
前面兩個實驗都很好理解,可第三個實驗就讓我不明白了,過程如下:從第一個activity跳到第二個activity(此時第一個不關閉),然后第二個跳回第一個(此時第二個也不關閉),然后第一個再跳回第二個(此時第一個不關閉),照上面來推斷,應該還是會調用onRestart才對,可實際上它調用的卻是onStart,why???
這里先不討論例子了,來看看官方文檔對Activity生命周期的介紹。
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這一對了,叫foreground lifetime,表達的了是否處于激活狀態的過程。
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的生命,切記的是:如果組件的選擇不當,很有可能系統會殺掉一個正在進行重要工作的進程。
自定義控件
這里主要介紹下“編輯日志”中使用的一個自定義EditText控件,它的效果如下圖:
主要功能就是在文本語句之間繪制分割線。
{
private Rect mRect;
private Paint mPaint;
// we need this constructor for LayoutInflater
public LinedEditText(Context context, AttributeSet attrs)
{
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
@Override
protected void onDraw(Canvas canvas)
{
int count = getLineCount();
Rect r = mRect;
Paint paint = mPaint;
for (int i = 0; i < count; i++)
{
int baseline = getLineBounds(i, r);
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
}
super.onDraw(canvas);
}
}
主要工作就是重載onDraw方法,利用從TextView繼承下來的getLineCount函數獲取文本所占的行數,以及getLineBounds來獲取特定行的基準高度值,而且這個函數第二個參數會返回此行的“外包裝”值。再利用這些值繪制這一行的線條。為了讓界面的View使用自定義的EditText類,必須在配置文件中進行設置
class="com.example.android.notepad.NoteEditor$LinedEditText"
android:id="@+id/note"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/transparent"
android:padding="5dip"
android:scrollbars="vertical"
android:fadingEdge="vertical"
android:gravity="top"
android:textSize="22sp"
android:capitalize="sentences"
/>
這里class="com.example.android.notepad.NoteEditor$LinedEditText"就指明了應當使用自定義的LinedEditText類。