ITEEDU

教程: 练习 3

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

  • 生命周期函数及你的程序如何调用它
  • 维护程序状态的技术

步骤 1

Notepadv3 导入Eclipse. 如果你看到一个关于 AndroidManifest.xml, 的错误,或者一些与Android.zip文件有关的问题, 右击工程然后从弹出的按钮中选择Android Tools(Android工具) > 修正工程属性 . 此练习的起点正是Nodepadv2的结束处。

当前应用程序存在问题 — 点击返回按钮当编辑工作会失败, ,还有其他的一些在编辑过程中产生的问题也会导致编辑内容丢失.

要修正这个问题, 我们需要将创建和编辑记事本的大多数功能(函数)放进 NoteEdit类, 并且为编辑记事本 制定完整的生命周期。

  1. 删除 NoteEdit 里的那些从附加束里分析标题和主题的代码.

    相反(相对于上面的方法), 我们将用 DBHelper 这个类 直接从数据库访问记事本. 我们所需传进 NoteEdit Activity 的是一个 mRowId (但仅仅是我们编辑的时候,如果我们执行的是创建 操作,我们什么也不用传进去).删除这些行:

    String title = extras.getString(NotesDbAdapter.KEY_TITLE);
    String body = extras.getString(NotesDbAdapter.KEY_BODY);
  2. 被传进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 失去焦点并被重启的时候).

  1. 替换当前初始化 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;
            }
            
  2. 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

还是在 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() 方法后留空以添加以下的一些生命周期的方法:

  1. onSaveInstanceState():
        @重写
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putLong(NotesDbAdapter.KEY_ROWID, mRowId);
        }
  2. onPause():
        @重写
        protected void onPause() {
            super.onPause();
            saveState();
        }

    We'll define saveState() next.

  3. 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

现在拿出先前的在Notepadv3onActivityResult() 方法中定义的处理代码

所有的标注检索和更新都发生在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();

注意到 mmNotesCursor 代表着成员域(成员变量), 因此当 使 notesCursor 成为一个局部变量, 要去掉 m. 记得在fillData() 方法中重命名所有的mNotesCursor

运行之! (在Project上单击右键 -> Run As -> Android Application )

解决方案与下一步骤

你可以在从zip file中找到Notepadv3Solution,并在里面查看关于这一练习的解决方案 以对比你自己的.

如果准备好了, 转到 额外教程 练习, 这里你可以用eclipse debugger去查生命周期事件.

回到教程主菜单...