Android实例剖析笔记(四)

上篇文章介绍了Activity的生命周期,并通过一个实验来探索状态转换的机制,然后介绍了应用中使用的一个自定义控件。本文将继续分析NoteEditor这个类和以及Content Provider机制。

NoteEditor深入分析

首先来弄清楚“日志编辑“的状态转换,通过上篇文章的方法来做下面这样一个实验,

首先进入“日志编辑“时会触发onCreate和onResume,然后用户通过Option Menu选择”Edit title”后,会触发onSaveInstanceState和onPause,最后,用户回到编辑界面,则再次触发onResume.

最终通过LogCat可以得到下图:

那么下面就按照上述顺序对此类进行剖析。

首先是onCreate方法,一开始先获取导致进入“日志编辑”界面的intent,分析其操作类型可得知是“编辑日志”还是“新增日志”。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> final Intentintent = getIntent();
// Dosomesetupbasedontheactionbeingperformed.
final Stringaction = intent.getAction();

若是编辑日志,则设置当前状态为编辑,并保存待编辑日志的URI.

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> mState = STATE_EDIT;
mUri
= intent.getData();

若是“新增日志”,则设置当前状态为“新增”,并通过content provider向数据库中新增一个“空白日志”,后者返回“空白日志”的URI.

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> mState = STATE_INSERT;
mUri
= getContentResolver().insert(intent.getData(), null );

然后不管是编辑新增,都需要从数据库中读取日志信息(当然,若是新增,读出来的肯定是空数据)。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> mCursor = managedQuery(mUri,PROJECTION, null , null , null );

最后,类似于web应用中使用的Session,这里也将日志文本保存在InstanceState中,因此,若此activity的实例此前是处于stop状态,则我们可以从它那取出它原本的文本数据.

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> if (savedInstanceState != null )
{
mOriginalContent
= savedInstanceState.getString(ORIGINAL_CONTENT);
}

第二个来分析onResume函数,首先把游标置于第一行(也只有一行)

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> mCursor.moveToFirst();

然后取出“正文”字段,这时有一个比较有趣的技巧,“设置文本”并不是调用setText,而是调用的setTextKeepState,后者相对于前者有一个优点,就是当界面此前stop掉,现在重新resume回来,那么此前光标所在位置仍然得以保存。而若使用setText,则光标会重置到行首。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> Stringnote = mCursor.getString(COLUMN_INDEX_NOTE);
mText.setTextKeepState(note);

最后,将当前编辑的正文保存到一个字符串变量中,用于当activity被暂停时使用。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> if (mOriginalContent == null )
{
mOriginalContent
= note;
}

通过前面的图可以得知,activity被暂停时,首先调用的是onSaveInstanceState函数。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> outState.putString(ORIGINAL_CONTENT,mOriginalContent);

这里就仅仅将当前正编辑的正文保存到InstanceState中(类似于Session).

最后来看onPause函数,这里首先要考虑的是若activity正要关闭,并且编辑区没有正文,则将此日志删除。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> if (isFinishing() && (length == 0 ) && ! mNoteOnly)
{
setResult(RESULT_CANCELED);
deleteNote();
}

否则的话,就更新日志信息

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> ContentValuesvalues = new ContentValues();
if ( ! mNoteOnly)
{
values.put(Notes.MODIFIED_DATE,System.currentTimeMillis());
if (mState == STATE_INSERT)
{
Stringtitle
= 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中,我们再一次看到下面这段熟悉的代码了:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> Intentintent = 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发出请求,由后者完成实际的数据库操作。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> private final void cancelNote()
{
if (mCursor != null )
{
if (mState == STATE_EDIT)
{
// Puttheoriginalnotetextbackintothedatabase
mCursor.close();
mCursor
= null ;
ContentValuesvalues
= new ContentValues();
values.put(Notes.NOTE,mOriginalContent);
getContentResolver().update(mUri,values,
null , null );
}
else if (mState == STATE_INSERT)
{
// Weinsertedanemptynote,makesuretodeleteit
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)返回数据类型

这五个功能分别对应下述五个可以重载的方法:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> public int delete(Uriuri,Stringselection,String[]selectionArgs)
{
return 0 ;
}
public StringgetType(Uriuri)
{
return null ;
}
public Uriinsert(Uriuri,ContentValuesvalues)
{
return null ;
}
public boolean onCreate()
{
return false ;
}
public Cursorquery(Uriuri,String[]projection,Stringselection,
String[]selectionArgs,StringsortOrder)
{
return null ;
}
public int update(Uriuri,ContentValuesvalues,Stringselection,
String[]selectionArgs)
{
return 0 ;
}

这些都要你自己实现,不同的实现就是对应不同的content-provider。但是activity使用content-provider不是直接创建一个对象,然后调用这些具体方法。

而是调用managedQuery,getContentResolver().delete,update等来实现,这些函数其实是先找到符合条件的content-provider,然后再调用具体content-provider的函数来实现,那又是怎么找到content-provider,就是通过uri中的authority来找到content-provider,这些都是通过系统完成,应用程序不用操心,这样就达到了有效地隔离应用和内容提供者的具体实现的目的。

有了以上初步知识后,我们来看NotePadProvider是如何为上层提供数据库层支持的。

下面这三个字段指明了数据库名称,数据库版本,数据表名称。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> private static final StringDATABASE_NAME = " note_pad.db " ;
private static final int DATABASE_VERSION = 2 ;
private static final StringNOTES_TABLE_NAME = " notes " ;

实际的数据库操作其实都是通过一个私有静态类DatabaseHelper实现的,其构造函数负责创建指定名称和版本的数据库,onCreate函数则创建指定名称和各个数据域的数据表(就是简单的建表SQL语句)。onUpgrade负责删除数据表,再重新建表。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> private static class DatabaseHelper extends SQLiteOpenHelper
{
DatabaseHelper(Contextcontext)
{
super (context,DATABASE_NAME, null ,DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabasedb)
{
db.execSQL(
" CREATETABLE " + NOTES_TABLE_NAME + " ( "
+ Notes._ID + " INTEGERPRIMARYKEY, "
+ Notes.TITLE + " TEXT, "
+ Notes.NOTE + " TEXT, "
+ Notes.CREATED_DATE + " INTEGER, "
+ Notes.MODIFIED_DATE + " INTEGER "
+ " ); " );
}
@Override
public void onUpgrade(SQLiteDatabasedb, int oldVersion, int newVersion)
{
Log.w(TAG,
" Upgradingdatabasefromversion " + oldVersion + " to "
+ newVersion + " ,whichwilldestroyallolddata " );
db.execSQL(
" DROPTABLEIFEXISTSnotes " );
onCreate(db);
}
}

Android实例剖析笔记(一)这篇文章中我们已经见识到了getType函数的用处了,也正是通过它的解析,才能区分开到底是对全部日志还是对某一条日志进行操作。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> public StringgetType(Uriuri)
{
switch (sUriMatcher.match(uri))
{
case NOTES:
return Notes.CONTENT_TYPE;
case NOTE_ID:
return Notes.CONTENT_ITEM_TYPE;
default :
throw new IllegalArgumentException( " UnknownURI " + uri);
}
}

上面的sUriMatcher.match是用来检测uri是否能够被处理,而sUriMatcher.match(uri)返回值其实是由下述语句决定的。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(NotePad.AUTHORITY,
" notes " ,NOTES);
sUriMatcher.addURI(NotePad.AUTHORITY,
" notes/# " ,NOTE_ID);

sNotesProjectionMap这个私有字段是用来在上层应用使用的字段和底层数据库字段之间建立映射关系的,当然,这个程序里两处对应的字段都是一样(但并不需要一样)。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> 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);
}

数据库的增,删,改,查操作基本都一样,具体可以参考官方文档,这里就仅仅以删除为例进行说明。

一般可以分为三步来完成,首先打开数据库

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> SQLiteDatabasedb = mOpenHelper.getWritableDatabase();

然后根据URI指向的是日志列表还是某一篇日志,到数据库中执行删除动作

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> switch (sUriMatcher.match(uri)){
case NOTES:
count
= db.delete(NOTES_TABLE_NAME,where,whereArgs);
break ;
case NOTE_ID:
StringnoteId
= uri.getPathSegments().get( 1 );
count
= db.delete(NOTES_TABLE_NAME,Notes._ID + " = " + noteId
+ ( ! TextUtils.isEmpty(where) ? " AND( " + where + ' ) ' : "" ),whereArgs);
break ;
}

最后,一定记得通知上层:其传递下来的URI在底层数据库中已经发生了变化。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> getContext().getContentResolver().notifyChange(uri, null );

NotePad的改进
首先我想指出NotePad的一个bug,其实这个小bug在2月份就有人向官方报告了,参见http://code.google.com/p/android/issues/detail?id=1909。NoteEditor类中的变量mNoteOnly根本就是没有用处的,因为它始终都是false,没有任何变化,所以可以删除掉。

第二点是在NoteEditor类中,有下面这样的语句:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> setResult(RESULT_OK,( new Intent()).setAction(mUri.toString()));
setResult(RESULT_CANCELED);
可到底想展示什么技术呢?实际上并没有完整展现出来,这里我对其进行修改后来指明

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

首先在NotesList类中增加一个变量

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> private static final int REQUEST_INSERT = 100 ; // 请求插入标识符

然后修改onOptionsItemSelected函数如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @Override
public boolean onOptionsItemSelected(MenuItemitem)
{
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。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> protected void onActivityResult( int requestCode, int resultCode,Intentdata)
{
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://phinecos.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但请保留此段声明,并在文章页面明显位置给出原文连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值