Android 4.3 SDK Samples 阅读笔记之二 NotePad (1)_移动开发_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > 移动开发 > Android 4.3 SDK Samples 阅读笔记之二 NotePad (1)

Android 4.3 SDK Samples 阅读笔记之二 NotePad (1)

 2013/10/12 17:23:22  [武汉]胖大海  博客园  我要评论(0)
  • 摘要:一、综述:NotePad是一个简单的便签的示例代码,共有6个class,其中四个为Activity,一个为ContentProvider,一个为普通的数据类。四个Activity类中,有三个是普通的Activity类,一个为LiveFolder使用的Activity类。ContentProvider类,是实际的数据操作者,封装了对数据的增删改查操作。在ContactManager的源码中,已经使用过Android系统自带的ContentProvider类了,而在NotePad中
  • 标签:笔记 SDK android not

class="p0">一、综述:

NotePad是一个简单的便签的示例代码,共有6个class,其中四个为Activity,一个为ContentProvider,一个为普通的数据类。

四个Activity类中,有三个是普通的Activity类,一个为LiveFolder使用的Activity类。

ContentProvider类,是实际的数据操作者,封装了对数据的增删改查操作。在ContactManager的源码中,已经使用过Android系统自带的ContentProvider类了,而在NotePad中,将完成自定义的ContentProvider类。

 这个示例中演示了自定义ContentProvider类的编写、应用程序对剪贴板的操作、ListFolder的编写等知识。

二、阅读笔记:

1.NotesLiveFolder.java源码分析

NotesLiveFolder类继承自Activity,但这里并不是作为普通的Activity类使用的,而是一种特殊的LiveFolder使用的Activity类。

LiveFolder被称为实时文件夹,在主屏上长按,会弹出一个选单,选择选单中的一个项,则会在主屏上创建一个快捷方式,例如,选择了联系人,则会创建一个联系人的快捷方式,点开这个快捷方式,会弹出一个非全屏的文件夹,在文件夹显示所有的联系人,选中某个联系人,会进入一个对该联系人进行操作的界面,这种文件夹,就是所谓的实时文件夹。

由于实时文件夹在安全性方面的问题,已经在Android4.0中被列为不建议使用,同时使用了新的技术替代,并提供了相关的示例代码,在阅读该示例代码时,再进行学习。

在主屏上长按时,Android系统会搜索到所有的注册了android.intent.action.CREATE_LIVE_FOLDER的ActionActivity,用于创建LiveFolder,注册代码位于AndroidMainfest.xml中,具体代码如下图:

 

该段代码位于application标签中。

Activity被创建后使用setResult方法返回结果,随后调用finish,关闭自己,本例中代码如下图:

 

使用setResult返回的结果分为两种:RESULT_CANCELED代表调用失败,RESULT_OK代表调用成功,并附上liveFolderIntent用于构造LiveFolder

决定返回结果的if语句代码如下图:

 

首先获取创建Active的意图,再获取意图中的action,如果action的值等于LiveFolders.ACTION_CREATE_LIVE_FOLDER,则返回RESULT_OKLiveFolder.ACTION_CREATE_LIVE_FOLDER的值为android.intent.action.CREATE_LIVE_FOLDER,正是在AndroidMainfest.xml中注册的action值。也即是说,如果该Activity是因为别的Action触发的,会返回调用失败并立即关闭。

返回RESULT_OK时,同时返回的liveFolderIntent是在代码中创建的一个意图,附加了创建LiveFolder所需要的数据,具体代码逐行解读。

 

首先是调用setData,传入了一个Uri,这个Uri用于打开LiveFolder时查询数据。

 

接着使用putExtra附加了创建的LiveFolder的名称。

 

接着同样附加了创建的LiveFolder的图标。

 

然后附加了LiveFolder中项的显示模式,LiveFolders.DISPLAY_MODE_LIST代表列表显示,如果值为LiveFolder.DISPLAY_MODE_GRID代表网格显示。

 

最后,创建了一个意图,其Action为标准的修改操作,并附加查询的Uri。该意图在点击了LiveFolder中的显示项后触发,第二个参数指示查询得到点击项数据的Uri

至此,创建LiveFolder所需的全部数据全部附加在liveFlderIntent上返回给调用者创建LiveFolder

1.2 NotePad.java源码分析。

在分析NotesLiveFolder.java的类中,碰到两个Uri,其定义便是位于NotePad.java中的内部类Note中。

NotePad类是用于NotePadProvider的辅助类,定义了NotePadProvider中所需要的各种常量,其中一部分常量定义在内部类Notes中。注意Notes从BaseColumns中继承,BaseColumns定义了_ID和_COUNT两个常用字段。

其中定义的常量的意义如下:

NotePad. AUTHORITY  用作NotePadProvider类的标识符

Notes.TABLE_NAME   存储数据的表名

Notes.SCHEME       NotePadProvider类对应Uri的scheme,这个值必须是“content://”

Notes.PATH_NOTES     批量查询的PATH,用于构造批量查询的Uri。

注:这里批量查询的意思为返回结果可能为多条的查询,下同。

Notes.PATH_NOTE_ID  按ID单个查询的PATH,用于构造查询得到单条数据的Uri

注:这里单个查询的意思为返回结果最多为一条的查询,下同。

Notes.NOTE_ID_PATH_POSITION  ID在Uri中PATH中的坐标。

Notes.PATH_LIVE_FOLDER  LiveFolder中批量查询的PATH,用于构造LiveFolder中批量查询的Uri.

Notes.CONTENT_URI    批量查询的Uri

Notes.CONTENT_ID_URI_BASE  用于构造单个查询的Uri的基础,在其后加上ID的值,即可得到查询到对应ID数据的Uri.

Notes.CONTENT_ID_URI_PATTERN  匹配单个查询Uri的字符串,#号代表ID值的占位符。

Notes.LIVE_FOLDER_URI   LiveFolder中批量查询的Uri

同时定义了用于MIME-Type定义的两个名称常量:

Notes.CONTENT_TYPE= "vnd.android.cursor.dir/vnd.google.note"

Notes.CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.note"

其中CONTENT_TYPE用于定义数据记录集合的MIME-Type,CONTENT_ITEM_TYPE定义了数据记录集合的MIME-Type。其值由“/”分为两部分,前面为资源类型,后面为内容的类型。

vnd.android.cursor.dir 命名约定:其含义为游标中包含的是数据集合。

vnd.android.cursor.item 命名约定:其含义为游标中包含的一条数据。

vnd.google.note名称可随意取,用于区别同一资源类型下的不同具体类型。

 

Notes. DEFAULT_SORT_ORDER  定义了默认的排序方式。

 

随后定义了四个字段名称常量,Notes. COLUMN_NAME_TITLE、Notes.COLUMN_NAME_NOTE、Notes.COLUMN_NAME_CREATE_DATE与Notes.COLUMN_NAME_MODIFICATION_DATE。

 

1.3 NotePadProvider.java源码解析

 

NotePadProvider是对便签数据库进行操作的实际类,首先介绍其中的常量定义。

DATABASE_NAME  存储数据的数据库名称

DATABASE_VERSION   数据库设计版本,用于不同版本时的升级操作。

READ_NOTE_NOTE_INDEX NOTE字段在字符串数组READ_NOTE_PROJECTION中的位置。

READ_NOTE_TITLE_INDEX   INDEX字段在字符串数组READ_NOTE_PROJECTION中的位置。

 

NOTES    批量查询Uri的类别值

NOTE_ID   按ID查询的单条结果的Uri的类别值

LIVE_PHONE_NOTES   用于LiveFolder中批量查询的类别值

 

并且定义了两个HashMap和一个String数组。

其中String类型的数组中包含了要返回的三个字段名称的常量定义。

HashMap sNotesProjectionMap为用于NOTES和NOTE_ID类别Uri的字段映射表。

HashMap sLiveFolderProjectionMap 为用于LIVE_PHONE_NOTES类别Uri的字段映射表。

 

随后在静态代码块中使用了UriMatcher对象,注册URI与查询类别的关系,代码如下图:

 

第一行为构造UriMatcher对象,传入的是代表Uri未能匹配到查询类别时的常量值。

第二行注册了URI的路由部分只包括notes字符串时,其查询类别值为NOTES常量。第一个参数即为ContentProvider的识别标志,在NotePad类中定义。

第三行和第四行分别注册了另外两种查询类别的路由匹配,其中#号为ID的占位符。

 

注册多个类别,主要是为了在一个ContentProvider中实现多种查询或查询多个表的功能。在调用添加,删除,修改操作时,会根据传入的Uri来判定查询的类别,然后根据类别可以选择对多个表进行操作,或者对一个表进行不同类型的操作。

 

静态代码块中,还为两个字段映射表赋了值。其中sNotesProjectionMap中的四个字段均保持原名不变,而sLiveFolderProjectionMap中两个字段的名称均作了改变,主要原因是LiveFolder的批量查询要求返回值包括LiveFolder._ID和LiveFolder.NAME字段。_ID字段将用在构建LiveFolder中项被点击后发送的Intent中的Uri,而LiveFolder.Name字段用于项显示时的名称。

LiveFolder还支持以下字段:

LiveFolder.DESCRIPTION 用于项的描述。

LiveFolder.INTENT 用于项选中时发送的意图,如果没有,则发送在SetResult中传递过来的Intent中附加的LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT指定的Intent。

LiveFolder. ICON_BITMAP 用于项的图标。

LiveFolder.ICON_PACKAGE  用于项的图标对应的包。

LiveFolder.ICON_RESOURCE  用于项的图标对应的资源名称。

 

LiveFolder要求必须要有_ID字段和_COUNT字段,以及NAME字段。

以上查询中未出现_COUNT字段的原因,猜测是因为_COUNT字段本身的意义在于返回结果集合中的行数统计,出现在单行数据中没有意义,如确有需要也应该是设计为映射到getCount方法或size方法中。因源码下载尚未成功,只能留待以后考证。

 

为了LiveFolder中的需要,LiveFolder的批量查询语句必须将原有字段名映射成为LiveFolder需要的字段名,代码如下图:

 

此处需要注意的是,映射时如果需要改变返回字段的名称使用的是SQL中取别名的语法 AS。另外,如果要将字段名称做映射,必须将返回的所有字段都做上映射,哪怕其名称没有变化,没变化时也不用加上AS取别名。另外,使用了字段映射为字段改名后,用于选择字段,条件,排序的参数中,要使用映射修改后的名称。

 

在常量定义和静态代码块后,ContentProvider定义了内部类DatabaseHelper助手类,该类继承于抽象类SQLiteOpenHelper,需要实现onCreate和onUpgrade抽象方法。

实例化时传入了操作的数据库名称与版本常量,如果数据库不存在,则会触发onCreate方法,如果传入的数据库版本与创建时的数据库版本不同,则会触发onUpgrade方法。

 

NotePadProvider是ContenProvider的子类,因此必须实现该抽象类中定义的所有抽象方法。

boolean onCreate()    ContentProvider的初始化方法。

Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)  查询方法,Uri可为批量或单条查询,其他参数指定返回的字段,过滤条件,排序方式,返回一个Cursor或者null。

Uri  insert(Uri uri, ContentValues values)  插入数据,并返回一个能查找到新加入的数据的Uri。ContentValues类似于Map类型,将一条数据的字段名与值以键值对的方式保存在其中。猜测这里uri应该也可用批量查询和单条查询的uri,但应该不可以使用重命名,未做实验,稍后再补正。

int  update(Uri uri, ContentValues values, String selection, String[] selectionArgs   修改数据,操作Uri查询得到的结果中满足条件的数据,将ContentValues中的字段名和值覆盖原始数据,返回受影响的行数。

int delete(Uri, String, String[]) 删除数据,Uri可为批量或单条查询,只操作结果集中满足条个的数据,返回受影响的行数。

String getType(Uri)  返回Uri在ContentProvider中定义的查询的结果的MIME-Type,必须遵守MIME-Type的命名约定。

 

在query方法中,使用了SQLiteQueryBuilder类构建查询。

首先使用方法setTables(String name)指定了查询所用的数据表,随后的代码中,使用UriMachter对象的match(Uri uri)方法获得注册的查询类型。根据不同类型,调用setProjectionMap(HashMap<String,String>)传入不同的映射表,对数据字段进行映射。主要解决LiveFolder查询结果要求的字段名称问题。

如果有需要,也可以先取得查询类型,然后再根据类型,调用setTables(String name)设置操作不同的表,达到同一个Provider查询多个表的目标。

然后再使用了DatabaseHelper类的getReadableDatabase()方法打开数据库,注意:打开数据库的方式有getWritableDatabase方法和getReadableDatabase方法,其中getReadableDatabase方法不是以只读的方式打开数据库,而是先调用getWritableDatabase方法尝试以读写方式打开数据库,仅仅在读写方式打开失败后,才会尝试以只读方式打开数据库。

随后创建了Cursor对象,并调用Cursor对象的setNotificationUri()方法,使用该方法后Cursor会观察到指定的Uri中数据的变化,当数据发生变化时,Cursor会调用onChange方法,通知消费Cursor的Adapter对象更新视图。具体原理很复杂,但不需要太关心。

Insert方法、delete方法、update方法都是SQLiteDatabase类的方法的简单使用。

getType(Uri uri)方法,根据传入的uri,返回其查询结果集的MIME-Type类型集合,结果为字符串集合。

另外创建了一个ClipDescription类型的变量NOTE_STREAM _TYPES,ClipDescription类型的变量,一般用于描述剪贴板中数据的描述信息,第一个参数猜测只是该数据类型的描述信息,第二个参数是数据的MIME-Type类型名称的集合,示例中使用代码构造了一个ClipDescription类对象。

 

理解ClipDescription类的作用,必须要对Android系统的剪贴板的工作方式有所了解。在Android系统中,剪贴板由公共的ClipboardManager类的对象实例进行管理,在代码中不要再创建该类的实例,只需要使用代码getSystemService(CLIPBOARD_SERVICE)即可获得对该对象实例的的引用。

放入到剪贴板中的数据,其类型为ClipData类型,每次只能将一个ClipData类型的实例放入剪贴板。ClipData类型中包含数据类型描述和数据本身,从代码角度去看,ClipData中包含一个ClipDescription类的对象以及若干个ClipData.ItemClipData.Item可以是一段文本,也可以是一个Intent或是一个Uri。而ClipDescription是描述了ClipData中包含的ItemMIME-Type

ClipData.ItemIntent时支持复制应用程序的快捷方式。

ClipDate.ItemUri时,ClipboardManager会先调用Uri对应的的ContentProvider对象的getType方法获得查询结果的MIME-Type,。ClipboardManager类会调用Uri对象的ContentProvidergetType方法,获得查询结果的类型,取得类型主要是为了下一步调用openTypedAssetFile方式去获得查询结果时需要类型做为参数。

openTypeAssetFile方法调用了ContentProvider类的openPipeHelper方法,以开启一个新的线程读取游标中的数据,并通过Pipe的转换为流返回给调用者。Pipe是连接两个线程的管道,用于线程间交换数据。

调用openPipeHelper方法原型如下:

 

最后一个参数是实现了PipeDateWriter<T>接口的对象,本例中传入的是this,也即是NotePodProvider对象的实例。在openPipeHelper方法中,调用了传入对象的writeDataToPipe方法,并使用传入的Uri,mimeType,opts,args作为参数。这也是为什么NotePadProvider类要实现PipeDateWrite<Cursor>接口的原因。

代码最终又转回调用了NotePadProvider类的writeDataToPipe方法,在writeDateToPipe方法中,实现了将Cursor中数据输出到流中的代码。

整个NotePadProvider类,向外提供了增加,删除,修改和查询的方法,这是普通Provider类都需要实现的方法。

NotePadProvider为了支持将剪贴板中粘贴NotePadProvider中的数据,新增了openTypedAssetFile供剪贴板管理对象调用。

因为openTypeAssetFile需要数据的MIME-Type集合,故又增加了getStreamTypes方法。

最后,由于openTypeAssetFile中使用了ContentProvider中的openPipeHelper方法,该方法中需要传入一个PipeDataWriter<T>接口的对象,openPipeHelper要调用该接口中定义的writeDataToPipe方法。主要是为了把查询得到的Cursor转换为数据流写入Pipe,供调用者在Pipe另一端接收。将查询得到的Cursor中的数据进行处理,需要处理数据列,最适合的类,就是查询使用的ContentProvider本身。

 

1.4 NoteEditor.java

此类是对便签进行编辑的Activity,内容看似十分非常简单,但实际上有很多实现细节需要详述,稍后继续扒。

未完待续。。。

发表评论
用户名: 匿名