android中的四大组件之一----内容提供者
用于不同app之间的数据共享,比如我在A应用程序中有个数据库,而应用B需要用到这个数据库的某个表信息,由于权限问题我们是无法直接操作的,我们就可以在A应用中用ContentProvider来提供这个表的查询接口,这样也就做到了app之间的数据共享,还能让用户自定义接口提高安全性,避免了进程间调用,权限和开发成本问题。
先来复习一下Uri
Uri 通用资源标志符,Web上可用的每种资源 -HTML文档、图像、视频片段、程序等可由一个Uri进行定位
构由以下几个部分组成
scheme、authority、path、query和fragment组成
[scheme:][//host:port][path][?query][#fragment]——最详细的划分形式
以这种形式的字符串转换成Uri即可通过Uri类中的方法来获取各个部分,而ContentProvider就是用Uri来标识访问的是那个应用程序的那个表的哪个操作,如:content://com.example.databasetest.provider/book/2
,android提供了一个UriMatch类进行Uri的匹配,这样其他应用程序就不能为所欲为的进行操作了
使用
接下来我们开始使用
这个例子我们用LitePal来进行数据库操作
创建model:
class Book : LitePalSupport() {
var author: String? = null;
var price: Int? = null;
var pages: Int? = null;
var name: String? = null;
}
class Category : LitePalSupport() {
var category_name : String? = null;
var category_code : Int? = null;
}
配置文件
<litepal>
<!--配置数据库名-->
<dbname value="demo"></dbname>
<!--配置版本-->
<version value="1"></version>
<!--加入模型类声明-->
<list>
<mapping class="com.example.contentprovider.Book"></mapping>
<mapping class="com.example.contentprovider.Category"></mapping>
</list>
</litepal>
然后先添加了几个数据方便后面使用
val connector = Connector()
book.author="Elizabeth3"
book.name="Learn3"
book.pages=190
book.price=100
println("haha${book.save()}")
创建内容提供者:
生成自定义内容提供者,可见实现几个方法都是针对于数据库操作的
class MyContentProvider : ContentProvider() {
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
TODO("Implement this to handle requests to delete one or more rows")
}
override fun getType(uri: Uri): String? {
TODO(
"Implement this to handle requests for the MIME type of the data" +
"at the given URI"
)
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
TODO("Implement this to handle requests to insert a new row.")
}
override fun onCreate(): Boolean {
TODO("Implement this to initialize your content provider on startup.")
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
TODO("Implement this to handle query requests from clients.")
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
TODO("Implement this to handle requests to update one or more rows.")
}
}
清单文件自动注册
<provider
android:name=".MyContentProvider"
android:authorities="con.example.contentprovider"
android:enabled="true"
android:exported="true"></provider>
内容提供者是根据Uri进行匹配的,可见在实现的增删改查方法上都传入了一个Uri对象,这个对象时其他应用程序在使用内容提供者的某个方法时传入的uri,因此我们得自己定义过滤条件防止滥操作:
companion object{
val QUERY=1
val DELETE=2
val INSERT=3
val UPDATE=4
val AUTHORITY = "con.example.contentprovider"
var mUriMatcher:UriMatcher? = null
init {
//定义匹配器 参数指定匹配不成功时的返回值
mUriMatcher = UriMatcher(UriMatcher.NO_MATCH)
//添加规则 第一个参数是刚刚配置的authorities,第二个参数不固定可自定义,第三个参数表示匹配成功的返回值
mUriMatcher?.addURI(AUTHORITY, "queryAll", QUERY)
}
}
重写query方法,在里边进行Uri的匹配过滤
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
//先进行匹配,决定要走query方法的哪个逻辑
val match = mUriMatcher?.match(uri)
var cusor: Cursor? = null
if (match == QUERY){
cusor = LitePal.findBySQL("select * from book")
}else{
throw Exception("路径不匹配")
}
return cusor
}
应用程序二:
fun danxuan(view: View) {
//定义Uri
val uri = Uri.parse("content://con.example.contentprovider/queryAll")
//定义内容解析者, 指定了执行内容提供者的query方法, 参数对应于内容提供者中对应方法的参数
val cusor = contentResolver.query(uri, null, null, null, null)
while (cusor!!.moveToNext()){
val author= cusor.getString(1)
val price = cusor.getInt(2)
val pages = cusor.getInt(3)
val name = cusor.getString(4)
println("${author}-${price}-${pages}-${name}")
}
}
Uri的其他用法:
*
表示匹配任意长度的任意字符
#
表示匹配任意长度的数字
实例:
添加uri匹配:
mUriMatcher?.addURI(AUTHORITY, "queryAll/*", QUERYBYNAME)
query方法:
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
//先进行匹配
val match = mUriMatcher?.match(uri)
var cusor: Cursor? = null
if (match == QUERY){
cusor = LitePal.findBySQL("select * from book")
} else if (match == QUERYBYNAME){
val s = "select * from book where name like \"${uri.pathSegments[1]}\""
cusor = LitePal.findBySQL(s)
}
else{
throw Exception("路径不匹配")
}
return cusor
}
应用二中内容解析者传入的uri:
val uri = Uri.parse("content://con.example.contentprovider/queryAll/Learn2")
内容观察者
用于监听内容提供者的修改
需要在清单文件添加权限:
<uses-permission android:name="android.permission.READ_SMS" />
inner class MyContentObserver(handler: Handler?) : ContentObserver(handler) {
//当观察的内容发生改变的时候调用
override fun onChange(selfChange: Boolean) {
Toast.makeText(this@MainActivity, "数据库变化", Toast.LENGTH_SHORT).show()
super.onChange(selfChange)
}
}
//参数true代表要查询uri及其子集
contentResolver.registerContentObserver(uri, true, MyContentObserver(Handler()))
记得在内容提供者对应中操作完成后进行通知:
//第二个参数为null不知道具体观察者,所以会通知所有观察者
context.contentResolver.notifyChange(uri, null)