天天看點

Android執行個體剖析筆記(四)

上篇文章介紹了Activity的生命周期,并通過一個實驗來探索狀态轉換的機制,然後介紹了應用中使用的一個自定義控件。本文将繼續分析NoteEditor這個類和以及Content Provider機制。

NoteEditor深入分析

首先來弄清楚“日志編輯“的狀态轉換,通過上篇文章的方法來做下面這樣一個實驗,

首先進入“日志編輯“時會觸發onCreate和onResume,然後使用者通過Option Menu選擇”Edit title”後,會觸發onSaveInstanceState和onPause,最後,使用者回到編輯界面,則再次觸發onResume.

最終通過LogCat可以得到下圖:

那麼下面就按照上述順序對此類進行剖析。

首先是onCreate方法,一開始先擷取導緻進入“日志編輯”界面的intent,分析其操作類型可得知是“編輯日志”還是“新增日志”。

       final Intent intent = getIntent();

        // Do some setup based on the action being performed.

        final String action = intent.getAction();

若是“編輯日志”,則設定目前狀态為“編輯”,并儲存待編輯日志的URI.

             mState = STATE_EDIT;

            mUri = intent.getData();

      若是“新增日志”,則設定目前狀态為“新增”,并通過content provider向資料庫中新增一個“空白日志”,後者傳回“空白日志”的URI.

          mState = STATE_INSERT;

            mUri = getContentResolver().insert(intent.getData(), null);

然後不管是“編輯”或“新增”,都需要從資料庫中讀取日志資訊(當然,若是“新增”,讀出來的肯定是空資料)。

mCursor = managedQuery(mUri, PROJECTION, null, null, null);

最後,類似于web應用中使用的Session,這裡也将日志文本儲存在InstanceState中,是以,若此activity的執行個體此前是處于stop狀态,則我們可以從它那取出它原本的文本資料.

        if (savedInstanceState != null) 

        {

            mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);

        }

第二個來分析onResume函數,首先把遊标置于第一行(也隻有一行)

            mCursor.moveToFirst();

然後取出“正文”字段,這時有一個比較有趣的技巧,“設定文本”并不是調用setText,而是調用的setTextKeepState,後者相對于前者有一個優點,就是當界面此前stop掉,現在重新resume回來,那麼此前光标所在位置仍然得以儲存。而若使用setText,則光标會重置到行首。

             String note = mCursor.getString(COLUMN_INDEX_NOTE);

            mText.setTextKeepState(note);

最後,将目前編輯的正文儲存到一個字元串變量中,用于當activity被暫停時使用。

            if (mOriginalContent == null) 

            {

                mOriginalContent = note;

            }

通過前面的圖可以得知,activity被暫停時,首先調用的是onSaveInstanceState函數。

outState.putString(ORIGINAL_CONTENT, mOriginalContent);

這裡就僅僅将目前正編輯的正文儲存到InstanceState中(類似于Session).

最後來看onPause函數,這裡首先要考慮的是若activity正要關閉,并且編輯區沒有正文,則将此日志删除。

            if (isFinishing() && (length == 0) && !mNoteOnly) 

                setResult(RESULT_CANCELED);

                deleteNote();

            } 

否則的話,就更新日志資訊

複制代碼

                ContentValues values = new ContentValues();

                if (!mNoteOnly) 

                {

                    values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());

                    if (mState == STATE_INSERT)

                    {

                        String title = text.substring(0, Math.min(30, length));

                        if (length > 30) 

                        {

                            int lastSpace = title.lastIndexOf(' ');

                            if (lastSpace > 0) 

                            {

                                title = title.substring(0, lastSpace);

                            }

                        }

                        values.put(Notes.TITLE, title);

                    }

                }

                values.put(Notes.NOTE, text);

                getContentResolver().update(mUri, values, null, null);

在生成Option Menu的函數onCreateOptionsMenu中,我們再一次看到下面這段熟悉的代碼了:

Intent intent = new Intent(null, getIntent().getData());

intent.addCategory(Intent.CATEGORY_ALTERNATIVE);

menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,

new ComponentName(this, NoteEditor.class), null, intent, 0, null);

這種生成動态菜單的機制在Android執行個體剖析筆記(二)這篇文章中已經介紹過了,就不贅述了。

最後,來看下放棄日志和删除日志的實作,由于還沒有接觸到底層的content provider,這裡都是通過getContentResolver()提供的update,delete,insert來向底層的content provider送出請求,由後者完成實際的資料庫操作。

    private final void cancelNote() 

    {

        if (mCursor != null)

            if (mState == STATE_EDIT) 

                // Put the original note text back into the database

                mCursor.close();

                mCursor = null;

                values.put(Notes.NOTE, mOriginalContent);

            else if (mState == STATE_INSERT) 

                // We inserted an empty note, make sure to delete it

        setResult(RESULT_CANCELED);

        finish();

    }

    private final void deleteNote() 

        if (mCursor != null) 

            mCursor.close();

            mCursor = null;

            getContentResolver().delete(mUri, null, null);

            mText.setText("");

剖析NotePadProvider

      NotePadProvider就是所謂的content provider,它繼承自android.content.ContentProvider,也是負責資料庫層的核心類,主要提供五個功能:

1)查詢資料

2)修改資料

3)添加資料

4)删除資料

5)傳回資料類型

這五個功能分别對應下述五個可以重載的方法:

public int delete(Uri uri, String selection, String[] selectionArgs) 

{

       return 0;

}

public String getType(Uri uri) 

       return null;

public Uri insert(Uri uri, ContentValues values) 

public boolean onCreate() 

       return false;

public Cursor query(Uri uri, String[] projection, String selection,

           String[] selectionArgs, String sortOrder)

public int update(Uri uri, ContentValues values, String selection,

           String[] selectionArgs) 

這些都要你自己實作,不同的實作就是對應不同的content-provider。但是activity使用content-provider不是直接建立一個對象,然後調用這些具體方法。

而是調用managedQuery,getContentResolver().delete,update等來實作,這些函數其實是先找到符合條件的content-provider,然後再調用具體content-provider的函數來實作,那又是怎麼找到content-provider,就是通過uri中的authority來找到content-provider,這些都是通過系統完成,應用程式不用操心,這樣就達到了有效地隔離應用和内容提供者的具體實作的目的。

      有了以上初步知識後,我們來看NotePadProvider是如何為上層提供資料庫層支援的。

      下面這三個字段指明了資料庫名稱,資料庫版本,資料表名稱。

private static final String DATABASE_NAME = "note_pad.db";

private static final int DATABASE_VERSION = 2;

private static final String NOTES_TABLE_NAME = "notes";

      實際的資料庫操作其實都是通過一個私有靜态類DatabaseHelper實作的,其構造函數負責建立指定名稱和版本的資料庫,onCreate函數則建立指定名稱和各個資料域的資料表(就是簡單的建表SQL語句)。onUpgrade負責删除資料表,再重建立表。

private static class DatabaseHelper extends SQLiteOpenHelper 

        DatabaseHelper(Context context) 

            super(context, DATABASE_NAME, null, DATABASE_VERSION);

        @Override

        public void onCreate(SQLiteDatabase db) 

            db.execSQL("CREATE TABLE " + NOTES_TABLE_NAME + " ("

                    + Notes._ID + " INTEGER PRIMARY KEY,"

                    + Notes.TITLE + " TEXT,"

                    + Notes.NOTE + " TEXT,"

                    + Notes.CREATED_DATE + " INTEGER,"

                    + Notes.MODIFIED_DATE + " INTEGER"

                    + ");");

        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 

            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "

                    + newVersion + ", which will destroy all old data");

            db.execSQL("DROP TABLE IF EXISTS notes");

            onCreate(db);

在Android執行個體剖析筆記(一)這篇文章中我們已經見識到了getType函數的用處了,也正是通過它的解析,才能區分開到底是對全部日志還是對某一條日志進行操作。

        switch (sUriMatcher.match(uri))

        case NOTES:

            return Notes.CONTENT_TYPE;

        case NOTE_ID:

            return Notes.CONTENT_ITEM_TYPE;

        default:

            throw new IllegalArgumentException("Unknown URI " + uri);

上面的sUriMatcher.match是用來檢測uri是否能夠被處理,而sUriMatcher.match(uri)傳回值其實是由下述語句決定的。

        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES);

        sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID);

sNotesProjectionMap這個私有字段是用來在上層應用使用的字段和底層資料庫字段之間建立映射關系的,當然,這個程式裡兩處對應的字段都是一樣(但并不需要一樣)。

private static HashMap<String, String> sNotesProjectionMap;

    static 

        sNotesProjectionMap = new HashMap<String, String>();

        sNotesProjectionMap.put(Notes._ID, Notes._ID);

        sNotesProjectionMap.put(Notes.TITLE, Notes.TITLE);

        sNotesProjectionMap.put(Notes.NOTE, Notes.NOTE);

        sNotesProjectionMap.put(Notes.CREATED_DATE, Notes.CREATED_DATE);

        sNotesProjectionMap.put(Notes.MODIFIED_DATE, Notes.MODIFIED_DATE);

資料庫的增,删,改,查操作基本都一樣,具體可以參考官方文檔,這裡就僅僅以删除為例進行說明。

一般可以分為三步來完成,首先打開資料庫

        SQLiteDatabase db = mOpenHelper.getWritableDatabase();

然後根據URI指向的是日志清單還是某一篇日志,到資料庫中執行删除動作

    switch (sUriMatcher.match(uri)) {

            count = db.delete(NOTES_TABLE_NAME, where, whereArgs);

            break;

            String noteId = uri.getPathSegments().get(1);

            count = db.delete(NOTES_TABLE_NAME, Notes._ID + "=" + noteId

                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);

最後,一定記得通知上層:其傳遞下來的URI在底層資料庫中已經發生了變化。

        getContext().getContentResolver().notifyChange(uri, null);

對NotePad的改進

    首先我想指出NotePad的一個bug,其實這個小bug在2月份就有人向官方報告了,參見http://code.google.com/p/android/issues/detail?id=1909。NoteEditor類中的變量mNoteOnly根本就是沒有用處的,因為它始終都是false,沒有任何變化,是以可以删除掉。

      第二點是在NoteEditor類中,有下面這樣的語句:

setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));

setResult(RESULT_CANCELED);

可到底想展示什麼技術呢?實際上并沒有完整展現出來,這裡我對其進行修改後來指明

參見http://code.google.com/p/android/issues/detail?id=1671)。

首先在NotesList類中增加一個變量

private static final int REQUEST_INSERT = 100;//請求插入辨別符

然後修改onOptionsItemSelected函數如下:

   @Override

    public boolean onOptionsItemSelected(MenuItem item) 

        switch (item.getItemId())

        case MENU_ITEM_INSERT:

            this.startActivityForResult(new Intent(Intent.ACTION_INSERT, getIntent().getData()), REQUEST_INSERT);

            return true;

        return super.onOptionsItemSelected(item);

     最後重載onActivityResult函數來處理接收到的activity result。

    protected void onActivityResult(int requestCode, int resultCode, Intent data)

        if(requestCode == REQUEST_INSERT)

            if(resultCode==RESULT_OK)

                Log.d(TAG, "OK!!!");

            else if(resultCode==RESULT_CANCELED)

                Log.d(TAG, "CANCELED!!!");

試試,當你在NoteEditor中儲存或放棄日志時,觀察LogCat,你可以看到下面這樣的畫面:

本文轉自Phinecos(洞庭散人)部落格園部落格,原文連結:http://www.cnblogs.com/phinecos/archive/2009/08/27/1555372.html,如需轉載請自行聯系原作者