文章目录
介绍
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)
}
}
}
}