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的简述4的更多相关文章

  1. Android Animation简述

    Android Animation简述 一.动画(Animation)          Android框架提供了两种动画系统:属性动画(Android3.0)和视图动画.同时使用两种动画是可行的,但 ...

  2. 学习笔记-- android动画简述

    android支持三种类型的动画: ·属性动画  一种补间动画,通过在目标对象的任何属性的两个值之间应用赠了变化,可以生成一种动画效果.这种动画可以用来生成各种效果,例如:改变视图的颜色.透明条.淡入 ...

  3. Android字体简述

    Android是一个典型的Linux内核的操作系统.在Android系统中,主要有DroidSans和DroidSerif两大字体阵营,从名字就可以看出来,前者是无衬线字体,后者是衬线字体.具体来说, ...

  4. Android的简述3

    Activity的生命周期 Activity类中有许多onXXX形式的函数可以重载,比如onCreate,onStart,onStop,onPause,那么它们的调用顺序到底是如何的呢?下面就通过一个 ...

  5. Android的简述2

    android提供了三种菜单类型,分别为options menu,context menu,sub menu. options menu就是通过按home键来显示,context menu需要在vie ...

  6. Android的简述

    程序截图 先来简单了解下程序运行的效果 程序入口点  类似于win32程序里的WinMain函数,Android自然也有它的程序入口点.它通过在AndroidManifest.xml文件中配置来指明, ...

  7. Android SDK自带调试优化工具

    Android sdk中自带了一些分析内存,界面调优的非常实用的工具,这对于分析和调试我们的应用十分有帮助,由于我使用的是linux版本的sdk,所以就以linux版本的工具做一个介绍,这些工具的具体 ...

  8. Android网络定位服务定制简述

    Android 添加高德或百度网络定位服务 Android的网络定位服务以第三方的APK方式提供服务,由于在国内Android原生自带的com.google.android.gms服务几乎处于不可用状 ...

  9. Android开发3:Intent、Bundle的使用和ListView的应用 、RelativeLayout(相对布局)简述(简单通讯录的实现)

    前言 啦啦啦~博主又来骚扰大家啦~大家是不是感觉上次的Android开发博文有点长呢~主要是因为博主也是小白,在做实验的过程中查询了很多很多概念,努力去理解每一个知识点,才完成了最终的实验.还有就是随 ...

随机推荐

  1. python合并多个文件

    import os filelist=os.listdir('/root/Music') for item in filelist: print item newfile=open('/root/Mu ...

  2. 第一个SpringBoot测试实例

    1.SpringBoot项目构建:http://start-spring.io   自动化构建SpringBoot项目,保存在本地并解压 2.安装gradle并配置gradle环境 3.配置阿里云ma ...

  3. ZooKeeper 系列(二)—— Zookeeper单机环境和集群环境搭建

    一.单机环境搭建         1.1 下载         1.2 解压         1.3 配置环境变量         1.4 修改配置         1.5 启动         1. ...

  4. 球体的双目视觉定位(matlab,附代码)

    球体的双目视觉定位(matlab,附代码) 标签(空格分隔): 机器视觉 引言 双目视觉定位是我们的一个课程设计,最近刚做完,拿出来与大家分享一下,实验的目的是在拍摄的照片中识别球体,并求出该球体到相 ...

  5. PATA 1011 World Cup Betting (20)

    1011. World Cup Betting (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Wit ...

  6. python 基础学习笔记(2)---字符串功能函数

    **上一篇写到了,基本的数据类型,今天重点来讲一下字符串的功能函数**回顾一下上篇的内容:一.int 整型,在python 3 中与long型合并 可以达到 -9223372036854775808- ...

  7. leetcode的Hot100系列--序

    小白程序猿,练练手,做做题目,分享下经验, 有不对的,还请大家能够指出,多多包涵!谢谢!! 先简单,后复杂,循序渐进,希望能够坚持下来, 大家一起进步~~

  8. 【无线安全实践入门】网络扫描和ARP欺骗

    文中可能存在错误操作或错误理解,望大家不吝指正. 同时也希望可以帮助到想要学习接触此方面.或兴趣使然的你,让你有个大概的印象. !阅前须知! 本文是基于我几年前的一本笔记本,上面记录了我学习网络基础时 ...

  9. 按行读取String类型

    BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(content.getByt ...

  10. Don’t Repeat Yourself

    The Don’t Repeat Yourself (DRY) principle states that duplication in logic should be eliminated via ...