Android - ContentProvider

官方页面

一、概念

简化访问屏蔽细节提供统一访问接口,只需要利用 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/目录下就直接能访问了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值