在这章练习中, 你将用生命周期事件回调去存储和获得 应用程序的状态代码. 这个练习描述了:
将 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);
}
在NoteEdit类的顶部为NotesDbAdapter 创建一个类对象:
private NotesDbAdapter mDbHelper;
也在函数onCreate()中为 NotesDbAdapter添加一个实例
(紧接着在super.onCreate() 调用后):
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
在 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没有为它提供.这是一个可用来快速安全使用
值的三元操作符,当然如果没有提供,它会是空。
接下来, 如果我们有mRowId,我们需要在它之上产生相关的域(fields):
populateFields();
这个动作需要在confirmButton.setOnClickListener()前做,
这个方法将在呆会定义。
无需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();
}
});
定义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生命周期所描述的一样:释放和创建资源, 而无需我们担心
. 在那之后, 我们只需从光标处获得标题和主体,并且产生它们的视图元素。
如果你习惯了总是控制着你的应用程序, 你可能会不能明白为何必需要 这些整个生命周期都存在的东西。其实原因是:在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();
}
定义 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 字段,
这样我们就能在将来更新之,而不用再重建一个。 (重建也可能会发生在生命周期事件被触发时).
现在拿出先前的在Notepadv3类 onActivityResult() 方法中定义的处理代码
所有的标注检索和更新都发生在NoteEdit 生命周期内, 因此所有的 onActivityResult()
方法需要做的就是更新数据视图,不需要其他的工作了。
该方法如下:
@重写
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData();
}
因为另外一个类正在做这个工作,这个函数所需的做的就是更新数据.
将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 )
你可以在从zip file中找到Notepadv3Solution,并在里面查看关于这一练习的解决方案
以对比你自己的.
如果准备好了, 转到 额外教程 练习, 这里你可以用eclipse debugger去查生命周期事件.