Android的简述4

满堂花醉三千客,一剑霜寒十四洲。这篇文章主要讲述Android的简述4相关的知识,希望能为你提供帮助。
 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);
                }【Android的简述4】第二个来分析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;
                                ContentValues  values  =  new  ContentValues();
                                values.put(Notes.NOTE,  mOriginalContent);
                                getContentResolver().update(mUri,  values,  null,  null);
                        }
                        else  if  (mState  ==  STATE_INSERT) 
                        {
                                //  We  inserted  an  empty  note,  make  sure  to  delete  it
                                deleteNote();
                        }
                }
                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) 
{
              return  null;
}
public  boolean  onCreate() 
{
              return  false;
}
public  Cursor  query(Uri  uri,  String[]  projection,  String  selection,
                      String[]  selectionArgs,  String  sortOrder)
{
              return  null;
}
public  int  update(Uri  uri,  ContentValues  values,  String  selection,
                      String[]  selectionArgs) 
{
              return  0;
}这些都要你自己实现,不同的实现就是对应不同的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"
                                        +  "); ");
                }
                @Override
                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函数的用处了,也正是通过它的解析,才能区分开到底是对全部日志还是对某一条日志进行操作。
public  String  getType(Uri  uri) 
{
                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))  {
                case  NOTES:
                        count  =  db.delete(NOTES_TABLE_NAME,  where,  whereArgs);
                        break;
                case  NOTE_ID:
                        String  noteId  =  uri.getPathSegments().get(1);
                        count  =  db.delete(NOTES_TABLE_NAME,  Notes._ID  +  "="  +  noteId
                                        +  (!TextUtils.isEmpty(where)  ?  "  AND  ("  +  where  +  \')\'  :  ""),  whereArgs);
                        break;
                }最后,一定记得通知上层:其传递下来的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,你可以看到下面这样的画面:

    推荐阅读