Android中的Content provider机制可支持在多个应用中存储和读取数据。这也是跨应用共享数据的唯一方式。在android系统中,没有一个公共的内存区域,供多个应用共享存储数据。

Android提供了一些主要数据类型的Content provider,比如音频、视频、图片和私人通讯录等。可在android.provider包下面找到一些android提供的Content provider。可以获得这些Content provider,查询它们包含的数据,当然前提是已获得适当的读取权限。

如果想公开自己的数据,那么可有两种办法:

创建自己的Content provider,需要继承ContentProvider类;
如果你的数据和已存在的Content provider数据结构一致,可以将数据写到已存在的Content provider中,当然前提是获取写该Content provider的权限。比如把OA中的成员通讯信息加入到系统的联系人Content provider中。

Content provider基础
所有Content provider都需要实现相同的接口用于查询Content provider并返回数据,也包括增加、修改和删除数据。

首先需要获得一个ContentResolver的实例,可通过Activity的成员方法getContentResovler()方法:

ContentResolver cr = getContentResolver();

ContentResolver实例带的方法可实现找到指定的Content provider并获取到Content provider的数据。

ContentResolver的查询过程开始,Android系统将确定查询所需的具体Content provider,确认它是否启动并运行它。android系统负责初始化所有的Content provider,不需要用户自己去创建。实际上,content provider的用户都不可能直接访问到content provider实例,只能通过ContentResolver在中间代理。

数据模型
Content provider展示数据类似一个单个数据库表。其中:

每行有个带唯一值的数字字段,名为_ID,可用于对表中指定记录的定位;
Content provider返回的数据结构,是类似JDBC的ResultSet,在android中,是Cursor对象。
URI
每个content provider定义一个唯一的公开的URI,用于指定到它的数据集。一个content provider可以包含多个数据集(可以看作多张表),这样,就需要有多个URI与每个数据集对应。这些URI要以这样的格式开头:

content://

表示这个uri指定一个content provider。

如果你想创建自己的content provider,最好把自定义的URI设置为类的常量,这样简化别人的调用,并且以后如果更新URI也很容易。android定义了CONTENT_URI常量用于URI,比如:

android.provider.Contacts.Phones.CONTENT_URI android.provider.Contacts.Photos.CONTENT_URI

要注意的是上面例子中的Contacts,已经在android 2.0及以上版本不赞成使用。

查询Content provider
要想使用一个content provider,需要以下信息:

定义这个content provider的URI
返回结果的字段名称
这些字段的数据类型
如果需要查询content provider数据集的特定记录(行),还需要知道该记录的ID的值。

构建查询
查询就是输入URI等参数,其中URI是必须的,其他是可选的,如果系统能找到URI对应的content provider将返回一个Cursor对象。

可以通过ContentResolver.query()或者Activity.managedQuery()方法。两者的方法参数完全一样,查询过程和返回值也是相同的。区别是,通过Activity.managedQuery()方法,不但获取到Cursor对象,而且能够管理Cursor对象的生命周期,比如当Activity暂停(pause)的时候,卸载该Cursor对象,当Activity restart的时候重新查询。另外,也可以对一个没有处于Activity管理的Cursor对象做成被Activity管理的,通过调用Activity.startManaginCursor()方法。

类似这样:

Cursor cur = managedQuery(myPerson, null, null, null, null);

其中第一个参数myPerson是Uri类型实例。

如果需要查询的是指定行的记录,需要用_ID值,比如ID值为23,URI将是类似:

content://. . . ./23

android提供了方便的方法,让开发者不需要自己拼接上面这样的URI,比如类似:

Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);

或者:

Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");

二者的区别是一个接收整数类型的ID值,一个接收字符串类型。

其他几个参数:

names,可以为null,表示取数据集的全部列,或者声明一个String数组,数组中存放列名称,比如:People._ID。一般列名都在该Content provider中有常量对应;
针对返回结果的过滤器,格式类似于SQL中的WHERE子句,区别是不带WHERE关键字,如果返回null表示不过滤,比如name=?;
前面过滤器的参数,是String数组,是针对前面条件中?占位符的值;
排序参数,类似SQL的ORDER BY字句,不过不需要写ORDER BY部分,比如name desc,如果不排序,可输入null。
返回值是Cursor对象,游标位置在第一条记录之前。

下面实例适用于android 2.0及以上版本,从android通讯录中得到姓名字段:

Cursor cursor = getContentResolver().query(         ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null,         null,         null);

返回值的内容

返回值的内容类似上图,不同的content provider会有不同的列和名称,但是会有两个相同的列,上面提到过的一个是_ID,用于唯一标识记录,还有一个_COUNT,用于记录整个结果集的大小,可以看到上面图中的_COUNT的值是相同的。

读取返回的数据
如果在查询的时候使用到ID,那么返回的数据只有一条记录。在其他情况下,一般会有多条记录。和JDBC的ResultSet类似,需要操作游标遍历结果集,在每行,再通过列名获取到列的值,可以通过getString()、getInt()、getFloat()等方法获取值。比如类似下面:

while (cursor.moveToNext()) {     builder             .append(                     cursor                             .getString(cursor                                     .getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)))             .append("-"); }

和JDBC中不同,没有直接通过列名获取列值的方法,只能先列名获取到列的整型索引值,然后再通过该索引值定位获取列的值。

编辑数据
可以通过content provider实现以下编辑功能:

增加新的记录;
在已经存在的记录中增加新的值;
批量更新已经存在的多个记录;
删除记录。
所有的编辑功能都是通过ContentResolver的方法实现。一些Content provider对权限要求更严格一些,需要写的权限,如果没有会报错。

增加记录
要想增加记录到content provider,首先,要在ContentValues对象中设置类似map的键值对,在这里,键的值对应content provider中的列的名字,键值对的值,是对应列希望的类型。然后,调用ContentResolver.insert()方法,传入这个ContentValues对象,和对应Content provider的URI即可。返回值是这个新记录的URI对象。这样你可以通过这个URI获得包含这条记录的Cursor对象。比如:

ContentValues values = new ContentValues();

values.put(People.NAME, "Abraham Lincoln");

Uri uri = getContentResolver().insert(People.CONTENT_URI, values);

在原有记录上增加值
如果记录已经存在,可在记录上增加新的值,或者编辑已经存在的值。

首先要过去到原来的值对象,然后要清除原有的值,然后像上面增加记录一样即可:

Uri uri=Uri.withAppendedPath(People.CONTENT_URI, "23");

Uri phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);

values.clear(); values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE); values.put(People.Phones.NUMBER, "1233214567"); getContentResolver().insert(phoneUri, values);

批量更新值
批量更新一组记录的值,比如NY改名为Eew York。可调用ContenResolver.update()方法。

删除记录
如果是删除单个记录,调用ContentResolver.delete()方法,URI参数,指定到具体行即可。

如果是删除多个记录,调用ContentResolver.delete()方法,URI参数指定Content provider即可,并带一个类似SQL的WHERE子句条件。这里和上面类似,不带WHERE关键字。

创建自己的Content provider
创建content provider,需要:

设置存储系统。大多数content provider使用文件或者SQLite数据库,不过你可以用任何方式存储数据。android提供SQLiteoOpenHelper帮助开发者创建和管理SQLiteDatabase。
继承ContentProvider,提供对数据的访问。
在manifest文件中声明content provider。
继承ContentProvider类
必须定义ContentProvider类的子类,需要实现如下方法:

query() insert() update() delete() getType() onCreate()

query()方法,返回值是Cursor实例,用于迭代请求的数据。Cursor是一个接口。android为该接口提供了一些只读的(和JDBC的ResultSet不一样,后者还提供可写入的可选特性)Cursor实现。比如SQLiteCursor,可迭代SQLite数据库中的数据。可以通过SQLiteDatabase类的query()方法获取到该Cursor实例。还有其他的Cursor实现,比如MatrixCursor,用于数据不是存储在数据库的情况下。

因为Content provider可能被多个ContentResolver对象在不同的进程和线程中调用,因此实现Content provider必须考虑线程安全问题。

作为良好的习惯,在实现编辑数据的代码中,要调用ContentResolver.notifyChange()方法,通知那些监听数据变化的监听器。

在实现子类的时候,还有一些步骤可以简化Content provider客户端的使用:

定义public static final Uri常量,名称为CONTENT_URI:

public static final Uri CONTENT_URI =                Uri.parse("content://com.example.codelab.transportationprovider");

如果有多个表,它们也是使用相同的CONTENT_URI,只是它们的路径部分不同。

也就是说红色框部分是一致的。

定义返回的列名,public static final,列名的值,比如使用SQLite数据库作为存储,对应表的列名。

在文档中要写出各个列的数据类型,便于使用者读取。

如果需要处理新的MIME数据类型,比如通过Intent的方式,并且带data的mimeType(参见总结一下Intent概念),那么需要在ContentProvider.getType()方法中进行处理,参见编写完整的Content provider示例编写一个getType方法部分。

如果处理数据库表中超大的数据,比如很大的位图文件,一般存在文件系统中,可以参照在content provider中使用大型二进制文件,这样第三方的content provider使用者,可以访问不属于它权限的文件,通过content provider做代理。

声明Content Provider
创建Content Provider后,需要在manifest文件中声明,android系统才能知道它,当其他应用需要调用该Content Provider时才能创建或者调用它。

语法类似:

<provider android:name="com.easymorse.cp.MyContentProvider"             android:authorities="com.easymorse.cp.mycp"></provider>

android:name要写ContentProvider继承类的全名。

android:authorities要写和CONTENT_URI常量的B部分(见上面图)。

注意不要把上图C和D部分加到authorities中去。authorities是用来识别ContentProvider的,C和D部分实际上是ContentProvider内部使用的。

在在Content provider实现中使用SQLiteOpenHelper中实现的是特别简单的content provider,方法实现不全。下面给出一个实现比较全面的Content provider,当然也是很简单的。

使用UriMatcher
UriMatcher可以帮助开发者识别Uri。比如:

content://com.easymorse.cp.mycp/emperors

表示获取朝代列表集合。而:

content://com.easymorse.cp.mycp/emperors/1

表示其中的_ID值为1的一个特定的记录。

如果自己写分析Uri的代码,比较繁琐和重复。android提供了方便的工具类UriMatcher。

首先定义两个常量,针对集合与特定的单条记录:

private static final int ITEMS = 1; 

private static final int ITEM = 2;

另外要创建UriMatcher实例:

private static UriMatcher uriMatcher;

static {     uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);     uriMatcher.addURI(PROVIDER_NAME, TABLE_EMPERORS, ITEMS);     uriMatcher.addURI(PROVIDER_NAME, TABLE_EMPERORS + "/#", ITEM); }

静态即可。然后在静态初始化块中把ITEMS和ITEM加进去。这里指定了ITEMS的URI模式是:

content://com.easymorse.cp.mycp/emperors

而ITEM是:

content://com.easymorse.cp.mycp/emperors/#

#号表示一个id值。

UriMatcher如何使用呢?

uriMatcher.match(uri)

如果一个content://com.easymorse.cp.mycp/emperors Uri,该方法将返回1,也就是常量ITEMS的值,以此类推。

编写一个getType方法:

@Override public String getType(Uri uri) {     switch (uriMatcher.match(uri)) {     case ITEMS:         return "vnd.android.cursor.dir/vnd.easymorse.mycp";     case ITEM:         return "vnd.android.cursor.item/vnd.easymorse.mycp";     default:         throw new IllegalArgumentException("Unsupported URI: " + uri);     } }

这个方法在本例中并未起到作用,如果作为intent并使用data(Mimetype)就会用到这个方法。可见总结一下Intent概念的data测试部分。

编写查询方法
查询方法的代码:

@Override     public Cursor query(Uri uri, String[] projection, String selection,             String[] selectionArgs, String sortOrder) {         switch (uriMatcher.match(uri)) {         case ITEMS:             return database.query(TABLE_EMPERORS, projection, selection,                     selectionArgs, null, null, sortOrder);         case ITEM:             return database.query(TABLE_EMPERORS, projection, _ID + "="                     + uri.getPathSegments().get(1), selectionArgs, null, null,                     null);         default:             throw new IllegalArgumentException("unknown uri: " + uri);         }     }

这里有两种可能,通过条件查询,或者通过id查询,前者走ITEMS,后者走ITEM,都是通过Uri判断的,借助UriMatcher。这里:

uri.getPathSegments().get(1),

将取到id号。如果get(0),将取到path的第一个值emperors。

使用该Content provider的代码:

private String getContentProviderValues() {     StringBuilder builder = new StringBuilder(); 

    // 查名称和朝代,朝代=明,而且按照登基时间倒排序     Cursor cursor = managedQuery(MyContentProvider.CONTENT_URI,             new String[] { MyContentProvider.NAME,                     MyContentProvider.DYNASTY }, MyContentProvider.DYNASTY                     + "=?", new String[] { "明" }, " start_year desc"); 

    // 查全部记录     // Cursor cursor = managedQuery(MyContentProvider.CONTENT_URI, null,     // null,     // null, null); 

    // 根据id定位记录(0..1)     // Cursor cursor = managedQuery(ContentUris.withAppendedId(     // MyContentProvider.CONTENT_URI, 1),     // new String[] { MyContentProvider.NAME }, null, null, null); 

    while (cursor.moveToNext()) {         builder                 .append(                         cursor.getString(cursor                                 .getColumnIndex(MyContentProvider.NAME)))                 .append(" | ")                 // .append(                 // cursor                 // .getString(cursor                 // .getColumnIndex(MyContentProvider.START_YEAR)))                 // .append(" | ")                 .append(                         cursor.getString(cursor                                 .getColumnIndex(MyContentProvider.DYNASTY)))                 .append("\n");     }

可以有多种使用方式,本例中屏蔽部分,是其他使用方式。

编写编辑方法
insert()、update()和delete()都属于编辑记录的功能。拿update举例,因为比较复杂一点:

@Override public int update(Uri uri, ContentValues contentValues, String selection,         String[] selectionArgs) {     switch (uriMatcher.match(uri)) {     case ITEM:         return database.update(TABLE_EMPERORS, contentValues,                 _ID + "=" + uri.getPathSegments().get(1) + " and ("                         + selection + ")", selectionArgs);     case ITEMS:         return database.update(TABLE_EMPERORS, contentValues, selection,                 selectionArgs);     default:         throw new IllegalArgumentException("unknown uri: " + uri);     } }

这里复杂的地方是如果根据id做修改,比如还有其他附加条件,需要拼接where子句字符串。后面的and要带括号,否则逻辑可能不同。

使用这个Content provider的做update的示例:

ContentValues values = new ContentValues(); values.put(MyContentProvider.NAME, "朱重八"); getContentResolver().update(MyContentProvider.CONTENT_URI, values,         MyContentProvider.NAME + "=?", new String[] { "朱元璋" });

修改表中是朱元璋的,改为他的小名,朱重八。

如果是delete和insert操作,情况是类似的。

ContentProvider详解及使用大全相关推荐

  1. 【ADB 操作命令详解及用法大全(非常全)】

    ADB操作命令详解及用法大全 一.ADB是什么?   ADB,即 Android Debug Bridge 是一种允许模拟器或已连接的 Android 设备进行通信的命令行工具,它可为各种设备操作提供 ...

  2. ad19pcb设置恢复默认_电脑主板BIOS设置详解BIOS知识大全

    电脑主板BIOS设置详解-BIOS知识大全 什么是电脑BIOS,一般电脑主板都设置了电脑开机后一直按着Del键即可进入BIOS. 系统开机启动 BIOS,即微机的基本输入输出系统(Basic Inpu ...

  3. adc网络语什么意思_王者荣耀adc是什么意思详解 专业术语大全

    王者荣耀adc是什么意思详解 专业术语大全.大家在游戏中经常会碰到一些小术语,比如最常见的ADC.AP以及AD等等,有很多小萌新不知道这些术语是什么意思,下面小编就来详细的为大家说明这个问题. ADC ...

  4. 在计算机中配置了网关作用是什么意思,网关有什么用(网关详解网关作用大全)...

    网关有什么用(网关详解&网关作用大全) 很多小伙伴在学习或看资料的时候,经常会遇到"默认网关"这个概念,但很多小伙伴不知道这个默认网关到底指的是什么,今天小编正好有时间,可 ...

  5. Android面试基础之ContentProvider详解(斗帝养成系列三)

    斗帝养成 斗师,一至九星,斗气纱衣,聚气化液态. 我匆忙了一生,我却留不下任何东西. Android面试基础之Activity详解(斗帝养成系列一) Android面试基础之Service详解(斗帝养 ...

  6. Linux高级专题详解--shell编程大全(shell变量,if语句,case语句,for循环,while循环,函数调用,数组,正则表达式,shell脚本三剑客--grep,sed,awk家族)

    shell编程 初始shell 程序 语言 编程 ---------------------------------- 语言 自然语言:汉语.英语 计算机语言:c语言.c++.(java php py ...

  7. Android之自定义ContentProvider详解

    第一个版本  对android中MIME类型的理解 初始MIME类型,是在学习ContentProvider的时候. 当在创建自己的ContentProvider的时,需要从抽象类ContentPro ...

  8. ContentProvider详解

    本文基于android 12.0 主要讲解的是A进程访问B进程的ContentProvider的流程, 主要涉及到的代码路径: frameworks/base/core/java/android/co ...

  9. Android——ContentProvider详解

    1. 简介 ContentProvider,内容提供者属于Android的四大组件之一 用于进程间 进行数据交互 & 共享,即跨进程通信 原理:使用binder机制(后续再进行介绍 统一资源标 ...

最新文章

  1. SDUT_2116 数据结构实验之链表一:顺序建立链表
  2. sqlite3 表里插入系统时间(时间戳)
  3. 技术如何秒懂你?阿里百万级QPS资源调度系统揭秘
  4. Linux: 系统配置 crond 和 crontab(有图有代码有真相!!!)
  5. 【Elasticsearch】 es watcher 视频 笔记
  6. 主机消息服务器在哪找,怎么找电脑服务器主机地址吗
  7. (宏)Word 仅修改选中图片的尺寸
  8. Yii 2.0 权威指南 (4) 使用 Gii 生成代码
  9. [收藏]判断文本框中的内容必须全为数字
  10. Unable to modify the parent container element before the child element is closed
  11. 高并发大流量,大麦抢票的技术涅槃之路
  12. 图纸打印什么时候用蓝图_为什么工程图纸都是蓝色的?是叫“蓝图”吗?
  13. php 文字动画,CSS3绘制2018新年快乐文字动画特效
  14. oxc000014c解决方法
  15. 计算机网络安全凭据,账户为用户或计算机提供安全凭证,以便用户和计算机能够登录到网络,并拥有响应访 - 百科题库网...
  16. 初识ecshop小京东(1)——认识路由
  17. 【java华为机试】HJ10 字符个数统计
  18. 宝洁网测管道题技巧_宝洁笔试网测小技巧分享
  19. Unity3D_(Shuriken粒子系统)制作简单的烟花爆炸效果
  20. idea中import project是什么意思

热门文章

  1. 使用Python类似pandas的方式读取xml文件
  2. Redis数据持久化(详解+样例)
  3. WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码
  4. Linux arp命令详解及C/C++代码实现
  5. gradle project详解
  6. 黑客如何掩盖 Linux 服务器上的操作痕迹
  7. python-docx 文档翻译
  8. 请问大家如果买笔记本电脑会选择什么牌子的?
  9. FileYee一键备份电脑所有文件内容
  10. 在邮件中自动播放flash