第一行代码 第三版 第八章 内容提供器

第8章 内容提供器

8.1 ContentProvider简介

ContentProvider:不同程序之间实现之间的数据共享的功能,提供了一套完整的机制,允许一个程序访问另一个程序的数据,并且能够确保安全。

8.2 运行时权限

8.2.1 安卓权限机制的讲解

  • 申请了权限后在安卓和应用程序管理界面就能看到申请的权限。

  • 安卓6.0以后,用户可以在要用到某项功能的时候申请权限而不是直接不能够使用。

  • 权限分为普通权限和危险权限。普通权限在清单文件中写了,系统会自动帮我们申请。而危险权限写了后需要手动授权。

  • 当申请一个危险权限后同组的其他权限也会自动被申请,但不要用这个逻辑写代码,因为组会调整。

  • 安卓10系统为止所有的危险权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btz5NDIv-1660907088897)(第一行代码Kotlin.assets/image-20220818215436323.png)]

8.2.2 在程序运行时申请权限
  1. 添加按钮
  2. 清单中添加权限
<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>
  1. 动态申请权限
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 读取系统联系人

  1. 准备ListView
  2. 代码
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 创建内容提供器的步骤

  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进行解析出调用方需要数据的表和数据

  1. 两种Uri格式:某张表,某张表下id为1的数据
content://com.example.app.provider/table1
content://com.example.app.provider/table1/1
  1. 两种通配符
  • *表示匹配任意长度的任意字符
  • #表示匹配任意长度的任意数字
  • 利用通配符的Uri:能够匹配任意表,能够匹配表中的任意一行
content://com.example.app.provider/*
content://com.example.app.provider/table1/#
  1. 借助 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 实现跨程序数据共享

  1. 打开 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
    }

}
  1. 清单文件自动配置好了
  2. 创建需要读取数据的项目
<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>
  1. 读取其他项目的数据库
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)
            }
        }
    }
}
  1. 注意添加权限

    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"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蒋的学习笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值