在這章練習中, 你將用生命周期事件回調去存儲和獲得應用程序的狀態代碼. 這個練習描述了:
生命周期函數及你的程序如何調用它
維護程序狀態的技術
步驟 1
將 Notepadv3 導入Eclipse. 如果你看到一個關於 androidManifest.XML, 的錯誤,或者一些與android.zip文件有關的問題, 右擊工程然後從彈出的按鈕中選擇
Android Tools(android工具) >
修正工程屬性 . 此練習的起點正是Nodepadv2的結束處。
當前應用程序存在問題 — 點擊返回按鈕當編輯工作會失敗,,還有其他的一些在編輯過程中產生的問題也會導致編輯內容丟失.
要修正這個問題, 我們需要將創建和編輯記事本的大多數功能(函數)放進 NoteEdit類, 並且為編輯記事本制定完整的生命周期。
- 刪除 NoteEdit 裡的那些從附加束裡分析標題和主題的代碼.
相反(相對於上面的方法), 我們將用 DBHelper 這個類 直接從數據庫訪問記事本. 我們所需傳進 NoteEdit Activity 的是一個 mRowId (但僅僅是我們編輯的時候,如果我們執行的是創建 操作,我們什麼也不用傳進去).刪除這些行:
String title = extras.getString(NotesDbAdapter.KEY_TITLE);
String body = extras.getString(NotesDbAdapter.KEY_BODY);
- 被傳進extras Bundle的那些屬性我們也不需要了, 這些屬性只是被我們用來 在UI上編輯body文本的. 所以刪除:
- if (title != null) {
- mTitleText.setText(title);
- }
- if (body != null) {
- mBodyText.setText(body);
}
步驟 2
在NoteEdit類的頂部為NotesDbAdapter 創建一個類對象:
private NotesDbAdapter mDbHelper;
也在函數onCreate()中為 NotesDbAdapter添加一個實例 (緊接著在super.onCreate() 調用後):
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
步驟 3
在 NoteEdit, 我們需要為mRowId檢查
savedInstanceState , 以防編輯含有束中的已保存的狀態, 而這些是我們需要覆蓋掉的狀態 (這會發生在activity 失去焦點並被重啟的時候).
- 替換當前初始化 mRowId的代碼:
- mRowId = null;
- Bundle extras = getIntent().getExtras();
- if (extras != null) {
- mRowId = extras.getLong(NotesDbAdapter.KEY_ROWID);
- }
用以下代碼替換:
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
- 為 savedInstanceState標注空值檢查, 並且仍需從extras Bundle 加載mRowId:如果savedInstanceState沒有為它提供.這是一個可用來快速安全使用 值的三元操作符,當然如果沒有提供,它會是空。
步驟 4
接下來, 如果我們有mRowId,我們需要在它之上產生相關的域(fIElds):
populateFIElds();
這個動作需要在confirmButton.setOnClickListener()前做, 這個方法將在呆會定義。
步驟 5
無需onClick()處理函數裡創建bundle和設置bundle值, Activity 也不再用返回任何附加信息給調用者。 因為我們無需返回值,我們可以用setResult()的簡化版:
public void onClick(View vIEw) {
setResult(RESULT_OK);
finish();
}
使用生命周期函數時,要謹記將更新和方法說明存儲到數據庫。
整個 onCreate() 函數應該如此:
super.onCreate(savedInstanceState);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
setContentVIEw(R.layout.note_edit);
mTitleText = (EditText) findVIEwById(R.id.title);
mBodyText = (EditText) findVIEwById(R.id.body);
Button confirmButton = (Button) findVIEwById(R.id.confirm);
mRowId = savedInstanceState != null ? savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID)
: null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ? extras.getLong(NotesDbAdapter.KEY_ROWID)
: null;
}
populateFIElds();
confirmButton.setOnClickListener(new VIEw.OnClickListener() {
public void onClick(View vIEw) {
setResult(RESULT_OK);
finish();
}
});
步驟 6
定義populateFIElds() 方法.
private void populateFIElds() {
if (mRowId != null) {
Cursor note = mDbHelper.fetchNote(mRowId);
startManagingCursor(note);
mTitleText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)));
mBodyText.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY)));
}
}
該方法用了 NotesDbAdapter.fetchNote() 方法去找到要編輯的說明(標注) , 然後它調用Activity 類的startManagingCursor() 方法 , 這是android提供的一個跟蹤光標生命周期的方法. 然後將按照Activity生命周期所描述的一樣:釋放和創建資源, 而無需我們擔心 . 在那之後, 我們只需從光標處獲得標題和主體,並且產生它們的視圖元素。
步驟 7
為什麼處理生命周期時間非常重要
如果你習慣了總是控制著你的應用程序, 你可能會不能明白為何必需要 這些整個生命周期都存在的東西。其實原因是:在android,是操作系統,而不是你在控制著程序的運 作!
正如我們所看到的,android模型是基於ActivitIEs的相互調用的. 當 一個Activity調用另外一個時,至少當前的Activity被暫時停止掉,在操作系統運行資源不足時, 也有可能兩個都被停掉。如果這種情況真的發生,那麼該Activity必須保存足夠的狀態信息,以期恢復。 最好就是保存它被停掉時的狀態.
android有一個 明確的生命周期. 即使你沒有顯式地將控制權交個一個Activity,它生命周期時間也有可能會發生. 比如說,當有電話打 進你的手機,而當時你手機上某個Activity正在運行,那麼當前運行的Activity將被交換(系統資源控制權轉換) 而電話Activity將運行(占有資源控制權).
還是在 NoteEdit 類, 現在覆蓋這些方法: onSaveInstanceState(), onPause() 及 onResume(). 這些都是已有的onCreate()函數附帶的方法。
當某Activity將被停止掉
有可能在重啟之前被殺掉, android將會調用 onSaveInstanceState() 這將意味著任何有用的狀態信息將被保存,以使該Activity 重啟時能被重新初始化. 這是和 onCreate() 方法對應的, 實際上 被傳進 onCreate()的savedInstanceState 的Bundle 正是你在onSaveInstanceState() 中作為outState(暫失效狀態)創建的bundle.
onPause() 與 onResume() 也是自動調用的方法. onPause() 總是在一個 Activity結束時被調用,即使我們特意調用了finish()。我們將用這個去將當前的標注保存到數據 庫。為了減少被占用的資源,良好做法是在onPause()期間盡量釋放任何能被釋放的資源. 因此我們關掉DbHelper類, 並將它的域設為空,使得它能被適時回收。 另一方面,onResume() 能重建 mDbHelper 實例以使我們能夠使用它。 然後從數據庫中將標注重新讀出,並且產生新的域。
因此,在 populateFIElds() 方法後留空以添加以下的一些生命周期的方法:
- onSaveInstanceState():
- @重寫
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
}
- onPause():
- @重寫
- protected void onPause() {
- super.onPause();
- saveState();
}
We'll define saveState() next.
- onResume():
- @重寫
- protected void onResume() {
- super.onResume();
- populateFIElds();
}
Step 8
定義 saveState() 方法以把數據讀出到數據庫.
private void saveState() {
String title = mTitleText.getText().toString();
String body = mBodyText.getText().toString();
if (mRowId == null) {
long id = mDbHelper.createNote(title, body);
if (id > 0) {
mRowId = id;
}
} else {
mDbHelper.updateNote(mRowId, title, body);
}
}
注意到我們接受了 createNote() 的返回值,如果該值有效,我們將它保存到 mRowId 字段, 這樣我們就能在將來更新之,而不用再重建一個。 (重建也可能會發生在生命周期事件被觸發時).
步驟 9
現在拿出先前的在Notepadv3類 onActivityResult() 方法中定義的處理代碼
所有的標注檢索和更新都發生在NoteEdit 生命周期內, 因此所有的 onActivityResult() 方法需要做的就是更新數據視圖,不需要其他的工作了。 該方法如下:
@重寫
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData();
}
因為另外一個類正在做這個工作,這個函數所需的做的就是更新數據.
步驟 10
將onListItemClick() 方法裡設置標題和主體的代碼移除 (一樣,它們都是無需的, 除了mRowId ):
Cursor c = mNotesCursor;
c.moveToPosition(position);
也移除:
i.putExtra(NotesDbAdapter.KEY_TITLE, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_TITLE)));
i.putExtra(NotesDbAdapter.KEY_BODY, c.getString(
c.getColumnIndex(NotesDbAdapter.KEY_BODY)));
因此剩下的代碼應是:
super.onListItemClick(l, v, position, id);
Intent i = new Intent(this, NoteEdit.class);
i.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult(i, ACTIVITY_EDIT);
現在也可以將mNotesCursor從類中移除, 而在fillData() 方法中用一個局部變量將它 設回:
Cursor notesCursor = mDbHelper.fetchAllNotes();
注意到 m 在 mNotesCursor 代表著成員域(成員變量), 因此當 使 notesCursor 成為一個局部變量, 要去掉 m. 記得在fillData() 方法中重命名所有的mNotesCursor。
運行之! (在Project上單擊右鍵 ->
Run As -> android Application )