Content Provider 与 权限
- 运行时权限
- 拨号为例
- ContentProvider
- 使用现有ContentProvider
- 读取系统联系人
- 自定义ContentResolver
在Android 13(T)官方的获取权限不见得比其他辅助的库更复杂,具体用法
这里没有更新,请自行查阅资料
运行时权限
安卓权限组分普通权限和危险权限,每组又有多个,只要授权某组的一个,该组就默认全都授权,但组规则会变,不要基于此实现任何功能逻辑。
拨号为例
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
val button = Button(this)
button.setOnClickListener {
callPhone()
}
setContentView(button)
}
private fun callPhone()
{
try
{
// ACTION_CALL 直接拨打权限,需要在AndroidManifest申明
// <uses-permission android:name="android.permission.CALL_PHONE"/>
Intent(Intent.ACTION_CALL).run {
data = Uri.parse("tel:10086")
startActivity(this)
}
} catch (e: SecurityException)
{
e.printStackTrace()
}
}
}
在安卓6.0版本之前,这是没有问题的,更高版本,需要运行时处理,修复如下:
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
val button = Button(this)
button.setOnClickListener {
checkPermissions()
}
setContentView(button)
}
private fun callPhone()
{
try
{
// ACTION_CALL 直接拨打权限,需要在AndroidManifest申明
// <uses-permission android:name="android.permission.CALL_PHONE"/>
Intent(Intent.ACTION_CALL).run {
data = Uri.parse("tel:10086")
startActivity(this)
}
} catch (e: SecurityException)
{
e.printStackTrace()
}
}
private fun checkPermissions()
{
// 第一步,判断用户是否已经给与授权
if(ContextCompat.checkSelfPermission(this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED)
{
// 请求授权,不论结果如何,都会调用onRequestPermissionsResult()方法
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CALL_PHONE),
1 // 请求码,用于返回匹配结果, 应该 >= 0
)
} else {
callPhone()
}
}
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)
{
callPhone()
} else {
Toast.makeText(this, "你已拒绝", Toast.LENGTH_SHORT).show()
}
}
}
}
}
ContentProvider
确保安全性的同时,实现数据共享。用法有:
- 使用现有ContentProvider读取和操作应用程序数据
- 自定义ContentProvider ,给程序数据提供接口访问
使用现有ContentProvider
对于每个应用程序,访问ContentProvider需要借助ContentResolver,
在Kotlin中,直接引用contentResolver即可(语法糖)
ContentResolver提供的类似SQLiteDatabase的增删查改操作,不过较为容易。但都不再接收表名参数,而是使用Uri替代。标准格式如下:
content://authority.provider/path
authority:区分不同应用程序,一般为避免冲突,采用应用包名命名。
path:同一应用不同表名做区分
除此之外,还可以加上id
content://authority.provider/path/id
以id结尾可以有更大的拓展可能性,可以用通配符分别匹配这两种格式的内容URI,规则如下。
- * 表示 匹配任意长度的任意字符
- # 表示 匹配任意长度的梳子
解析方法:
val uri = Uri.parse("content://authority.provider/path")
查询表
val cursor = contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder
)
参数 | 对应SQL码 | 描述 |
uri | feom table_name | 指定查询某应用下的一个表 |
projection | select column1, column2 | 指定查询的列名 |
selection | where column = value | 指定where的约束条件 |
selectionArgs | - | 为where的占位符提供具体描述 |
sortOrder | order by column1, column2 | 指定查询结果的排序方式 |
cursor表量是Cursor
对象,这样就能读取数据,依然是遍历所有行
while(cursor.moveToNext())
{
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getInt (cursor.getColumnIndex("column2"))
}
增加数据
contentResolver.insert(
uri,
contentValuesOf(
"colum1" to "text",
"column2" to 1
)
)
更新数据
若更新如下数据,把column1值清空,可由update()实现
contentResolver.update(
uri,
contentValuesOf("colum1" to ""),
"column1 = ? and column2 = ?",
arrayOf("text", "1")
)
上述代码使用了selection和selectionArgs参数对想要更新的数据进行约束,防止所有行为都受到影响
删除
可以如下删除这条数据
contentResolver.delete(
uri,
"column1 = ?",
arrayOf("1")
)
读取系统联系人
首先还是申请运行时权限、AndroidManiFest.xml写入信息,检查是否授权READ_CONTACTS等等,这里不再赘述。
关键读取代码如下:
@SuppressLint("Range")
private fun readContacts()
{
// 查询联系人数据
contentResolver.query(
// 这里封装好了Uri
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
))
}
// 关闭流
close()
}
}
自定义ContentResolver
自定义主要是继承contentResolver然后重写方法,需要结合UriMatcher和数据库来实现,现有资料很多,这里便不再赘述。