一、概念
不同APP间进行数据共享的组件。ContentProvider 为存储和读取数据提供了统一的接口,其它APP通过 ContentResolver 来访问 ContentProvider 提供的数据,而 ContentResolver 通过 URI 来定位自己要访问的数据。
| 简化访问 | 屏蔽细节提供统一访问接口,只需要利用 URI 来访问,简化了应用间数据共享的操作(多为数据库)。 |
| 安全访问 | 数据很重要,提供给其它APP访问(外部访问)但不能随便操作,暴露的是自己定义好的操作方式。 |
| 运行在主线程 | ContentProvider的 onCreate() 运行在主线程,而 query()、insert()、delete()、update()是运行在子线程中,调用这些函数并不会阻塞 ContentProvider 所在进程的主线程,但是可能会阻塞调用者所在进程的主线程(系统让调用线程等待异步操作完成),因此需要在子线程中访问ContentProvider。 |
1.1 统一资源标识符 Uri
通过 URI 提供唯一标识供外部访问自己,协议(scheme)固定为 content。详见
//转换成URI,编译器推荐使用KTX方式
//val uri = Uri.parse("content://com.example.yy/book")
val uri = "content://com.example.yy/book".toUri()
1.2 UriMatcher 工具类
先用来注册自定义的 URI,就可以用来查询外部传入的 URI 是否能处理(是不是自己注册过的)。
| addURI() 注册Uri | public void addURI(String authority, String path, int code) 将自己定义的 URI 进行注册。其中参数authority与path组成一个URI,参数code是自定义的匹配码,用来区分不同的操作。 |
| match() 查询Uri是否注册过 | public int match(Uri uri) 对外部传入的 URI 进行匹配查询,确保传来的 URI 是注册过的,是该 ContentProvider 可以处理的。返回的是注册该 URI 时定义的匹配码,通过对匹配码的判断作出对应的处理,如果查找不到对应的 URI 会返回 -1。 |
val AUTHORITY = "com.jomurphys.myapp" //xml中定义的那个
val PATH = "table" //
val CODE_A = 1 //不同的 code 用来区分不同的操作
val CODE_B = 2
//初始化传入的常量 NO_MATCH 是匹配失败返回的CODE(值为-1),意味着不匹配任何路径的返回码
val matcher = UriMatcher(UriMatcher.NO_MATCH)
//注册需要的URI
matcher.addURI(AUTHORITY, PATH, CODE_A)
matcher.addURI(AUTHORITY, PATH, CODE_B)
//查询外部传来的URI进行不同的操作
val code = matcher.match(uri)
when (code) {
CODE_A ->
CODE_B ->
else ->
}
1.3 ContentUris 工具类
用来操作 URI 的 ID 部分。
| withAppendedId( ) | public static Uri withAppendedId(Uri contentUri, long id) 添加ID |
| removeId( ) | public static Uri removeId(Uri contentUri) 移除ID |
| parseId( ) | public static long parseId(Uri contentUri) 获取ID |
val oldUri1 = "www.jomurphys.myapp:8080/23/44".toUri()
val oldUri2 = "www.jomurphys.myapp8080/23/44".toUri() //去掉冒号
val newUri1 = ContentUris.withAppendedId(uri1, 123) //打印:www.jomurphys.myapp:/123
val newUri2 = ContentUris.withAppendedId(uri2, 123) //打印:www.jomurphys.myapp8080/23/44/123
val newUri3 = ContentUris.removeId(uri1) //报错
val newUri4 = ContentUris.removeId(uri2) //打印:/www.jomurphys.myapp8080/23
1.4 ContentValues
用于将数据封装成符合 SQLite 数据库要求的格式。其实就是一个 Map,key只能是 String 类型,value只能是基本数据类型,不能存储自定义对象。
//创建方式一
val values1 = contentValuesOf(
"a" to 1,
"b" to 2
)
//创建方式二
val values2 = ContentValues().apply {
put("a", 1)
put("b", 2)
}
//获取元素,获取不到返回null
val element1 = values1.get("a") //类型Any
val element2 = values1.getAsInteger("b") //类型Integer
二、ContentProvider 提供数据
2.1 Manifest 中注册
四大组件都需要在Manifest中注册。系统提供的 ContentProvider 位于 android.provider 包中。
<provider
android:authorities="com.jomurphys.myapp" //Uri标识
android:name=".MyContentProvide" //指定实现类的名称
android:exported="true" //是否对外暴露,为true其他APP就能访问到
/>
2.2 自定义ContentProvider
重写的函数其实就是实现自定义的CRUD操作,在必要的时候通知注册该 URI 的监听者共享数据发生了变化。
| 初始化 | public abstract boolean onCreate() 外部第一次访问就会调用,用来初始化对外提供的数据(运行在主线程不要做耗时操作) |
| 增 | public abstract @Nullable Uri insert( @NonNull Uri uri, @Nullable ContentValues values ) 返回新条目的 URI 或 null。 |
| 删 | public abstract int delete( @NonNull Uri uri, @Nullable String selection, ); 返回受影响的行数。 |
| 改 | public abstract int update( @NonNull Uri uri, @Nullable ContentValues values, @Nullable String[] selectionArgs ); 返回受影响的行数。 |
| 查 | public abstract @Nullable Cursor query( @NonNull Uri uri, //要查询的 ContentProvider 的 URI。 @Nullable String[] projection, //要查询的字段(列Column),用 null 表示返回所有字段内容。 @Nullable String[] selectionArgs, //如果 selection 里有?符号这里可以以实际值代替。没有的话可以为null。 ); 返回游标或null。 |
| 数据类型 | public abstract String getType(Uri uri) 返回当前 URI 对应数据的 MIME 类型或 null。详见 |
class MyContentProvider : ContentProvider() {
//定义匹配码
private val MATCH_CODE_A = 1
private val MATCH_CODE_B = 2
//定义Uri各部分
private val AUTHORITY = "com.jomurphys.myapp" //在xml中注册的那个
private val PATH = "table"
//初始化并配置UriMatcher
private val uriMacher by lazy {
//传入的常量 NO_MATCH 是匹配失败返回的CODE(值为-1),意味着不匹配任何路径的返回码
UriMatcher(UriMatcher.NO_MATCH).apply {
//添加我们需要注册的Uri,#为通配符
addURI(AUTHORITY, PATH, MATCH_CODE_A)
addURI(AUTHORITY, PATH + "/#", MATCH_CODE_B)
}
}
//数据更改后用来通知的
private val NOTIFY_URI = "content://$AUTHORITY$PATH/自己提供的数据库表名".toUri()
//外部第一次访问就会调用,用来初始化对外提供的数据(数据库)
override fun onCreate(): Boolean {
val db = DemoDatabase.getInstance(getContext())
val dao = db.demoDao()
}
override fun query(
uri: Uri, projection: Array<out String?>?,
selection: String?,
selectionArgs: Array<out String?>?,
sortOrder: String?
): Cursor? {
//根据传入的Uri获取匹配的CODE
val code = uriMacher.match(uri)
//判断匹配的是哪个CODE,并做相应查询
//返回数据库的 Cursor 或 null
return when(code) {
MATCH_CODE_A -> null
MATCH_CODE_B -> null
else -> null //都不匹配返回null
}
}
override fun getType(uri: Uri): String? {
val code = uriMacher.match(uri)
//返回该 URI 对应数据的 MIME 类型
return when(code) {
MATCH_CODE_A -> "audio/mp3"
MATCH_CODE_B -> "image/*"
else -> null //都不匹配返回null
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val code = uriMacher.match(uri)
//返回新条目的 URI 或 null
return when(code) {
MATCH_CODE_A -> {
//通知所用注册该URI的观察者,该URI共享的数据发生了变化
context?.contentResolver?.notifyChange(NOTIFY_URI, null) //观察者这里用null代替了
null
}
MATCH_CODE_B -> null
else -> null //都不匹配返回null
}
}
override fun delete(
uri: Uri,
selection: String?,
selectionArgs: Array<out String?>?
): Int {
val code = uriMacher.match(uri)
//返回受影响的行数
return when(code) {
MATCH_CODE_A -> 0
MATCH_CODE_B -> 0
else -> 0
}
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String?>?
): Int {
val code = uriMacher.match(uri)
//返回受影响的行数
return when(code) {
MATCH_CODE_A -> 0
MATCH_CODE_B -> 0
else -> 0
}
}
}
四、ContentResolver 获取数据
4.1 调用 ContentProvider
外部进程使用 ContentResolver 通过 URI 即可访问不同的 ContentProvider 中的数据。提供了与 ContentProvider 中同名的 CRUD 四个方法,
| 增 | public Uri insert(Uri uri, ContentValues values) |
| 删 | public int delete(Uri uri, String selection, String[] selectionArgs) |
| 改 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) |
| 查 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) |
//可通过在所有继承Context的类中 通过调用getContentResolver()来获得
val contentResolver = contentResolver
val uri = Uri.parse("content://com.jomurphys/myapp:8080/demo")
//查
val cursor = contentResolver.query(uri, null, null, null)
if (cursor != null && cursor.count > 0) {
while (cursor.moveToNext()) {
}
cursor.close()
}
//增
val contentValues = ContentValues().apply {
put("name", "Zhangsan")
put("age", 18)
}
contentResolver.insert(uri, contentValues)
//删
contentResolver.delete(uri, null, null)
4.2 处理文件 Uri
很多 API 会返回文件的 Uri,可以转成流进行处理。
| getType() | public final @Nullable String getType(@NonNull Uri url) 判断 Uri 文件的类型(如 image/jpeg),不是文件返回 null。 |
| openInputStream() | public final @Nullable InputStream openInputStream(@NonNull Uri uri) throws FileNotFoundException 通过 Uri 打开输入流。 |
| openOutputStream() | public final @Nullable OutputStream openOutputStream(@NonNull Uri uri) throws FileNotFoundException 通过 Uri 打开输出流。 |
五、ContentObserver 监听数据变化
数据发生变化后,通知外部访问者。
5.1 自定义内容观察者
//自定义内容观察者
class MyObserve(val handler: Handler) : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
//更新界面
val message = Message.obtain()
message.obj = uri
handler.sendMessage(message)
}
}
5.2 在 ContentProvider 中发出提醒
class MyContentProvider : ContentProvider() {
override fun insert(uri: Uri, values: ContentValues?): Uri? {
//数据发生变化后,通知外部访问者
context?.contentResolver?.notifyChange(uri, null) //观察者这里用null代替了
return null
}
}
5.3 注册解绑监听
val handler = Handler(mainLooper) { false }
val myObserve = MyObserve(handler)
val contentResolver = contentResolver
val uri = Uri.parse("content://com.jomurphys.myapp/demo")
//注册监听
contentResolver.notifyChange(uri, myObserve)
//记得在生命周期中注销监听
contentResolver.unregisterContentObserver(myObserve)
六、补充
访问 assert 资源目录下的数据库
用代码将数据库复制到/data/data/packagename/databases/目录下就直接能访问了。
本文介绍了Android中ContentProvider组件,它是不同APP间数据共享的组件,为存储和读取数据提供统一接口。详细阐述了ContentProvider提供数据的方式,包括在Manifest中注册、使用Uri等,还介绍了ContentResolver获取数据以及ContentObserver监听数据变化的方法。
1800

被折叠的 条评论
为什么被折叠?



