Android的简述4

摘要:
NoteEditor首先进行深入分析,以澄清“日志编辑”的状态转换。以下实验是通过上一篇文章中的方法进行的。首先,当进入“日志编辑”时,将触发onCreate和onResume。然后,用户通过OptionMenu选择“Edittitle”后,将触发onSaveInstanceState和onPause。最后,当用户返回编辑界面时,将再次触发onResume。最后,通过LogCat可以获得下图:

 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》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇仓颉输入法学习PL/SQL Developer使用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android小技巧(二):为ContentProvider添加数据库事务支持

转载请注明出处。博客地址:http://blog.csdn.net/mylzc 介绍:数据库事务是由一组数据库操作序列组成,事务作为一个整体被执行。 事务的原子性:包含在其中的对数据库的操作序列最终要么全部执行,要么全部不执行。当全部执行时,事务对数据库的修改将生效;当全部不执行时,数据库维持原有的状态,不会被修改。 问题:最近在做一个从sdcard导入...

MySQL升级方法一

1、在目标服务器上安装新版本的mysql数据库。 1)解压缩新版mysql数据库压缩包并cp到指定目录: [root@faspdev mnt]# tar -zxvf mysql-5.6.24-linux-glibc2.5-x86_64.tar.gz [root@faspdev local]# mv /mnt/mysql-5.6.24-linux-glib...

64/32位oracle客户端安装配置详细教程

如何连接远程oracle数据库? 之前的文章我们讲过linux程序员第一天需要配置scrt工具和ue工具,如果公司使用数据库产品的话,我们还需要了解如何去连接远程数据,接下来我们来介绍如何连接远程服务器上的oracle数据,以linux主机上的数据库为例. 首先来了解一下连接数据库具备的条件: 远程主机有oracle,并且已经启动. 本机装有oracle...

10条技巧优化数据库速度

  大多数网站的内容都存在数据库里,用户通过请求来访问内容。数据库非常的快,有许多技巧能让你优化数据库的速度,使你不浪费服务器的资源。在这篇文章中,我收录了十个优化数据库速度的技巧。   1、小心设计数据库   第一个技巧也许看来理所当然,但事实上大部分数据库的问题都来自于设计不好的数据库结构。   譬如我曾经遇见过将客户端信息和支付信息储存在同一个数据库...

ThinkPHP框架使用笔记

SQL日志问题 THINK_VERSION : 5.0.13 SQL访问日志,默认在debug模式下才会打印出来。 测试发现: 浏览器访问、数据库调试模式 debug为false日志不会打印SQL日志,为true会打印日志 命令行访问、数据库调试模式debug对打印SQL日志没有影响都会打印出来。可以设置全局debug模式为false,则不会打印sql日志...

[转]C#从MySQL数据库中读取

实现了数据库的建表、存储数据的功能后,还需要实现数据库的读取,综合查资料后发现有两种发发比较好; 一、如需要界面操作,需要将数据表格在界面上显示出来的话,需要使用DataGrid控件。 基本操作流程:声明一个数据适配器和数据集,并将读取指令赋值给数据适配器的指令,打开数据库,执行数据适配器的指令,然后将适配器读取的数据填充到数据集中,最后将数据集通过DAT...