一、简介
ContentProvider主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的 机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使 用ContentProvider是Android实现跨程序共享数据的标准方式。ContentProvider 可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
1.ContentResolver的基本用法
对于每一个应用程序来说,如果想要访问ContentProvider中共享的数据,就一定要借助 ContentResolver类,可以通过Context中的getContentResolver()方法获取该类的实 例。ContentResolver中提供了一系列的方法用于对数据进行增删改查操作,其中insert() 方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方 法用于查询数据。有没有似曾相识的感觉?没错,SQLiteDatabase中也是使用这几个方法进 行增删改查操作的,只不过它们在方法参数上稍微有一些区别。 不同于SQLiteDatabase,ContentResolver中的增删改查方法都是不接收表名参数的,而是 使用一个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,我们还需要在字符串的头部加上协议声明。因此, 内容URI最标准的格式如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入。解析的方 法也相当简单,代码如下所示:
val uri = Uri.parse("content://com.example.app.provider/table1")
2.使用
val cursor = contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder)
查询完成后返回的仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取 出来了。读取的思路仍然是通过移动游标的位置遍历Cursor的所有行,然后取出每一行中相应 列的数据,代码如下所示:
while (cursor.moveToNext()) {
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()
掌握了最难的查询操作,剩下的增加、修改、删除操作就更不在话下了。我们先来看看如何向 table1表中添加一条数据,代码如下所示:
val values = contentValuesOf("column1" to "text", "column2" to 1) contentResolver.insert(uri, values)
可以看到,仍然是将待添加的数据组装到ContentValues中,然后调用ContentResolver的 insert()方法,将Uri和ContentValues作为参数传入即可
如果我们想要更新这条新添加的数据,把column1的值清空,可以借助ContentResolver的 update()方法实现,代码如下所示:
val values = contentValuesOf("column1" to "")
contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", "1"))
注意,上述代码使用了selection和selectionArgs参数来对想要更新的数据进行约束,以 防止所有的行都会受影响。 最后,可以调用ContentResolver的delete()方法将这条数据删除掉,代码如下所示:
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
使用
//<uses-permission android:name="android.permission.READ_CONTACTS" />
class MainActivity : AppCompatActivity() {
private val contactsList = ArrayList<String>()
private lateinit var adapter: ArrayAdapter<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
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<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()
}
}
}
二、ContentProvider的使用
class MyProvider : ContentProvider() {
override fun onCreate(): Boolean {
return false
}
override fun query(uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
return null
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
return null
}
override fun update(uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?): Int {
return 0
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
return 0
}
override fun getType(uri: Uri): String? {
return null
}
}
(1) onCreate()。初始化ContentProvider的时候调用。通常会在这里完成对数据库的创建和 升级等操作,返回true表示ContentProvider初始化成功,返回false则表示失败。
(2) query()。从ContentProvider中查询数据。uri参数用于确定查询哪张表,projection 参数用于确定查询哪些列,selection和selectionArgs参数用于约束查询哪些行, sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。
(3) insert()。向ContentProvider中添加一条数据。uri参数用于确定要添加到的表,待添 加的数据保存在values参数中。添加完成后,返回一个用于表示这条新记录的URI。
(4) update()。更新ContentProvider中已有的数据。uri参数用于确定更新哪一张表中的数 据,新数据保存在values参数中,selection和selectionArgs参数用于约束更新哪些行, 受影响的行数将作为返回值返回。
(5) delete()。从ContentProvider中删除数据。uri参数用于确定删除哪一张表中的数据, selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。
(6) getType()。根据传入的内容URI返回相应的MIME类型。 可以看到,很多方法里带有uri这个参数,这个参数也正是调用ContentResolver的增删改查 方法时传递过来的。而现在我们需要对传入的uri参数进行解析,从中分析出调用方期望访问的 表和数据。