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的Action的Activity,用于创建LiveFolder,注册代码位于AndroidMainfest.xml中,具体代码如下图:
该段代码位于application标签中。
该Activity被创建后使用setResult方法返回结果,随后调用finish,关闭自己,本例中代码如下图:
使用setResult返回的结果分为两种:RESULT_CANCELED代表调用失败,RESULT_OK代表调用成功,并附上liveFolderIntent用于构造LiveFolder。
决定返回结果的if语句代码如下图:
首先获取创建Active的意图,再获取意图中的action,如果action的值等于LiveFolders.ACTION_CREATE_LIVE_FOLDER,则返回RESULT_OK。LiveFolder.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.Item,ClipData.Item可以是一段文本,也可以是一个Intent或是一个Uri。而ClipDescription是描述了ClipData中包含的Item的MIME-Type。
ClipData.Item为Intent时支持复制应用程序的快捷方式。
ClipDate.Item为Uri时,ClipboardManager会先调用Uri对应的的ContentProvider对象的getType方法获得查询结果的MIME-Type,。ClipboardManager类会调用Uri对象的ContentProvider的getType方法,获得查询结果的类型,取得类型主要是为了下一步调用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,内容看似十分非常简单,但实际上有很多实现细节需要详述,稍后继续扒。
未完待续。。。