第8章 内容提供器
8.1 ContentProvider简介
ContentProvider:不同程序之间实现之间的数据共享的功能,提供了一套完整的机制,允许一个程序访问另一个程序的数据,并且能够确保安全。
8.2 运行时权限
8.2.1 安卓权限机制的讲解
-
申请了权限后在安卓和应用程序管理界面就能看到申请的权限。
-
安卓6.0以后,用户可以在要用到某项功能的时候申请权限而不是直接不能够使用。
-
权限分为普通权限和危险权限。普通权限在清单文件中写了,系统会自动帮我们申请。而危险权限写了后需要手动授权。
-
当申请一个危险权限后同组的其他权限也会自动被申请,但不要用这个逻辑写代码,因为组会调整。
-
安卓10系统为止所有的危险权限
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btz5NDIv-1660907088897)(第一行代码Kotlin.assets/image-20220818215436323.png)]
8.2.2 在程序运行时申请权限
- 添加按钮
- 清单中添加权限
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
- 动态申请权限
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
makeCall.setOnClickListener {
if(ContextCompat.checkSelfPermission(this,android.Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(this, 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,"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()
}
}
}
补充:申请的危险权限可以在程序管理界面取消
8.3 访问其他程序的数据
- 内容提供器的用法有两种,第一种是利用现有的内容提供器访问其他程序的数据,如通讯录、短信、媒体库都有开放内容提供器。第二种是自己开放内容提供器供其他应用程序读取数据
8.3.1 内容提供器的基本用法
- 如果要读取 ContentProvider 中提供的数据需要用到 ContentResolver 类,它里面有CRUD方法
- 这边的CRUD操作接收的不是表名而是Uri,Uri分为三部分,协议+包名+表名:用来区分哪个应用程序的哪张表。如:content://com.example.app.provider/table1
- 有了字符串后需要用 Uri.parsre(“content://com.example.app.provider/table1”)解析成uri
- 之后查询的操作和SQLite操作大体相同,不过参数有点区别
- query(uri,列,行的约束条件,为占位符提供的具体值,排序方式),返回cursor对象
读取
while(cursor.moveToNext()){
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getString(cursor.getColumnIndex("column2"))
}
添加
val values = contentValuesOf("column1" to "text","column2" to 1)
contentResolver.insert(uri,values)
把column1 的值清空
val values = contentValuesOf("column1" to "")
contentResolver.update(uri,values,"column1 = ? and column2 = ?",arrayOf("text","1"))
清除数据
contentResolver.delete(uri,"column2 = ?",arrayOf("1"))
8.3.2 读取系统联系人
- 准备ListView
- 代码
class MainActivity : AppCompatActivity() {
private val contactList = ArrayList<String>()
private lateinit var adapter: ArrayAdapter<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = ArrayAdapter(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item,contactList)
contactView.adapter = adapter
if(ContextCompat.checkSelfPermission(this,android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.READ_CONTACTS),1)
}else readContacts()
}
@SuppressLint("Range")
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()
}
}
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) {
readContacts()
}else{
Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show()
}
}
}
}
}
8.4 创建自己的内容提供器
8.4.1 创建内容提供器的步骤
- 创建自己的类继承ContentProvider,重写6个抽象方法
class MyProvider : ContentProvider() {
override fun onCreate(): Boolean {
TODO("Not yet implemented")
}
override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
TODO("Not yet implemented")
}
override fun getType(p0: Uri): String? {
TODO("Not yet implemented")
}
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
TODO("Not yet implemented")
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {
TODO("Not yet implemented")
}
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
TODO("Not yet implemented")
}
}
-
onCreate()。初始化ContentProvider的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回true表示ContentProvider初始化成功,返回false则表示失败。
-
query()。从ContentProvider中查询数据。uri参数用于确定查询哪张表,projection参数用于确定查询哪些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。
-
insert()。向ContentProvider中添加一条数据。uri参数用于确定要添加到的表,待添加的数据保存在values参数中。添加完成后,返回一个用于表示这条新记录的URI。
-
update()。更新ContentProvider中已有的数据。uri参数用于确定更新哪一张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回。
-
delete()。从ContentProvider中删除数据。uri参数用于确定删除哪一张表中的数据,selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。
-
getType()。根据传入的内容URI返回相应的MIME类型。
-
需要对传入的Uri进行解析出调用方需要数据的表和数据
- 两种Uri格式:某张表,某张表下id为1的数据
content://com.example.app.provider/table1
content://com.example.app.provider/table1/1
- 两种通配符
- *表示匹配任意长度的任意字符
- #表示匹配任意长度的任意数字
- 利用通配符的Uri:能够匹配任意表,能够匹配表中的任意一行
content://com.example.app.provider/*
content://com.example.app.provider/table1/#
- 借助 UriMatcher 这个类来匹配URI
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 onCreate(): Boolean {
TODO("Not yet implemented")
}
override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
when (uriMatcher.match(p0)) {
table1Dir ->{
//查询表一中所有数据,下面类似
}
table1Item ->{
}
table2Dir ->{
}
table2Item ->{
}
}
}
- getType( ) 获取Uri对象所对应的MIME类型。MIME类型由三部分组成
- vnd开头
- 路径结尾后接android.cursor.dir/ ;id结尾后接android.cursor.item/
- 最后接上 vnd..
- 例子:
- content://com.example.app.provider/table1
- <=> vnd.android.cursor.dir/vnd.com.example.app.provider.table1
- content://com.example.app.provider/table1/1
- <=> vnd.android.cursor.item/vnd.com.example.app.provider.table1
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
}
8.5.2 实现跨程序数据共享
- 打开 DatabaseTest , 创建内容提供器 new - other - content provider
class DatabaseProvider : ContentProvider() {
private val bookDir = 0
private val bookItem = 1
private val categoryDir = 2
private val categoryItem = 3
private val authority = "com.example.databasetest.provider"
private var dbHelper: MyDatabaseHelper? = null
/**
* by lazy 懒加载,代码块中的代码一开始并不执行,在uriMatcher变量首次调用的时候才会执行
* 并且代码块中最后一行代码会作为返回值返回
*/
private val uriMatcher by lazy{
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(authority, "book", bookDir)
matcher.addURI(authority, "book/#", bookItem)
matcher.addURI(authority, "category", categoryDir)
matcher.addURI(authority,"category/#",categoryItem)
matcher
}
/**
* Getter 语法糖
* ?. 操作符
* let函数
* ?:操作符
* 单行代码函数语法糖
*/
override fun onCreate() = context?.let {
dbHelper = MyDatabaseHelper(it, "BookStore.db", 3)
true
}?: false
override fun query(uri: Uri, projection: Array<String>?, selection: String?
, selectionArgs: Array<String>?, sortOrder: String?) = dbHelper?.let {
val db = it.readableDatabase
val cursor = when (uriMatcher.match(uri)) {
bookDir -> db.query("Book", projection, selection, selectionArgs, null, null, sortOrder)
bookItem -> {
val bookId = uri.pathSegments[1]
db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
}
categoryDir -> db.query("Category", projection, selection, selectionArgs, null, null, sortOrder)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.query("Category", projection, "id = ?", arrayOf(categoryId), null, null, sortOrder)
}
else -> null
}
cursor
}
override fun insert(uri: Uri, values: ContentValues?) = dbHelper?.let {
val db = it.writableDatabase
val uriReturn = when (uriMatcher.match(uri)) {
bookDir,bookItem ->{
val newBookId = db.insert("Book",null,values)
Uri.parse("content://$authority/book/$newBookId")
}
categoryDir,categoryItem ->{
val newCategoryId = db.insert("Category",null,values)
Uri.parse("content://$authority/book/$newCategoryId")
}
else -> null
}
uriReturn
}
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?) = dbHelper?.let {
val db = it.writableDatabase
val updateRows = when (uriMatcher.match(uri)) {
bookDir -> db.update("Book", values, selection, selectionArgs)
bookItem -> {
val bookId = uri.pathSegments[1]
db.update("Book", values, "id = ?", arrayOf(bookId))
}
categoryDir -> db.update("Category", values, selection, selectionArgs)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.update("Category",values,"id = ?", arrayOf(categoryId))
}
else -> 0
}
updateRows
} ?: 0
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = dbHelper?.let {
val db = it.writableDatabase
val deletedRows = when (uriMatcher.match(uri)) {
bookDir -> db.delete("Book", selection, selectionArgs)
bookItem -> {
val bookId = uri.pathSegments[1]
db.delete("Book", "id = ?", arrayOf(bookId))
}
categoryDir -> db.delete("Category", selection, selectionArgs)
categoryItem ->{
val categoryId = uri.pathSegments[1]
db.delete("Category","id = ?", arrayOf(categoryId))
}
else -> null
}
deletedRows
} ?: 0
override fun getType(p0: Uri) = when(uriMatcher.match(p0)){
bookDir -> "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book"
bookItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book"
categoryDir -> "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category"
bookItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category"
else -> null
}
}
- 清单文件自动配置好了
- 创建需要读取数据的项目
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/addData"
android:text="Add To Book"
>
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/queryData"
android:text="Query From Book"
>
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/updateData"
android:text="Update Book"
>
</Button>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/deleteData"
android:text="Delete From Book"
>
</Button>
</LinearLayout>
- 读取其他项目的数据库
class MainActivity : AppCompatActivity() {
var bookId:String? = null
private val TAG = "MainActivity"
@SuppressLint("Range")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addData.setOnClickListener {
val uri = Uri.parse("content://com.example.databasetest.provider/book")
val values = contentValuesOf("name" to "A Clash of Kings","author" to "George Martin","pages" to 1040,"price" to 22.85)
val newUri = contentResolver.insert(uri, values)
bookId = newUri?.pathSegments?.get(1)
}
queryData.setOnClickListener {
val uri = Uri.parse("content://com.example.databasetest.provider/book")
contentResolver.query(uri,null,null,null,null)?.apply {
while (moveToNext()) {
val name = getString(getColumnIndex("name"))
val author = getString(getColumnIndex("author"))
val pages = getString(getColumnIndex("pages"))
val price = getDouble(getColumnIndex("price"))
Log.d(TAG, "book name is $name")
Log.d(TAG, "book author is $author")
Log.d(TAG, "book pages is $pages")
Log.d(TAG, "book price is $price")
}
close()
}
}
updateData.setOnClickListener {
bookId?.let {
val uri = Uri.parse("content://com.example.databasetest.provider/book/$it")
val values = contentValuesOf("name" to "A Storm of Swords","pages" to 1216,"price" to 24.05)
contentResolver.update(uri,values,null,null)
}
}
deleteData.setOnClickListener {
bookId?.let {
val uri = Uri.parse("content://com.example.databasetest.provider/book/$it")
contentResolver.delete(uri,null,null)
}
}
}
}
-
注意添加权限
queries里添加要读取的程序的包名
<uses-permission android:name="DatabaseProvider._READ_PERMISSION" />
<uses-permission android:name="DatabaseProvider._WRITE_PERMISSION" />
<queries>
<package android:name="com.android.databasetest" />
</queries>
8.5 Kotlin课堂:泛型和委托
8.5.1 泛型的基本用法
定义:泛型允许我们在不指定具体类型的情况下进行编程
-
如何定义一个自己的泛型?语法:
- 定义泛型类:
class MyClass<T>{ fun method(param: T): T{ return param } }
- 使用:在调用泛型类的时候指定具体的类型
val myClass = MyClass<Int>() val result = myClass.method(123)
- 定义泛型方法:
class MyCass{ fun <T> method(param: T):T{ return param } }
- 使用
val myClass = MyClass() val result = myClass.method(Int)(123)
- 利用类型推导机制优化
val myClass = MyClass() val result = myClass.method(123)
- 补充:对泛型类型进行限制,指定上界
class MyCass{ fun <T : Number> method(param: T):T{ return param } } //这样子T只能指定 float,double,int,指定为string会报错
- 补充:上界不指定默认为Any?可以为空,如果设置不可为空,需要指定上界为Any
-
对6.5.1小节高阶函数优化
- 缺陷:build只能StringBuilder使用
fun StringBuilder.build(block: StringBuilder.() -> Unit):StringBuilder{ block() return this }
- 利用泛型则可在任意类下使用
新建一个build.kt文件
fun <T> T.build(block: T.() -> Unit):T{ block() return this }
contentResolver.query(uri,null,null,null,null)?.build { while (moveToNext()) { ... } close() }
-
两种懒加载
- 延时初始化lateinit 在使用的时候手动加载的懒加载方式然后再使用
- 惰性初始化by lazy 使用的时候自动加载懒加载然后再使用
8.5.2 类委托
定义:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。
-
分类
- 类委托:一个类委托给另一个类去实现
利用委托实现set接口
class MySet<T>(val helperSet: HashSet<T>) : Set<T> { override val size: Int get() = helperSet.size override fun contains(element: T) = helperSet.contains(element) override fun containsAll(element: Collection<T>) = helperSet.containsAll(elements) override fun isEmpty() = helperSet.isEmpty() override fun iterator() = helperSet.iterator() }
利用by关键字优化
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet{ //新增方法 fun helloWorld() = println("Hello World") //重载方法 override fun isEmpty() = false }
上面就是一个全新的数据结构类,有HashSet的全部功能和自己独有的功能,并且可以修改原有功能
- 委托属性:一个属性的具体实现委托给另一个类
语法结构:
class MyClass{ //将p属性委托给了Delegate这个类,当赋值和调用的时候会使用这个类里的get set方法. //还得对Delegate具体实现 var p by Delegate() }
class Delegate{ var propValue:Any? = null //第一个参数用于表示委托功能在什么类中使用 //第二个参数是属性操作类,用于获取各种属性相关的值,当下用不着,但是必须声明 //<*>这种写法表示不关心或者不知道泛型的具体类型 operator fun getValue(myClass: MyClass,prop: KProperty<*>):Any?{ return propValue } operator fun setValue(myClass: MyClass,prop: KProperty<*>, value: Any?){ propValue = value } }
8.5.3 实现一个自己的 lazy 函数
- by lazy语法结构
val p by lazy{ ... }
lazy函数会创建并且返回delegate对象,调用p时其实是调用delegete的getValue方法,然后getValue方法中调用lazy函数传入的lambda表达式,表达式的最后一行作为返回值给p
- 顶层函数:不定义在任何类中的函数
- 实现自己的懒加载函数later
class Later<T>(val block:()->T){
var value:Any? = null
operator fun getValue(any:Any?,prop: KProperty<*>):T{
if(value == null){
value = block
}
return value as T
}
}
fun <T> later(block: () -> T) = Later(block)
利用按钮的点击事件来检查,第一次点击的时候会打印,之后不会,不点击也不会打印,说明使用的时候才初始化
val p by later{
Log.d("TAG","run codes inside later bloce")
"test later"
}