它的优点?
ContentProvider 为存储和获取数据提供统一的接口,可以在不同应用程序之间共享数据。
ContentProvider 主要有以下优点:
-
提供了对底层数据存储方式的抽象。使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效。如底层可以采用
SQLite
方式存储数据,使用ContentProvider
封装之后,即便底层换成XML
存储也不会对上层应用代码产生影响。如一开始数据存储方式 采用 SQLite 数据库,后来把数据库换成 MongoDB,也不会对上层数据ContentProvider使用代码产生影响。 -
为应用间的数据交互提供了一个安全的环境。它准许你把自己的应用数据根据需求开放给其它应用进行操作,而不用担心直接开放数据库权限而带来的安全问题。
进程间 进行数据交互 & 共享,即跨进程通信。ContentProvider 底层是采用 Android 中的 Binder 机制。
ContentProvider
, ContentResolver
, ContentObserver
之间的关系
- ContentProvider
内容提供者,主要用于对外提供数据
- ContentResolver
内容解析者,用于获取内容提供者提供的数据
ContentValues values = new ContentValues();
values.put("id", 1);
values.put("name", name);
ContentResolver resolver = getContentResolver();
// 通过ContentResolver 向ContentProvider中插入数据
resolver.insert(uri, values);
- ContentObserver
注册
ContentObserver
监听Uri
数据的变化,类似于数据库技术中的触发器(Trigger),当ContentObserver
所观察的Uri
发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObsever
也分为表ContentObserver
、行ContentObserver
,当然这是与它所监听的Uri MIME Type
有关的。
URI
外界进程通过 URI 找到对应 ContentProvider 中的数据,再进行数据操作。
// 表示 匹配 provider 的任何内容
content://com.example.app.provider/*
// 表示 匹配 provider 中的 table 表中的所有行
content://com.example.app.provider/table/#
MIME 数据类型
指定某个文件用某种应用程序来打开
// 根据 URI 返回 MIME 类型
ContentProvider.geType(uri) ;
// MIME类型 = 类型 + 子类型,它是 一个 包含2部分的字符串
// 类型 = text、子类型 = html
text / html
单条记录:
// 若一个Uri如下
content://com.example.transportationprovider/trains/122
// 则ContentProvider会通过ContentProvider.geType(url)返回以下MIME类型
vnd.android.cursor.item/vnd.example.rail
多条记录:
// 若一个Uri如下
content://com.example.transportationprovider/trains
// 则ContentProvider会通过ContentProvider.geType(url)返回以下MIME类型
vnd.android.cursor.dir/vnd.example.rail
Android为常见的数据(如通讯录、日程表等)提供了内置默认的 ContentProvider
但也可根据需求自定义 ContentProvide ,但是必须重写它的 insert / delete / update / query / onCreate / getType
等6个方法。
组织数据方式
ContentProvider主要以表格的形式组织数据,同时也支持文件数据,只是表格形式用得比较多,等同数据库。
注意:
- ContentProvider 的增删改查操作(外部进程调用),运行在 ContentProvider 进程的
Binder 线程池中(不是主线程)
。
// 由系统进行调用,运行在 ContentProvider 进程的主线程,故不能做耗时操作
public boolean onCreate() {...}
-
存在多线程并发访问,需要实现线程同步
-
若 ContentProvider 的数据存储方式是使用 SQLite(单个) ,则不需要,因为 SQLite 内部实现好了线程同步,若是使用多个 SQLite 则需要,因为 SQL 对象之间无法进行线程同步。
-
若 ContentProvider 的数据存储方式是内存,则需要自己实现线程同步
-
ContentProvider 类并不会直接与外部进程交互,而是通过 ContentResolver 类
进程内通信
// android:exported -> false 只能被自己所在的应用使用
<provider
android:name=".MyContentProvider"
android:authorities="com.wwe.myprovider"
android:enabled="false"
android:exported="false" />
进程间进行数据共享
// 进程 A 创建 ContentProvider 存储数据利用 SQLite
<provider
android:name=".MyProvider"
android:authorities="com.wwe.PROVIDER"
android:permission="com.wwe.PROVIDER.ipc"
android:enabled="true"
android:exported="true">
</provider>
// 暴露权限
<permission
android:name="com.wwe.PROVIDER.ipc"
android:label="provider pomission"
android:protectionLevel="normal"/>
// 进程/应用 B 要访问进程/应用 A 的数据
<uses-permission android:name="com.wwe.PROVIDER.ipc" />
// 进程 B 获取进程 A 的数据
private void getDataByContentResolver() {
uri_user = Uri.parse("content://com.wjn.mycontentprovider/user");
ContentResolver resolver = getContentResolver();
if (null != resolver) {
Cursor cursor = resolver.query(uri_user, new String[]{"id", "name", "describe"}, null, null, null);
StringBuilder sbBuilder = new StringBuilder();
if (null != cursor) {
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndex("id"));
sbBuilder.append("ID:" + id + "\n");
String name = cursor.getString(cursor.getColumnIndex("name"));
sbBuilder.append("姓名:" + name + "\n");
String describe = cursor.getString(cursor.getColumnIndex("describe"));
sbBuilder.append("描述:" + describe + "\n\n\n");
}
cursor.close();
}
}
}
外部进程通过 ContentResolver类与 ContentProvider 进行交互,ContentResolver 类对所有的 ContentProvider 进行统一管理。
像
ContentUris
,UriMatcher
,ContentObserver
等核心类,自行上网了解一下即可,再次不做讲解。
如果只允许应用 B 获取应用 A 的数据不允许修改的话。该如何做呢?
其实以上
ContentProvider
还可以将访问的权限进一步细化,分成允许读取和允许写入两种。
当ContentProvider
设置了读取的权限,那么其他组件想读取到该ContentProvider
的内容时,就必须声明使用读的权限。
当ContentProvider
设置了写入的权限,那么其他组件想写入该ContentProvider
的内容时,就必须声明使用写的权限。
ContentProvider 的实现原理?
ContentProvider 核心机制之一也是 Binder,当数据量比较大的时候,继续用Parcel 做容器效率会比较低,因此它还使用了匿名共享内存的方式。
ContentProvider 发布者和调用者这两在 Framework 层是如何实现的。
网友评论