4.5 内容提供与处理
本节介绍Android四大组件之一的ContentProvider的基本概念和常见用法。首先说明如何使用内容提供器封装数据的外部访问接口;接着阐述如何通过内容解析器在外部查询和修改数据,以及使用内容操作器完成批量数据操作;然后说明内容观察器的应用场合,并演示如何借助内容观察器实现流量校准的功能。
4.5.1 内容提供器ContentProvider
Android号称提供了4大组件,分别是页面Activity、广播Broadcast、服务Service和内容提供器ContentProvider。其中内容提供器是跟数据存取有关的组件,完整的内容组件由内容提供器ContentProvider、内容解析器ContentResolver、内容观察器ContentObserver这三部分组成。
ContentProvider为App存取内部数据提供统一的外部接口,让不同的应用之间得以共享数据。像我们熟知的SQLite操作的是应用自身的内部数据库;文件的上传和下载操作的是后端服务器的文件;而ContentProvider操作的是本设备其他应用的内部数据,是一种中间层次的数据存储形式。
在实际编码中,ContentProvider只是一个服务端的数据存取接口,开发者需要在其基础上实现一个具体类,并重写以下相关数据库管理方法。
- onCreate:创建数据库并获得数据库连接。
- query:查询数据。
- insert:插入数据。
- update:更新数据。
- delete:删除数据。
- getType:获取数据类型。
这些方法看起来是不是很像SQLite?没错,ContentProvider作为中间接口,本身并不直接保存数据,而是通过SQLiteOpenHelper与SQLiteDatabase间接操作底层的SQLite。所以要想使用ContentProvider,首先得实现SQLite的数据表帮助类,然后由ContentProvider封装对外的接口。
下面是使用ContentProvider提供用户信息对外接口的代码:
既然内容提供器是四大组件之一,就得在AndroidManifest.xml中注册它的定义,并开放外部访问权限,注册代码如下:
注册完毕后就完成了服务端App的封装工作,接下来可由其他App进行数据存取。
4.5.2 内容解析器ContentResolver
前面提到了利用ContentProvider实现服务端App的数据封装,如果客户端App想访问对方的内部数据,就要通过内容解析器ContentResolver访问。内容解析器是客户端App操作服务端数据的工具,相对应的内容提供器是服务端的数据接口。要获取ContentResolver对象,在Activity代码中调用getContentResolver方法即可。
ContentResolver提供的方法与ContentProvider是一一对应的,比如query、insert、update、delete、getType等方法,连方法的参数类型都一模一样。其中,最常用的是query函数,调用该函数返回一个游标Cursor对象,这个游标与SQLite的游标是一样的,想必读者早已用得炉火纯青。
下面是query方法的具体参数说明(依顺序排列)。
- uri:Uri类型,可以理解为本次操作的数据表路径。
- projection:字符串数组类型,指定将要查询的字段名称列表。
- selection:字符串类型,指定查询条件。
- selectionArgs:字符串数组类型,指定查询条件中的参数取值列表。
- sortOrder:字符串类型,指定排序条件。
针对前面UserInfoProvider提供的数据接口,下面使用ContentResolver在客户端添加用户信息,代码如下:
下面是使用ContentResolver在客户端查询所有用户信息的代码:
添加用户信息的效果如图4-19所示,一开始服务端的用户表不存在用户记录,客户端使用ContentResolver添加一条记录后,服务端的用户记录数返回1。用户信息的查询明细如图4-20所示,点击页面上的用户记录数量文字,弹出一个对话框,提示当前找到的所有用户的明细数据,包括姓名、年龄、身高、体重等信息。
图4-19 利用内容提供器添加用户信息
图4-20 利用内容解析器查询获得用户信息
在实际开发中,普通App很少会开放数据接口给其他应用访问,作为服务端接口的ContentProvider基本用不到。内容组件能够派上用场的情况往往是App想要访问系统应用的通信数据,比如查看联系人、短信、通话记录,以及对这些通信信息进行增、删、改、查。
下面是使用ContentResolver添加联系人信息的代码片段,此时访问的数据来源变成了系统自带的raw_contacts:
注意上述代码用了4条insert语句,但业务上只添加了一个联系人信息。这样处理有一个问题,就是4个insert操作不在同一个事务中,要是中间某步insert操作失败,那么之前插入成功的记录就无法自动回滚,从而产生垃圾数据。
为了避免这种情况的发生,Android提供了内容操作器ContentProviderOperation进行批量数据的处理,即在一个请求中封装多条记录的修改动作,然后一次性提交给服务端,从而实现在一个事务中完成多条数据的更新操作。即使某条记录处理失败,ContentProviderOperation也能根据事务一致性原则自动回滚本事务已经执行的修改操作。
下面是使用ContentProviderOperation批量添加联系人信息的代码片段:
添加联系人信息的效果如图4-21和图4-22所示。其中,图4-21所示为添加之前的截图,此时联系人个数为157位;图4-22所示为添加成功之后的截图,此时联系人个数为158位。
图4-21 联系人添加之前的界面
图4-22 联系人添加之后的界面
4.5.3 内容观察器ContentObserver
ContentResolver获取数据采用的是主动查询方式,有查询就有数据,没查询就没数据。有时我们不但要获取以往的数据,还要实时获取新增的数据,最常见的业务场景是短信验证码。电商App经常在用户注册或付款时发送验证码短信,为了给用户省事,App通常会监控手机刚收到的验证码数字,并自动填入验证码输入框。这时就用到了内容观察器ContentObserver,给目标内容注册一个观察器,目标内容的数据一旦发生变化,观察器规定好的动作马上触发,从而执行开发者预先定义的代码。
内容观察器的用法与内容提供器类似,也要从ContentObserver派生一个观察器类,然后通过ContentResolver对象调用相应的方法注册或注销观察器。下面是ContentResolver与观察器有关的方法说明。
- registerContentObserver:注册内容观察器。
- unregisterContentObserver:注销内容观察器。
- notifyChange:通知内容观察器发生了数据变化。
为了让读者更好理解,下面举一个实际应用的例子。手机号码的每月流量限额一般由用户手动配置,但流量限额其实是由移动运营商指定的。以中国移动为例,只要发送流量校准短信给运营商客服号码(如发送18到10086),运营商就会给用户发送本月的流量数据,包括月流量额度、已使用流量、未使用流量等信息。手机App只需监控10086发送的短信内容,即可自动获取手机号码的月流量额度,无须用户手工配置。
下面是利用ContentObserver实现流量校准的代码片段:
流量校准的效果如图4-23和图4-24所示。其中,图4-23所示为用户实际收到的短信内容,图4-24所示为App监视短信并解析完成的流量数据页面。
图4-23 用户收到的短信内容
图4-24 内容观察器监视并解析得到的流量信息
总结一下在Content组件经常使用的系统URI,详细的URI取值说明见表4-3。
表4-3 常用的系统URI取值说明