ContentProvider主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。
目前,使用ContentProvider是Android实现跨程序共享数据的标准方式。
Android系统的权限机制设计得非常简单,就是用户如果认可你所申请的权限,就会安装你的程序,如果不认可你所申请的权限,那么拒绝安装就可以了。
而在Android 6.0系统中新增了运行时权限功能。
现在用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。
比如一款相机应用在运行时申请了地理位置定位权限,就算拒绝了这个权限,也应该可以使用这个应用的其他功能,而不是像之前那样直接无法安装它。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall.setOnClickListener {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1)
} else {
call()
}
}
}
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) {
call()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun call() {
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
ContentResolver的基本用法
ContentResolver中的增删改查方法都不接收表名参数,而是使用一个Uri参数代替,这个参数被称为内容URI。
内容URI给ContentProvider中的数据建立了唯一标识符,它主要由两部分组成:authority和path。
内容URI最标准的格式如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
得到了内容URI字符串之后,我们只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。
val uri = Uri.parse(“content://com.example.app.provider/table1”)
读取系统联系人
读取系统联系人示例写法如下(省略了申请运行时权限部分):
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
……
readContacts()
}
……
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的步骤:
ContentProvider类中有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写。
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
}
}
创建ContentProvider的步骤:
onCreate() .初始化ContentProvider的时候调用,通常会在这里完成对数据库的创建和升级等操作,返回true表示ContentProvider初始化成功,返回false则表示失败。
query () 从contentrovider中查询数据,uri参数用于确定查询那张表,projection 参数用于确定查询那些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。
insert()。向ContentProvider中添加一条数据。uri参数用于确定要添加到的表,待添加的数据保存在values参数中。添加完成后,返回一个用于表示这条新记录的URI。
update()。更新ContentProvider中已有的数据。uri参数用于确定更新哪一张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回。
delete()。从ContentProvider中删除数据。uri参数用于确定删除哪一张表中的数据,selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。
getType()。根据传入的内容URI返回相应的MIME类型。
实现跨进程数据共享:
借助UirMatcher 这个类能够实现匹配内容URi的功能。
当调用UriMaticher的match()方法时,可以将一个Uri对象传入,返回值是某个能够匹配这个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)
}
…
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
when (uriMatcher.match(uri)) {
table1Dir -> {
// 查询table1表中的所有数据
}
table1Item -> {
// 查询table1表中的单条数据
}
table2Dir -> {
// 查询table2表中的所有数据
}
table2Item -> {
// 查询table2表中的单条数据
}
}
…
}
…
}
实现跨程序数据共享
getType()方法是所有的ContentProvider都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定
必须以vnd开头。
如果内容URI以路径结尾,则后接android.cursor.dir/;如果内容URI以id结尾,则后接android.cursor.item/。
最后接上vnd..
所以,对于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
实现跨进程数据共享在这里插入代码片
getType()方法中的逻辑,如下所示:
class MyProvider : ContentProvider() {
…
override fun getType(uri: Uri) = 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
}
}