在这章练习中, 你将用生命周期事件回调去存储和获得 应用程序的状态代码. 这个练习描述了:
将 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去查生命周期事件.