ContentProvider主要用于在不同的应用程序之间实现数据共享的功能

运行时权限

这是ContentProvider能否使用的基础.
运行时权限的意义:在旧版android中,所有的权限都是在应用安装时会直接声明出来,如果你接受这些条件就安装。否则拒绝安装就可以了。而这就会导致一些店大欺客的问题。比如:旧版微信就会申请几乎所有的权限。而你如果不接受你就不能使用微信。

所以运行时权限就有了他存在的意义:应用会在需要使用该权限时才会像用户发起申请,即便用户拒绝了,也可以正常使用其他功能。
当然,并不是所有权限都需要在运行时申请,对于用户来说,不停地授权也很烦琐。Android现在将常用的权限大致归成了两类,一类是普通权限,一类是危险权限。对于普通权限,系统会帮我们自动授权。而危险权限都交给用户决定。
以下是Android到Android10版本所有的危险权限:

运行时权限申请样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

class FirstActivity : AppCompatActivity() {
private lateinit var binding: ActivityFirstBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityFirstBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.apply {
callme.setOnClickListener {
/**
* 判断是否拥有拨打电话的权限
* ContextCompat.checkSelfPermission()接收两个参数,第一个是context。第二个是具体的权限名
* PackageManager.PERMISSION_GRANTED是个常量表示用户完成授权
* ActivityCompat.requestPermissions()接收三个参数,第三个参数没有具体要求,只要是唯一值即可
*/
if (ContextCompat.checkSelfPermission(
this@FirstActivity,
android.Manifest.permission.CALL_PHONE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this@FirstActivity,
arrayOf(android.Manifest.permission.CALL_PHONE),
1
)

}else{
call()
}
}
}
}


/**
* 不难理解,所以不做解释
*/
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){
call()
}else{
Toast.makeText(this@FirstActivity,"You deny your Permission",Toast.LENGTH_LONG).show()
}
}
}
}
private fun call() {
try {
val intent = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
} catch (e: Exception) {
e.printStackTrace()
}
}
}

访问其他应用的数据

ContentProvider的使用:

  1. 他想要获取数据就需要借助ContentResolver类。可以通过Context中的getContentResolver()方法获取实例。
  2. ContentResolver类与SQLiteDatabase类似,也具有insert(),等四个增删改查方法。
  3. 但不同于SQLiteDatabase,他第一个参数接收的不是表名,而是Uri参数。它由三部分组成:协议,authority,path。authority是用于对不同的应用程序做区分的,一般为了避免冲突,会采用应用包名的方式进行命名。path就是对同一应用不同的表做区分,就像表名。它的比较标准的格式如下:content://com.example.app.provider/table1。只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了
    • query()

      它与数据库的参数很像,就是第一个不是表名,而是Uri。而且他的参数会更加简单一点。
      它返回的也是一个cursor对象,对于cursor对象,只需要遍历,并且按照列数完成对每一列的读取即可。
    • insert()
      1
      2
      val values = contentValuesOf("column1" to "text", "column2" to 1)
      contentResolver.insert(uri, values)
      它接收是也是Uri对象和contentValues对象。
    • update()
      1
      2
      val values = contentValuesOf("column1" to "")
      contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", "1"))
      这里使用了selection和selectionArgs参数来对想要更新的数据进行约束
    • delete()
      1
      contentResolver.delete(uri, "column2 = ?", arrayOf("1"))

构建自己的ContentProvider

已知:只需要获得该应用程序的内容URI,然后借助ContentResolver进行增删改查操作就可以了,但是如何向外部提供接口并保证数据的安全?
步骤:

  1. 新建一个类继承ContentProvider,然后重写其6个抽象方法。
    (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类型。

  1. uri解析
    一个标准的uri写法
    content://com.example.app.provider/table1
    这就表示调用方期望访问的是com.example.app这个应用的table1表中的数据。
    content://com.example.app.provider/table1/1
    表示调用方期望访问的是com.example.app这个应用的table1表中id为1的数据
  • *表示匹配任意长度的任意字符。
  • #表示匹配任意长度的数字。

一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*
一个能够匹配table1表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#

UriMatcher类有两个方法:

  • addURI():接收三个参数分别把authority,path和一个自定义代码传进去
  • match():接收一个uri对象作为参数。能够匹配到这个uri对象的自定义代码

URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定。

  • 必须以vnd开头。
  • 如果内容URI以路径结尾,则后接android.cursor.dir/;如果内容URI以id结尾,则后
  • android.cursor.item/
  • 最后接上vnd..