ContentProvider基础内容
# 1. ContentProvider简介
如短信应用在编辑短信时,需要添加联系人电话,但是联系人列表的表数据存储在库中;当前的联系人数据表是私有的,短信应用无法获取到。此时,我们中间通过ContentProvider(内容提供者)来获取到联系人列表,短信应用通过ContentProvider来获取;短信如何获取ContentProvider暴露的接口呢?通过ContentResolver(内容解析器) 通过 URI
来访问ContentProvider提供的接口
# 1.1 ContentProvider是什么
进程间 进行数据交互 & 共享,即跨进程通信
- ContentProvider=中间者角色(搬运工),真正存储&操作数据的数据源还是原来存储数据的方式(数据库、文件、xml或网络)
- 数据源可以:数据库(如Sqlite)、文件、XML、网络等等
ContentProvider是Android四大组件之一,将数据库表数据操作暴露给其他应用访问;其他应用需要使用ContentResolver来调用ContentProvider的方法,通过URI进行通信
ContentProvider是一种数据共享机制,它将允许其它应用程序对自身应用程序中的数据执行增删改查操作;
# 1.2 ContentProvider执行原理
# 1.3 ContentProvider相关API
provider对象创建后调用(应用安装完成或手机启动完成)
public abstract boolean onCreate();
查询数据表
public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
插入数据表
public abstract Uri insert(Uri uri, ContentValues values);
删除数据表
public abstract int delete(Uri uri, String selection, String[] selectionArgs);
更新数据表
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
# 2. ContentResolver 内容提供者解析类
统一管理不同 ContentProvider
间的操作
- 即通过
URI
即可操作 不同的ContentProvider
中的数据- 外部进程通过
ContentResolver
类 从而与ContentProvider
类进行交互
# 2.1 为什么不直接访问ContentProvider类
为什么要使用通过ContentResolver
类从而与ContentProvider
类进行交互,而不直接访问ContentProvider
类
- 一般来说,一款应用要使用多个
ContentProvider
,若需要了解每个ContentProvider
的不同实现从而再完成数据交互,操作成本高、难度大 - 所以再
ContentProvider
类上加多了一个ContentResolver
类对所有的ContentProvider
进行统一管理
得到对象
context.getContentResolver()
调用provider进行数据库CRUD操作
insert()
delete()
update()
query()
2
3
4
# 2.2 URI 请求地址数据的类
得到URI对象
Uri ststic parse(String uriString)
标准URI写法,表示访问top.iqqcode.app
这个应用的table表中的数据
content://top.iqqcode.app.provider/table
内容URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以id结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下。
*
:表示匹配任意长度的任意字符#
:表示匹配任意长度的数字
一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*
一个能够匹配table1表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#
# 3. 三个辅助ContentProvide的工具类
# 3.1 UriMatcher-匹配URI的容器
添加合法的URI
void addURI(String anthority, String path, int code)
匹配指定的URI,返回匹配码
int match(Uri uri)
# 3.2 ContentUris-解析URI的工具类
解析URI,得到字段的id
long parseId(Uri contentUri)
添加id到指定的URI中
Uri withAppendedli(Uri contentUri, long id)
# 3.3 ContentObserver类
观察 Uri
引起 ContentProvider
中的数据变化 & 通知外界(即访问该数据访问者)
当ContentProvider
中的数据发生变化(增、删 & 改)时,就会触发该 ContentObserver
类
具体使用
// 步骤1:注册内容观察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通过ContentResolver类进行注册,并指定需要观察的URI
// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知访问者
}
}
// 步骤3:解除观察者
getContentResolver().unregisterContentObserver(uri);
// 同样需要通过ContentResolver类进行解除
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4. 使用Provider
1. 编写Contentprovider子类,继承ContentProvider
<-- 4个核心方法 -->
// 外部进程向 ContentProvider 中添加数据
public Uri insert(Uri uri, ContentValues values)
// 外部进程 删除 ContentProvider 中的数据
public int delete(Uri uri, String selection, String[] selectionArgs)
// 外部进程更新 ContentProvider 中的数据
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
// 外部应用 获取 ContentProvider 中的数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
2
3
4
5
6
7
8
9
10
11
12
上述4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)
存在多线程并发访问,需要实现线程同步
- a. 若ContentProvider的数据存储方式是使用SQLite,则不需要;因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步
- b. 若ContentProvider的数据存储方式是内存,则需要自己实现线程同步
// ContentProvider创建后 或 打开系统后其它进程第一次访问该ContentProvider时 由系统进行调用
// 注:运行在ContentProvider进程的主线程,故不能做耗时操作
public boolean onCreate()
// 得到数据类型,即返回当前 Url 所代表数据的MIME类型
public String getType(Uri uri)
2
3
4
5
6
query方法参数说明
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
// TODO
}
2
3
4
5
【查询参数对照说明】
query()方法参数 | 对应SQL部分 | 描述 |
---|---|---|
uri | from table_name | 指定查询某个应用程序下的某一张表 |
projection | select column1, column2 | 指定查询的列名 |
selection | where column = value | 指定where 的约束条件 |
selectionArgs | - | 为where 中的占位符提供具体的值 |
sortOrder | order by column1, column2 | 指定查询结果的排序方式 |
SQL语句
selece id,name,age from table where id = ? and name = ? order by ...
String[] projection
对应 select 后面查询的字段
String[] selectionArgs
对应 where 后面的条件 id、name
等字段放入数组
order by
对应 sortOrder 为排序规则
2. manifest中注册
- 创建在
<application>
节点下添加<provider>
子节点; - 配置
android:name
属性,指定ContentProvider类; - 配置
android:authorities
属性,指定用于访问数据的URI的host部分; - 配置
android:exported
属性,指定值为true
<provider
android:name="PersonProvider"
android:authorities="top.iqqcode.contentproviderbasic.PersonProvider"
<!-- exported是否可被其他应用访问 -->
android:exported="true" />
2
3
4
5
# 5.1 进程内通信
【步骤说明】
- 创建数据库类
- 自定义
ContentProvider
类 - 注册 创建的
ContentProvider
类 - 进程内访问
ContentProvider
的数据
Demo app02-进程间通信
# 5.2 进程内通信
本文需要创建2个进程,即创建两个工程,作用如下
# 进程 1
使用步骤如下:
- 创建数据库类
- 自定义
ContentProvider
类 - 注册创建的
ContentProvider
类
# 进程2
步骤1:声明可访问的权限