文章目录
介绍
ContentProvider 主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使用 ContentProvider 是 Android 实现跨程序共享数据的标准方式
不同于 文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,ContentProvider 可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄露的风险
ContentProvider 的用法一般有两种:一种是使用现有的 ContentProvider 读取和操作相应程序中的数据;另一种是创建自己的 ContentProvider 给程序的数据提供外部访问接口
ContentResolver 的基本用法
知识储备
对于每一个应用程序来说,想要访问 ContentProvider 中共享的数据,就要借助 ContentResolver 类,可以通过 getContentResolver() 方法来获取该类实例。ContentResolver 中提供了一系列方法对数据进行增删改查:insert()添加数据;update()更新数据;delete()删除数据;query()查询数据
接收一个 Uri 参数,被称为 内容 URI。 内容 URI 给 ContentProvider 中的数据建立了唯一标识符,它由 authority 和 path 组成。authority是用于对不同的应用程序做区分的,一般为了避免冲突会采用包名的方式进行命名。例如某个应用的包名是 com.example.app ,那么该应用对应的 authority 可命名为 com.example.app.provider。path则是用于对同一应用程序中不同的表做区分的,通常会添加到 authority 后边。比如某个应用的数据库中有两张表 table1 和 table2,这时可以将 path 分别命名为 /table1 和 /table2,然后把 authority 和 path 进行组合,内容URI 就变成了 com.example.app.provider/table1 和com.example.app.provider/table2 。最后需要在字符串头部加上协议声明。因此内容 URI 最标准的格式为:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
然后需要把它解析成 Uri 对象才可以作为参数传入:
val uri = Uri.parse("content://com.example.app.provider/table1")
现在可以使用这个 Uri 对象查询 table1 表中的数据了:
val cursor = contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder
)
参数说明:
uri:指定查询某个应用程序下的某一张表
projection:指定查询的列名
selection:指定 where 的约束条件
selectionArgs:为 where 中占位符提供具体的值
sortOrder:指定查询结果的排序方式
查询完成后仍是 Cursor 对象,读取方式如下:
while(cursor.moveToNext()){
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.cloase()
增加一条数据可以这么写:
val values = contentValuesOf("column1" to "text","column2" to 1)
contentResolver.insert(uri,values)
如果想要更新这条添加的数据,把 column1 的值清空,可以借助 ContentResolver 的 update() 方法实现:
val values = contentValuesOf("column1" to "")
contentResolver.update(uri,values,"column1 = ? and column2 = ?",arrayOf("text","1"))
注意,上述代码使用了 selection 和 selectionArgs 参数来约束,防止所有行都会受到影响
删除这条数据:
contentResolver.delete(uri,"column2 = ?",arrayOf("1))
接下来我们利用目前所学的知识,读取系统通讯录中的联系人信息
读取系统联系人
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/contactsView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
代码
class TestActivity : AppCompatActivity() {
private val contactsList = ArrayList<String>()
private lateinit var adapter: ArrayAdapter<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList)
contactsView.adapter = adapter
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_CONTACTS
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CONTACTS), 1)
} else {
readContacts()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
1 -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
private fun readContacts() {
contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null
)?.apply {
while (moveToNext()) {
//获取联系人姓名
val displayName =
getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
//获取联系人手机号
val number =
getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
contactsList.add("$displayName\n$number")
}
adapter.notifyDataSetChanged()
close()
}
}
}
AndroidManifest中权限
<uses-permission android:name="android.permission.READ_CONTACTS"/>
运行结果(提前在模拟机中增加一些联系人数据)
创建自己的 ContentProvider
创建 ContentProvider 步骤
想要实现跨程序共享数据的功能,可以新建一个类去继承 ContentProvider 的方式来实现
class MyProvider : ContentProvider() {
/**
* 初始化 ContentProvider 的时候调用,通常在这里完成对数据库的创建和升级等操作
* 返回 true 表示 ContentProvider 初始化成功,false表示失败
*/
override fun onCreate(): Boolean {
return false
}
/**
* 从 ContentProvider 中查询数据
*/
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
TODO("Not yet implemented")
}
/**
* 向 ContentProvider 中添加一条数据
* uri:确定要添加到的表
* values:待添加的数据
* 返回一个用于表示这个新纪录的 URI
*/
override fun insert(uri: Uri, values: ContentValues?): Uri? {
TODO("Not yet implemented")
}
/**
* 更新 ContentProvider 中已有数据
* uri:更新哪个表中的数据
* values:更新数据保存在这个参数
* selection\selectionArgs 来约束更新哪些行
* 返回值:受影响的行
*/
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
TODO("Not yet implemented")
}
/**
* 从 ContentProvider 中删除数据
* 返回值:被删除的行
*/
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
TODO("Not yet implemented")
}
/**
* 根据传入的内容URI返回相应的MIME类型
*/
override fun getType(uri: Uri): String? {
TODO("Not yet implemented")
}
}
回顾一下,一个标准的内容URI写法是这样的:
content://com.example.app.provider/table1这就表示调用方期望访问的是com.example.app这个应用的table1表中的数据
除此之外,我们还可以在这个内容URI的后面加上一个id,如下所示:
content://com.example.app.provider/table1/1,这就表示调用方期望访问的是com.example.app这个应用的table1表中id为 1 的数据
内容URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以 id 结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下:
*:表示匹配任意长度的任意字符。
#:表示匹配任意长度的数字。
所以,一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配table1表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#
接着,我们再借助 UriMatcher 这个类就可以轻松地实现匹配内容 URI 的功能。UriMatcher中提供了一个addURI()方法,这个方法接收3个参数,可以分别把authority、path和一个自定义代码传进去。这样,当调用UriMatcher的match()方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。修改MyProvider中的代码,如下所示:
package com.example.myapplication
import android.content.ContentProvider
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
class MyProvider : ContentProvider() {
private val table1Dir = 0
private val table1Item = 1
private val table2Dir = 2
private val table2Item = 3
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
init {
uriMatcher.addURI("com.example.app.provider","table1",table1Dir)
uriMatcher.addURI("com.example.app.provider","table1/#",table1Item)
uriMatcher.addURI("com.example.app.provider","table2",table2Dir)
uriMatcher.addURI("com.example.app.provider","table2/#",table2Item)
}
/**
* 初始化 ContentProvider 的时候调用,通常在这里完成对数据库的创建和升级等操作
* 返回 true 表示 ContentProvider 初始化成功,false表示失败
*/
override fun onCreate(): Boolean {
return false
}
/**
* 从 ContentProvider 中查询数据
*/
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
when(uriMatcher.match(uri)){
table1Dir -> {
// 查询table1 表中的所有数据
}
table1Item -> {
// 查询table1 表中的单条数据
}
table2Dir -> {
// 查询table2 表中的所有数据
}
table2Item -> {
// 查询table2 表中的单条数据
}
}
return null
}
/**
* 向 ContentProvider 中添加一条数据
* uri:确定要添加到的表
* values:待添加的数据
* 返回一个用于表示这个新纪录的 URI
*/
override fun insert(uri: Uri, values: ContentValues?): Uri? {
TODO("Not yet implemented")
}
/**
* 更新 ContentProvider 中已有数据
* uri:更新哪个表中的数据
* values:更新数据保存在这个参数
* selection\selectionArgs 来约束更新哪些行
* 返回值:受影响的行
*/
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
TODO("Not yet implemented")
}
/**
* 从 ContentProvider 中删除数据
* 返回值:被删除的行
*/
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
TODO("Not yet implemented")
}
/**
* 根据传入的内容URI返回相应的MIME类型
*/
override fun getType(uri: Uri): String? {
TODO("Not yet implemented")
}
}
可以看到,MyProvider中新增了4个整型常量,其中 table1Dir 表示访问table1 表中的所有数据,table1Item 表示访问 table1 表中的单条数据,table2 也是同样的。接着在静态代码块里我们创建了UriMatcher的实例,并调用addURI()方法,将期望匹配的内容 URI 格式传递进去,注意这里传入的路径参数是可以使用通配符的。然后当query()方法被调用的时候,就会通过UriMatcher的match()方法对传入的 Uri 对象进行匹配,如果发现UriMatcher中某个内容 URI 格式成功匹配了该 Uri 对象,则会返回相应的自定义代码,然后我们就可以判断出调用方期望访问的到底是什么数据了
上述代码只是以query()方法为例做了个示范,其实insert()、update()、delete()这几个方法的实现也是差不多的,它们都会携带 Uri 这个参数,然后同样利用UriMatcher的match()方法判断出调用方期望访问的是哪张表,再对该表中的数据进行相应的操作就可以了
除此之外,还有一个方法你会比较陌生,即getType()方法。它是所有的内容提供器都必须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由3部分组成,Android对这3个部分做了如下格式规定
- 必须以vnd开头
- 如果内容 URI 以路径结尾,则后接
android.cursor.dir/;,如果内容 URI 以id结尾,则后接android.cursor.item/。 - 最后接上
vnd.<authority>.<path>
所以,对于content://com.example.app.provider/table1这个内容URI,它所对应的MIME类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于content://com.example.app.provider/table1/1这个内容URI,它所对应的MIME类型就可以写成:
vnd.android.cursor.item/vnd.com.example.app.provider.table1
现在我们可以继续完善 MyProvider 中的内容了,这次来实现getType()方法中的逻辑,代码如下所示:
/**
* 根据传入的内容URI返回相应的MIME类型
*/
override fun getType(uri: Uri): String? = when(uriMatcher.match(uri)){
table1Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
table1Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
else -> null
}
到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用ContentResolver来访问我们程序中的数据。那么前面所提到的,如何才能保证隐私数据不会泄漏出去呢?其实多亏了内容提供器的良好机制,这个问题在不知不觉中已经被解决了。因为所有的 CRUD 操作都一定要匹配到相应的内容 URI 格式才能进行的,而我们当然不可能向 UriMatcher 中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问到,安全问题也就不存在了
实现跨程序数据共享
接下来的操作在 【SQLite数据库存储】创建、升级数据库 这篇文章的项目上继续开发,通过 ContentProvider 来给它加入外部访问接口。打开项目,首先将 MyDatabaseHelper 中使用 Toast 弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用Toast。然后创建一个内容提供
器,右击com.example.myapplication 包→New→Other→Content Provider
这里我们将内容提供器命名为 DatabaseProvider,authority 指定为com.example.myapplication.provider,Exported 属性表示是否允许外部程序访问我们的内容提供器,Enabled 属性表示是否启用这个内容提供器。将两个属性都勾中,点击Finish完成创建
代码如下:
class DatabaseProvider : ContentProvider() {
private val bookDir = 0
private val bookItem = 1
private val categoryDir = 2
private val categoryItem = 3
private val authority = "com.example.myapplication.provider"
private var dbHelper: MyDatabaseHelper? = null
//by lazy 代码块是Kotlin 提供的一种懒加载技术,码块中的代码开始并不会执行,
//只有当 uriMatcher 变量首次被调用的时候才会执行,并且将代码块中最后一行代码的返回值 赋给 uriMatcher
private val uriMatcher by lazy {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(authority, "book", bookDir)
matcher.addURI(authority, "book/#", bookItem)
matcher.addURI(authority, "category", categoryDir)
matcher.addURI(authority, "category/#", categoryItem)
matcher
}
//delete()方法这里仍然是先获取SQLiteDatabase的实例
//然后根据传入的uri参数判断用户想要删除哪张表里的数据
//再调用 SQLiteDatabase的delete()方法进行删除就好了
// 被删除的行数将作为返回值返回
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) =
dbHelper?.let {
// 删除数据
val db = it.writableDatabase
val deletedRows = when (uriMatcher.match(uri)) {
bookDir -> db.delete("Book", selection, selectionArgs)
bookItem -> {
val bookId = uri.pathSegments[1]
db.delete("Book", "id = ?", arrayOf(bookId))
}
categoryDir -> db.delete(
"Category", selection, selectionArgs
)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.delete("Category", "id = ?", arrayOf(categoryId))
}
else -> 0
}
deletedRows
} ?: 0
override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
bookDir ->
"vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book"
bookItem ->
"vnd.android.cursor.item/vnd.com.example.databasetest.provider.book"
categoryDir ->
"vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category"
categoryItem ->
"vnd.android.cursor.item/vnd.com.example.databasetest.provider.category"
else -> null
}
//insert()方法,先获取了SQLiteDatabase的实例
//然后根据传入的Uri参数判断用户想要往哪张表里添加数据
//再调用SQLiteDatabase的insert()方法进行添加就可以了
//注意,insert()方法要求返回一个能够表示这条新增数据的URI
//所以我们还需要调用Uri.parse()方法,将一个内容URI解析成Uri对象
//当然这个内容URI是以新增数据的id结尾的
override fun insert(uri: Uri, values: ContentValues?) = dbHelper?.let {
// 添加数据
val db = it.readableDatabase
val uriReturn = when (uriMatcher.match(uri)) {
bookDir, bookItem -> {
val newBookId = db.insert("Book", null, values)
Uri.parse("content://$authority/book/$newBookId")
}
categoryDir, categoryItem -> {
val newCategoryId = db.insert("Category", null, values)
Uri.parse("content://$authority/category/$newCategoryId")
}
else -> null
}
uriReturn
}
//首先调用了getContext() 方法并借助 ?. 操作符和let 函数判断它的返回值是否为空
//如果为空就使用?:操作符返回false,表示ContentProvider 初始化失败
//如果不为空就执行let 函数中的代码
//在let函数中创建了一个 MyDatabaseHelper的实例,然后返回 true表示ContentProvider初始化成功
override fun onCreate() = context?.let {
dbHelper = MyDatabaseHelper(it, "BookStore", 2)
true
} ?: false
//先获取了SQLiteDatabase的实例,然后根据传入的Uri参数判断用户想要访问哪张表
//再调用 SQLiteDatabase的query()进行查询,并将Cursor对象返回就好了
//注意,当访问单条数据的时候,调用了Uri对象的getPathSegments()方法
//它会将内容URI权限之后的部分以“/”符号进行分割,并把分割后的结果放入一个字符串列表中
//那这个列表的第0个位置存放的就是路径
//第1个位置存放的就是id了
//得到了id之后,再通过selection和selectionArgs参数进行约束,就实现了查询单条数据的功能
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
) = dbHelper?.let {
// 查询数据
val db = it.readableDatabase
val cursor = when (uriMatcher.match(uri)) {
bookDir -> {
db.query("Book", projection, selection, selectionArgs, null, null, sortOrder)
}
bookItem -> {
val bookId = uri.pathSegments[1]
db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
}
categoryDir -> {
db.query("Category", projection, selection, selectionArgs, null, null, sortOrder)
}
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.query(
"Category",
projection,
"id = ?",
arrayOf(categoryId),
null,
null,
sortOrder
)
}
else -> null
}
cursor
}
//update()方法也是先获取SQLiteDatabase的实例
//然后根据传入的Uri参数判断出用户想要更新哪张表里的数据
//再调用SQLiteDatabase的update()方法进行更新就好了
//受影响的行数将作为返回值返回
override fun update(
uri: Uri, values: ContentValues?, selection:
String?, selectionArgs: Array<String>?
) = dbHelper?.let {
// 更新数据
val db = it.writableDatabase
val updatedRows = when (uriMatcher.match(uri)) {
bookDir -> db.update(
"Book", values, selection, selectionArgs
)
bookItem -> {
val bookId = uri.pathSegments[1]
db.update("Book", values, "id = ?", arrayOf(bookId))
}
categoryDir -> db.update(
"Category", values, selection, selectionArgs
)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.update(
"Category", values, "id = ?", arrayOf(categoryId)
)
}
else -> 0
}
updatedRows
} ?: 0
}
ContentProvider一定要在 AndroidManifest.xml 文件中注册才可以使用。由于我们是使用 Android Studio 的快捷方式创建的 ContentProvider,因此注册这一步已经自动完成了。打开 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
......
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<provider
android:name=".DatabaseProvider"
android:authorities="com.example.myapplication.provider"
android:enabled="true"
android:exported="true"></provider>
......
</application>
</manifest>
<application>标签内出现了一个新的标签<provider>,我
们使用它来对 DatabaseProvider 进行注册。android:name属性指定了
DatabaseProvider 的类名,android:authorities属性指定了
DatabaseProvider的authority,而enabled和exported属性则是根据我
们刚才勾选的状态自动生成的,这里表示允许 DatabaseProvider 被其他应
用程序访问
现在这个项目就已经拥有了跨程序共享数据的功能了,我们来试一下。首先需要将程序从模拟器中删除,以防止上一章中产生的遗留数据对我们造成干扰。然后运行一下项目,将程序重新安装在模拟器上
接着关闭这个项目,并创建一个新项目 ProviderTest,我们将通过这个程序去访问 刚才项目中的数据,编写布局文件,修改activity_main.xml中的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/addData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add To Book" />
<Button
android:id="@+id/queryData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Query From Book" />
<Button
android:id="@+id/updateData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Update Book" />
<Button
android:id="@+id/deleteData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Delete From Book" />
</LinearLayout>
布局文件很简单,里面放置了4个按钮,分别用于添加、查询、更新和删除
数据。然后修改 MainActivity 中的代码,如下所示:
class MainActivity : AppCompatActivity() {
var bookId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//添加数据的时候,首先调用了Uri.parse()方法将一个内容URI解析成Uri对象
//然后把要添加的数据都存放到ContentValues对象中
//接着调用ContentResolver的insert()方法执行添加操作就可以了
//注意,insert()方法会返回一个Uri对象,这个对象中包含了新增数据的id
//我们通过getPathSegments()方法将这个id取出,稍后会用到它。
addData.setOnClickListener {
// 添加数据
val uri = Uri.parse("content://com.example.myapplication.provider/book")
val values = contentValuesOf(
"name" to "A Clash of Kings",
"author" to "George Martin",
"pages" to 1040,
"price" to 22.85
)
val newUri = contentResolver.insert(uri, values)
bookId = newUri?.pathSegments?.get(1)
}
//查询数据的时候,同样是调用了Uri.parse()方法将一个内容URI解析成Uri对象
//然后调用ContentResolver的query()方法查询数据
//查询的结果当然还是存放在Cursor对象中
//之后对Cursor进行遍历,从中取出查询结果,并一一打印出来。
queryData.setOnClickListener {
// 查询数据
val uri = Uri.parse("content://com.example.myapplication.provider/book")
contentResolver.query(uri, null, null, null, null)?.apply {
while (moveToNext()) {
val name = getString(getColumnIndex("name"))
val author = getString(getColumnIndex("author"))
val pages = getInt(getColumnIndex("pages"))
val price = getDouble(getColumnIndex("price"))
Log.d("MainActivity", "book name is $name")
Log.d("MainActivity", "book author is $author")
Log.d("MainActivity", "book pages is $pages")
Log.d("MainActivity", "book price is $price")
}
close()
}
}
//更新数据的时候,也是先将内容URI解析成Uri对象,然后把想要更新的数据存放到ContentValues对象中
//再调用ContentResolver的update()方法执行更新操作就可以了
//这里我们为了不想让Book表中的其他行受到影响,在调用Uri.parse()方法时,给内容URI的尾部增加了id
//而这个id正是添加数据时所返回的。这就表示我们只希望更新刚刚添加的那条数据,Book表中的其他行都不会受影响
updateData.setOnClickListener {
// 更新数据
bookId?.let {
val uri =
Uri.parse("content://com.example.myapplication.provider/book/$it")
val values = contentValuesOf(
"name" to "A Storm of Swords",
"pages" to 1216, "price" to 24.05
)
contentResolver.update(uri, values, null, null)
}
}
//删除数据的时候,也是使用同样的方法解析了一个以id结尾的内容URI
//然后调用ContentResolver的delete()方法执行删除操作就可以了
//由于我们在内容URI里指定了一个id,因此只会删掉拥有相应id的那行数据,Book表中的其他数据都不会受影响
deleteData.setOnClickListener {
// 删除数据
bookId?.let {
val uri =
Uri.parse("content://com.example.myapplication.provider/book/$it")
contentResolver.delete(uri, null, null)
}
}
}
}