ContentProvider
ContentProvider主要用于在不同的应用程序之间实现数据共享功能,同时还能保证被访问数据的安全性,是Android实现跨程序共享数据的标准方式。
运行时权限
Android权限机制详解
声明权限之后,用户主要在两个方面得到了保护。
- 在低于Android6.0系统的设备上,会在安装界面给出提醒,这样用户就可以清楚的知晓该程序一共申请了哪些权限,从而决定是否要安装这个程序。
- 用户可以随时在应用成勋管理界面查看任意一个程序的权限申请情况,以此保证应用程序不会出现各种滥用权限的情况。
由于很多常用软件普遍存在滥用权限问题,在Android6.0系统后加入了运行时权限功能。
即用户不需要再安装软件时就一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。
Android将常用权限大致归为两类: - 普通权限:不会直接威胁到用户安全和隐私的权限,系统会自动授权。
- 危险权限:可能会触及用户隐私或者对设备安全性造成影响的权限。
11组30个危险权限
在程序运行时申请权限
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall.setOnClickListener {
// 调用ContextCompat.checkSelfPermission判断用户是否已经给过权限。
// checkSelfPermission第一个参数为Context,第二个为具体的权限名。
// 把返回值与PackageManager.PERMISSION_GRANTED做比较,相等就说明已经授权。
// 不等就要调用ActivityCompat.requestPermissions向用户申请授权。
// requestPermissions第一个参数为Activity实例、第二个是一个String数组,放入要申请的权限名即可、第三个是请求码。
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1)
} else {
call()
}
}
}
// 调用完requestPermissions后,系统会弹出申请权限的对话框,用户选择完之后会回调onRequestPermissionsResult方法。
// 授权结果被封装在grantResults中,然后判断用户是否同意。
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()
}
}
}
访问其他程序中的数据
新建URI对象,它主要由两个部分组成:authority即包名,path即表名。最后在头部加上协议声明,用如下方式创建。
val uri = Uri.parse("content://com.example.app.provider/table1")
然后调用contentResolver.query方法查询表中数据,得到Cursor对象。
val cursor = contentResolver.query(
uri,projection,selection,selectionArgs,sortOrder)
通过移动游标位置遍历Cursor的所有行来取出数据。
while(cursor.moveToNext()){
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()
增加操作:
val values = contentValuesOf("column1" to "text","column2" to 1)
contentResolver.insert(uri,values)
更新操作:
val values = contentValuesOf("column1" to "")
contentResolver.update(uri,values,"column1 = ? and column2 = ?",arrayOf("text","1")
上述代码使用selection和selectionArgs参数来约束想要更新的数据,防止所有行受影响。
删除操作:
contentResolver.delete(uri,"column2 = ?",arrayOf("1"))
创建自己的ContentProvider
创建一个类继承自ContentProvider,并重写6个抽象方法。
class MyProvider : ContentProvider() {
private val table1Dir = 0//表示访问table1中的所有数据
private val table1Item = 1//表示访问table1中的单条数据
private val table2Dir = 2
private val table2Item = 3
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
// 创建uriMatcher实例,调用addURI传入期望匹配的内容URI的格式。
// 可以使用通配符,*表示匹配任意长度字符,#表示匹配任意长度数字。
// 如:content://com.example.app.provider/*可以匹配任意表,
// content://com.example.app.provider/table/#可以匹配任意行。
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 onCreate(): Boolean {
return false
}
//以下只是做出示范
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表中的单条数据
}
}
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
}
// 用于获取Uri对象所对应的MIME类型,其主要由三部分组成
// 必须以vnd开头
// 如果URI以路径为结尾则后接android.cursor.dir/,如果以id结尾则后接android.cursor.item/
// 最后接上vnd.<authority>.<path>
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
}
}