ContentProvider主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。
不同于文件存储和SharedPerferences存储中两种全局可读写操作模式,ContentProvider可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
Android 6.0之前,安装程序时会给出程序申请的权限清单,由用户决定是否同意其获取,但如果安装就只能同意,不给其权限就无法安装。安装后可去设置里查看所有软件的权限申请情况。
为了保护用户的安全和隐私,解决部分软件“店大欺客”的问题,Android 6.0系统引入了运行时权限功能。用户不再需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。
也不是所有权限都需要在运行时申请,Android将常用权限分成了两类:
普通权限和危险权限。其实还有一些特殊权限,但使用较少。
普通权限指的是不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮用户进行授权,不需要用户手动操作。如监听启动广播权限,网络权限等。
危险权限则表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置、拨打电话等,对于这部分权限申请,必须由用户手动授权才可以,否则程序就无法使用相应的功能。而且用户授权后,还随时可以到程序的设置中将授权过的危险权限进行关闭。
遇到上图权限时需要进行运行时权限处理,否则只需要在AndroidManifest.xml文件中添加一下权限声明即可。
原则上,用户一旦同意了某个权限申请之后,同组的其他权限也会被系统自动授权,但不要基于此规则实现功能逻辑,因为Android随时可能调整权限的分组。
先添加在AndroidManifest注册文件中添加上权限声明。
首先判断用户是否已经授权,如果已授权则直接执行功能,否则就要申请授权。
makeCall.setOnClickListener {
if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE),1)
}else{
call()
}
}
调用requestPermissions方法后,系统弹出一个权限申请框,用户可以选择同意或拒绝权限申请。用户选择的结果会回调到onRequestPermissonResult方法中。之后根据用户选择,进行相应的逻辑。
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,"您拒绝了权限申请",Toast.LENGTH_SHORT).show()
}
}
}
}
ContentProvider的用法一般有两种:一种是使用现有的ContentProvider读取和操作相应程序中的数据,另一种是创建自己的ContentProvider,给程序的数据提供外部访问接口。
如果想访问ContentProvider中共享的数据,就一定要借助ContentResolver类,它提供了一系列方法用于对数据的CRUD操作。
相对于SQLite,ContentResolver不接收表名,而是接受一个内容URI。
内容UPI可清楚表达我们想要访问哪个程序中的哪张表的数据,其由两部分组成,authority和path。前者用于区分不同程序,一般用应用包名命名,后者则区分同一程序中不同的表。
其标准格式如下:
content://authoriy/path
content://com.example.app.provider/table1
访问com.example.app.provider下名为table1的表中的数据
content://com.example.app.provider/table1/1
访问com.example.app.provider下名为table1的表中id为1的数据
有了URI字符串之后,就可以用Uri.parse方法将其解析为Uri对象了。
//这里的uri使用的是系统为我们提供的访问通讯录的uri
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))
contactList.add("$displayName\n$number")
}
adapter.notifyDataSetChanged()
close()
}
}
如果想要实现将程序的数据提供给其他程序共享的话,就要创建自己的ContentProvider供其他程序使用。
新建一个类去继承ContentProvider,实现其六个抽象方法。分别是onCreate,初始化ContentProvider的时候调用,通常在这里完成数据库的创建和升级等功能。返回true表示创建成功,false表示创建失败。
query,insert,update,delete,
gettype,根据传入的内容Uri返回相应的MIME类型。
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 insert(uri: Uri, values: ContentValues?): Uri? {
return null
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
when (uriMatcher.match(uri)){
table1Dir -> {
}
table1Item -> {
}
table2Dir -> {
}
table2Item -> {
}
}
return null
}
override fun onCreate(): Boolean {
return false
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
return 0
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
return 0
}
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
}
}
传入的uri参数需要被进行解析,从中分析出调用方期望访问的表和数据。我们可以使用通配符匹配两种格式的内容uri:
content://com.example.app.provider/*
匹配com.example.app.provider下任意表的数据
content://com.example.app.provider/table1/#
匹配com.example.app.provider下名为table1的表中任意一行的数据
接着,我们借助UriMatcher实现匹配内容URI的功能,从而实现判断调用方访问的是哪张表的数据。
getType方法用于获取Uri对象所对应的MIME类型。
其格式如下:
如果内容Uri以路径结尾
vnd.android.cursor.dir/vnd.< anthority>.< path>
如果内容Uri以id结尾
vnd.android.cursor.item/vnd.< anthority>.< path>
由上述步骤我们可以知道,所有的增删改查操作都一定要匹配到相应的内容URi格式才能进行,而我们只要不向UriMatcher中添加隐私数据的URI,这部分数据就无法被外部程序访问。