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

确保安全性的同时,实现数据共享。用法有:

  1. 使用现有ContentProvider读取和操作应用程序数据
  2. 自定义ContentProvider ,给程序数据提供接口访问

使用现有ContentProvider

对于每个应用程序,访问ContentProvider需要借助ContentResolver,
在Kotlin中,直接引用contentResolver即可(语法糖)

ContentResolver提供的类似SQLiteDatabase的增删查改操作,不过较为容易。但都不再接收表名参数,而是使用Uri替代。标准格式如下:

content://authority.provider/path

authority:区分不同应用程序,一般为避免冲突,采用应用包名命名。
path:同一应用不同表名做区分

除此之外,还可以加上id

content://authority.provider/path/id

以id结尾可以有更大的拓展可能性,可以用通配符分别匹配这两种格式的内容URI,规则如下。

  1. * 表示 匹配任意长度的任意字符
  2. # 表示 匹配任意长度的梳子
    解析方法:
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")
)

上述代码使用了selectionselectionArgs参数对想要更新的数据进行约束,防止所有行为都受到影响

删除
可以如下删除这条数据

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和数据库来实现,现有资料很多,这里便不再赘述。