一、概念
简化访问 | 屏蔽细节提供统一访问接口,只需要利用 URI 来访问,简化了应用间数据共享的操作(多为数据库)。 |
安全访问 | 数据很重要,提供给外部访问但不能随便操作,暴露的是自己定义好的操作方式。 |
运行在主线程 | ContentProvider的 onCreate() 运行在主线程,而 query()、insert()、delete()、update()是运行在子线程中,调用这些函数并不会阻塞 ContentProvider 所在进程的主线程,但是可能会阻塞调用者所在进程的主线程(系统让调用线程等待异步操作完成),因此需要在子线程中访问ContentProvider。 |
二、提供数据 ContentProvider
四大组件都需要在Manifest中注册。系统提供的 ContentProvider 位于 android.provider 包中。
2.1 Manifest 中注册
<provider
android:authorities="com.jomurphys.myapp" //Uri标识
android:name=".MyContentProvide" //指定实现类的名称
android:exported="true" //是否对外暴露,为true其他APP就能访问到
/>
2.2 统一资源标识符 Uri
通过 URI(Universal Resource Identifier 统一资源标识符)提供唯一标识供外部访问自己。
content://com.jomurphys.myapp:8080/tablename/id | |
scheme | 协议,说明这是一个 ContentProvider 控制的数据。(content) |
authority | 唯一标识,授权者名称。(com.jomurphys.myapp) |
port | 端口。(8080) |
path | 资源路径,请求的是哪个资源(数据库表名)。(tablename) |
id | 数据编号,具体指定符合条件的资源。(id) |
//解析Uri
val uri = Uri.parse("content://com.jomurphys.myapp:8080/tablename/id")
//Uri各部分都可以通过代码拿到
uri.scheme
uri.authority
2.3 UriMatcher
对外部传入的 Uri 进行匹配,确保传来的 Uri 是 ContentProvider 可以处理的。
addURI( ) | public void addURI(String authority, String path, int code) 用于向UriMatcher注册Uri,其中参数authority与path组成一个Uri,参数code是该Uri对应的自定义匹配码。 |
match( ) | public int match(Uri uri) 根据前面注册的Uri返回其对应的标识码,如果在UriMatcher中找不到对应的Uri则返回-1。 |
2.4 ContentUris
用来拼接 Uri 字符串用。
withAppendedId( ) | public static @NonNull Uri withAppendedId(@NonNull Uri contentUri, long id) 添加ID |
removeId( ) | public static @NonNull Uri removeId(@NonNull Uri contentUri) 移除ID |
parseId( ) | public static long parseId(@NonNull Uri contentUri) 获取ID |
val uri = Uri.parse("www.jomurphys.myapp:8080/demo")
val newUri = ContentUris.withAppendedId(uri, 123)//www.jomurphys.myapp:8080/demo/123
val id = ContentUris.parseId(newUri) //123
2.5 自定义ContentProvider
重写的函数其实就是实现自定义的数据库CRUD操作,在必要的时候通知注册该Uri的监听者共享数据发生了变化。
初始化 | public abstract boolean onCreate(); 外部第一次访问就会调用,用来初始化对外提供的数据。 |
增 | 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); |
查 | public abstract Cursor query( Uri uri, //要查询的 ContentProvider 的 Uri String[] projection, //要查询的字段(列Column),用 null 表示返回所有字段内容。 String selection, //查询条件,相当于SQL语句中的where,用 null 表示不进行筛选。 String[] selectionArgs, //如果 selection 里有?符号这里可以以实际值代替。没有的话可以为null。 String sortOrder //对结果进行排序,相当于SQL语句中的Order by,升序 asc /降序 desc,null为默认排序。 ) |
public abstract String getType(Uri uri); |
class MyContentProvider : ContentProvider() {
//定义匹配成功的CODE
private val MATCH_CODE_A = 1
private val MATCH_CODE_B = 2
//定义Uri各部分,也就是在Manifest中定义的那个authorities
private val HOST = "com.jomurphys.myapp"
private val PATH = "table"
//初始化并配置UriMatcher
private val uriMacher by lazy {
//传入的参数是匹配失败返回的CODE(值为-1)
UriMatcher(UriMatcher.NO_MATCH).apply {
//添加我们需要匹配的Uri,匹配成功就返回对应的CODE,#为通配符
addURI(HOST, PATH, MATCH_CODE_A)
addURI(HOST, PATH + "/#", MATCH_CODE_B)
}
}
//数据更改后用来通知的
private val NOTIFY_URI = Uri.parse("content://$HOST$PATH/自己提供的数据库表名")
//外部第一次访问就会调用,用来初始化对外提供的数据
override fun onCreate(): Boolean {
TODO("Not yet implemented")
}
//查
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
//根据传入的Uri获取匹配的CODE
val match = uriMacher.match(uri)
//判断匹配的是哪个CODE,并做相应查询
if (match == MATCH_CODE_A) {
//查询完返回数据库的Cursor对象
} else if (match == MATCH_CODE_B) {
}
//都不匹配返回null
return null
}
override fun getType(uri: Uri): String? {
TODO("Uri都不匹配就return null")
}
//增
override fun insert(uri: Uri, values: ContentValues?): Uri? {
//根据传入的Uri回过去匹配的CODE
val match = uriMacher.match(uri)
//判断匹配的是哪个CODE,并做相应处理
if (match == MATCH_CODE_A) {
//通知所用注册该Uri的监听者,该Uri共享的数据发生了变化
context?.contentResolver?.notifyChange(NOTIFY_URI, null)
}
//都不匹配返回null
return null
}
//删
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
TODO("Uri都不匹配就return 0")
}
//改
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
TODO("Uri都不匹配就return 0")
}
}
四、获取数据 ContentResolver
val handler = Handler(mainLooper) { false }
val myObserve = MyObserve(handler)
val contentResolver = contentResolver
val uri = Uri.parse("content://com.jomurphys/myapp:8080/demo")
//(可选)注册监听用来更新界面
contentResolver.registerContentObserver(uri, true, myObserve)
//查
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)
//记得在生命周期中注销观察
contentResolver.unregisterContentObserver(myObserve)
//自定义内容观察者
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)
}
}
访问 assert 资源目录下的数据库
用代码将数据库复制到/data/data/packagename/databases/目录下就直接能访问了。