摘要:分析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;
                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,你可以看到下面这样的画面:

Android实例剖析笔记(四)相关推荐

  1. Android实例剖析笔记(二)

    上篇文章分析了NotesList这个Activity,并着重剖析了其中的intent机制,本文将继续上篇未完的工作,以NotesList为实例介绍Android的菜单机制(尤其是动态菜单机制). 简介 ...

  2. 【转】Android实例剖析笔记(二)--用实例讲解Andriod的开发过程,以NotesList为实例介绍Android的菜单机制...

    原文网址:http://kb.cnblogs.com/page/78304/ 简介 android提供了三种菜单类型,分别为options menu,context menu,sub menu. op ...

  3. android VectorDrawable使用笔记(四)

    (4)动态VectorDrawable兼容性问题以及对应的处理 向下兼容问题 Path Morphing ---路径变换动画,在Android pre-L版本下是无法使用 Path Interpola ...

  4. Android群英传笔记——第四章:ListView使用技巧

    Android群英传笔记--第四章:ListView使用技巧 近期也是比較迷茫.可是有一点点还是要坚持的,就是学习了.近期离职了,今天也是继续温习第四章ListView,也拖了事实上也挺久的了,lis ...

  5. Android实训案例(四)——关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程!...

    Android实训案例(四)--关于Game,2048方块的设计,逻辑,实现,编写.加上色彩.分数等深度剖析开发过程! 关于2048,我看到非常多大神,比方医生.郭神.所以我也研究了一段时间.还好是研 ...

  6. Android实训案例(四)——关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程!

    Android实训案例(四)--关于Game,2048方块的设计,逻辑,实现,编写,加上色彩,分数等深度剖析开发过程! 关于2048,我看到很多大神,比如医生,郭神,所以我也研究了一段时间,还好是研究 ...

  7. 查询优化器内核剖析第四篇:从一个实例看执行计划

    查询优化器内核剖析第四篇:从一个实例看执行计划 系列文章索引: 查询优化器内核剖析第一篇 查询优化器内核剖析第二篇:产生候选执行计划&执行计划成本估算 查询优化器内核剖析第三篇:查询的执行与计 ...

  8. setup.s 总结——Linux-0.11 剖析笔记(四)

    上一篇文章 setup.s 解读--Linux-0.11 剖析笔记(三) 详细地解释了 setup.s 的代码,整个代码完成的任务有: 获取一些参数保存在 0x90000 处 保存光标的位置 获取从 ...

  9. 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高...

    第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...

最新文章

  1. OTA升级flash分区
  2. 第二层EtherChannel
  3. 图解命令行手动编译构建一个win32汇编程序
  4. 【C#】分享一个弹出容器层,像右键菜单那样召即来挥则去
  5. [转]了解ASP.NET MVC几种ActionResult的本质:EmptyResult ContentResult
  6. oem监控mysql_OEM12c 安装配置MySQL Plug-in用来监控MySQL
  7. 2013二级计算机c语言50,2013年计算机二级C语言课后模拟题五及答案
  8. zabbix数据库分表的实现
  9. 消息已读未读的模型设计_构建一个即时消息应用(三):对话 | Linux 中国
  10. Python基础——字典(dictionary)
  11. Linux Semaphore
  12. ApplePay 支付开发
  13. Java代理模式实现与原理详解(一)
  14. APP微信登录后端PHP,PHP开发微信授权登录教程
  15. ping命令结果中如何显示时间
  16. css before 箭头,伪元素beforeafter以及制作三角箭头
  17. android通知栏的点击事件,Android监听消息通知栏点击事件
  18. Linux c线程间的同步----互斥锁、条件变量、信号量
  19. 在线生成二维码(微信)很好用
  20. 用幂律分布研究工资收入

热门文章

  1. mathematica趣题解答
  2. Exchange 2013/2016动态通讯组成员查询
  3. 磁盘占用百分百?Windows 10必做各项优化
  4. SQLServer代理新建或者编辑作业报错
  5. 暴力枚举 UVA 725 Division
  6. 华为静态、默认、备用路由配置
  7. ecshop商城首页怎样设置广告ALT标签
  8. Delphi 与 DirectX 之 DelphiX(77): TDIB.Distort();
  9. 电脑经典的小技巧48条
  10. 日本上市公司提供BTC, BCH, ETH担保的贷款