ContentProvider的介绍和使用

news2025/2/4 20:39:55

文章目录

  • ContentProvider
    • ContentProvider简介
    • 运行时权限
      • Android权限机制详解
      • 在程序运行时申请权限
    • 访问其他程序当中数据
      • ContentResolver的基本用法
      • 读取系统联系人信息
    • 创建自己的ContentProvider
      • 创建ContentProvider的步骤
      • 实现跨程序数据共享

ContentProvider

  • 如果我们想要实现跨程序数据共享的功能,我们就可以使用这个ContentProvider

ContentProvider简介

  • ContentProvider主要应用于不同的应用程序之间实现数据的共享,它提供了一整套完整的机制,允许一个程序访问另一个程序当中的数据,同时还能保证被访问数据的安全性.
  • 目前使用ContentProvider是Android实现跨程序共享数据的标准方式
  • 不同于文件存储和SharedPreferences存储中的两种全局可读可写操作模式,ContentProvider可以选择只对那一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄露的风险.

运行时权限

  • Android开发团队在Android6.0系统中引入了运行时权限这个功能,从而可以更好的保护用户的安全和隐私

Android权限机制详解

  • 在学习广播机制的时候,为了要监听开机广播,在AndroidManifest.xml文件当中添加了这样一段声明
<uses-permission android:name="android.intent.action.BOOT_COMPLETED" />
  • 因为监听开机广播涉及用户设备安全问题,所以必须要在AndroidManifest.xml文件当中进行权限声明,否则我们的程序就会崩溃
  • 当我们添加了这段权限声明之后,用户主要在两个方面受到了保护
  • 一方面,如果用户在低于Android6.0系统的设备上安装程序,会在安装界面给出下面这样的提示,这样用户就知晓该程序一共申请了哪些权限,从而决定是否要安装这个程序.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B6ih75pk-1671516835598)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221215155041884.png)].

  • 另外一方面,用户可以随时在应用程序管理界面查看任意一个程序权限申请情况,这样程序所申请的权限就尽收眼底了.
  • 而运行时权限的功能就是,用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件使用的过程当中再对某一项权限进行授权,比如一款相机在运行的时候申请了地理位置权限,就算我拒绝了它,也应该可以使用这个应用的其他功能,而不是像之前那样直接无法安装.
  • 当然也并不是说所有的权限都需要在运行的时候进行申请,频繁的授权也会很繁琐,Android现在将常用的权限分为了两个大类,一个是普通权限,一个是危险权限.
  • 普通权限就是那些不会直接威胁到用户的安全和隐私的权限,对于这部分的权限系统会自动帮助我们授权
  • 反之就是危险权限,如获取设备联系人信息,定位设备的地理位置,对于这部分的权限申请,必须由用户手动进行授权才可以,否则程序无法使用相应的程序.
  • Android中一共有上百种权限,危险权限就那么多,除了危险权限之外就是普通权限了,下面的表格当中就列举了Android 10系统到目前为止所有的危险权限,一共是11组30个权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C399jICc-1671516835601)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221215160811566.png)]

  • 表格当中每一个危险权限都属于一个权限组,我们在进行运行时权限处理的时候使用的是权限名,原则上用户一旦同意了某一个权限申请之后,同组的其他权限也会被系统自动授权,但是谨记不要使用此规则来实现任何功能逻辑,因为Android系统随时有可能调整权限分组.

在程序运行时申请权限

  • 创建一个RuntimePermissionTest项目来进行测试
  • 使用CALL_PHONE这个权限来作为示例
  • CALL_PHNOE这个权限是编写拨打电话功能的时候所需要使用到的权限,因为拨打电话涉及手机资费的问题所以该权限被列为了危险权限的系列
  • 修改activity_main.xml文件中的代码如下所示
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/makeCall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="make call" />

</LinearLayout>
  • 在activity_main.xml文件当中编写了一个按钮,点击按钮就去触发拨打按钮的逻辑,接着修改MainActivity当中的代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        makeCall.setOnClickListener {
            try {
                val intent = Intent(Intent.ACTION_CALL)
                intent.data = Uri.parse("tel:10086")
                startActivity(intent)
            } catch (e: SecurityException) {
                e.printStackTrace()
            }
        }
    }
}
  • 在按钮的点击事件当中,我们构建了一个隐私的Intent,Intent的action指定为Intent.ACTION_CALL,这是系统内置的一个打电话的动作,然后再data部分指定了协议tel,号码是10086
  • 接下来修改AndroidManifest.xml文件,在其中声明如下权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwRbbf86-1671516835601)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221215174121046.png)]

  • 这样我们就将拨打电话的功能实现了,并且在低于Android 6.0系统的手机上都是可以正常运行的.但是在Android 6.0版本及以上点击make call按钮就没有任何效果了,点击按钮我们会看到报错的信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z02zxvfJ-1671516835602)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221215174809025.png)]

  • 在报错信息当中可以看到Permission Denial,这是由于权限被禁止所导致的,因为Android 6.0及以上系统在使用危险权限时必须进行运行时权限处理.
  • 那么我们可以在MainActivity当中修复这个问题
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        makeCall.setOnClickListener {
            if (ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.CALL_PHONE
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                ActivityCompat.requestPermissions(this, arrayOf(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()
        }
    }
}
  • 运行时权限的核心就是在程序运行的过程当中,由用户授权我们去执行某一些危险的操作,程序是不可以擅作主张去执行一些危险操作的
  • 因此我们的第一步就是先判断用户是否给我们进行了授权,借助的是ContextCompat.checkSelfPermission()方法
  • 该方法接收两个参数,第一个参数是Context
  • 第二个参数是具体的权限名,比如拨打电话的权限名就是android.Manifest.permission.CALL_PHONE
  • 然后我们使用该方法的返回值和PackageManager.PERMISSION_GRANTED作比较,相等就说明我们做了授权,不相等就表示我们没有做授权
  • 如果授权了的话就比较简单了,直接点击按钮进行直接拨打电话即可,我们将拨打电话的逻辑封装到call()方法当中
  • 如果没有授权的话,则需要调用ActivityCompat.requestPermissions()方法向用户进行授权,requestPermissions()方法接收三个参数
  • 第一个参数:要求是Activity的实例
  • 第二个参数是String数组,我们要把申请的权限放在数组中即可
  • 第三个参数是请求码,只要是唯一值就可以了,我们在这里传入1
  • 调用完requestPermissions()方法之后,系统弹出一个权限申请的对话框,用户可以选择统一或者拒绝我们的权先申请,不论是那种结果,最终都会回调到onRequestPermissionsResult()方法,然而授权的结果会封装到grantResults参数当中,这里我们只需要判断一下最后的授权结果,如果是同意的话,就调用call()方法,如果用户不同意的话,就提示一段文本即可.

访问其他程序当中数据

  • ContentProvider的用法一般两种:一种是使用现有的ContentProvider读取和操作相应程序当中的数据;
  • 另外一种是创建自己的ContentProvider,给程序的数据提供外部访问接口

ContentResolver的基本用法

  • 对于任何一个应用程序来说,如果想要访问ContentProvider中共享的数据,就一定要借助ContentProvider类,可以通过Context中的getContentResolver()方法获取该类的实例.
  • ContentResolver中提供了一系列的方法用于对数据进行增删改查操作,其中inset()方法用于添加数据,update()方法用于更新数据,delete()方法用于对数据进行删除,query()方法用于对数据进行查询操作,只不过就是它们在方法参数上稍微有一些差别.
  • 增删改查方法会用到一个Uri参数,这个参数被称为内容URI.内容URI给ContentProvider中的数据建立为一个标识符,它主要由两个部分组成:anthority和path
  • anthority是用于对不同的程序做区分的,一般为了避免冲突,会采用应用包名的方式进行命名,比如某个应用的包名是com.example.app那么该应用对应的authority就可以命名为com.example.app.provider
  • path则是用于对一个应用程序中不同的表做区分,通常会添加到authority的后面,比如某一个应用程序在数据库中存放了两张表table1和table2,然后把authority和path进行组合
  • 内容URI就变成了com.example.app.provider/table1和com.example.app.provider/table2
  • 然后在字符串的头部还需要加上协议声明,因此内容URI最标准的格式如下:

content://com.example.app.provider/table1

content://com.example.app.provider/table2

  • 现在内容URI就可以非常清楚的表大我们想要访问哪个程序中的那张表里的数据,也正是因此,ContentResolver中的增删改查方法才接受Uri对象作为参数,如果使用表名的话,系统将无法的得知我们期望回访的是哪个应用程序当中的表.
  • 在我们得到内容URI的时候,我们还需要将他解析成Uri对象才可以作为参数进行传入,解析方法也相当简单,代码如下所示
val uri = Uri.parse("content://com.example.app.provider/table1")
  • 只需要调用Uri.parse()方法即可将内容URI解析成为Uri对象了
  • 现在我们想要使用这个Uri对象来查询table1表中的数据,代码如下所示
val cursor = contentResolver.query (
    uri,
    projection,
    selection,
    selectionArgs,
    sortOrder
)
  • 下面这个表对query()方法当中的参数做了详细的说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i4AndaHZ-1671516835603)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221216133028457.png)]

  • 查询完成之后,仍然是一个cursor对象,这时我们就可以将数据从cursor对象中逐个读取出来了,读取的思路仍然是通过移动游标的位置遍历cursor的所有行,然后取出每一行中相应列的数据,代码如下
while(cursor.moveToNext()) {
    val column1 = cursor.getString(cursor.getColumnIndex("column1"))
    val column2 = cursor.getString(cursor.getColumnIndex("column2"))
}
cursor.close()
  • 向表中添加一条数据,代码如下所示
//将待添加的数据添加到contentValues当中
val values = contentValuesOf("column1" to "text", "column2" to 1)
//然后调用contentResolver的insert()方法,将uri和ContentValues作为参数传入即可
contentResolver.insert(uri, values)
  • 如果我们想要将新添加到这个数据进行更新将column1的值进行清空,可以借助contentResolver的update()方法实现,代码如下所示
val values = contentValuesOf("column1" to "")
contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", " 1"))
  • 最后可以调用contentResolver.delete()方法将这条数据删除掉,代码如下所示
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))

读取系统联系人信息

  • 创建一个CotentsTest项目,修改activity_main.xml文件当中的内容
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/contactsView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
  • 接着修改MainActivity当中的代码.
class MainActivity : AppCompatActivity() {
    private val contactsList = ArrayList<String>()
    private lateinit var adapter: ArrayAdapter<String>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList)
        contactsView.adapter = adapter
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.READ_CONTACTS), 1
            )
        } else {
            readContacts()
        }
    }

    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, "你没有权限", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    /**
     * 查询联系人数据
     */
    @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
                    )
                )
                //将两个数据取出来之后进行拼接
                contactsList.add("$displayName\n$number")
            }
            adapter.notifyDataSetChanged()
            close()
        }
    }
}
  • 在onCreate()方法当中,我们首先按照ListView的标准用法对其进行初始化,然后开始调用运行时权限进行逻辑处理,因为READ_CONTACTS权限属于是危险权限了,这里我们在用户进行授权之后,调用readContacts()方法读取系统联系人信息.

  • readContacts()方法,可以看到使用了ContentResolver的query()方法查询系统的联系人数据

  • 不过传入的Uri参数并没有调用Uri.parse()方法去解析一个内容URI字符串,这是因为ContactsContract.CommonDataKinds.Phone类已经帮我们做好了封装,提供了一个CONTENT_URI常量,这个常量就是使用Uri.parse()方法解析出来的结果.

  • 接着我们对query()方法返回的Cursor对象进行遍历,这里使用了?.操作符和apply函数来简化遍历的代码

  • 在apply函数当中将联系人姓名和手机号逐个取出,联系人姓名这一列对应的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME

  • 联系人电话号码对应的常量是:ContactsContract.CommonDataKinds.Phone.NUMBER

  • 将两个数据取出来之后进行拼接,并且在中间加上换行符号,然后将拼接的数据放到ListView的数据源里面

  • 并且通知刷新一下ListView,最后千万不要忘记了Cursor对象的关闭

  • 最后我们还需要在AndroidManifest.xml文件当中声明读取联系人的权限

  • 所以总的来说在自己的程序当中访问其他程序当中的数据,还是比较简单的,只需要获得该应用程序的内容URI,然后借助ContentResolver进行增删改查就可以

创建自己的ContentProvider

创建ContentProvider的步骤

  • 想要实现一个跨程序数据共享,还可以就是自己写一个类取继承ContentProvider的方式来进行实现
  • ContentProvider类中共有6个抽象方法,我们在使用子类继承它的时候,需要将这六个方法全部进行重写.
class MyProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        TODO("Not yet implemented")
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        TODO("Not yet implemented")
    }

    override fun getType(uri: Uri): String? {
        TODO("Not yet implemented")
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        TODO("Not yet implemented")
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        TODO("Not yet implemented")
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        TODO("Not yet implemented")
    }
}
  • 需要的注意的就是标准URI的写法是这样的
content://com.example.app.provider/table1
  • 就表示我们期望访问的是com.example.app这个应用的table1表当中的数据
  • 还可以这样写
content://com.example.app.provider/table1/1
  • 这个就表示调用放期望访问的是com.example.app这个应用程序中table1表id为1的数据
  • 内容URI的格式主要就是上面两种,以路径为结尾表示期望访问该表当中的所有数据,以id为结尾表示期望访问该表当中拥有相应id的数据
  • 我们可以使用通配符的方式分别匹配这两种格式的内容URI,规则如下
    • *表示匹配任意长度的任意字符
    • #表示匹配任意长度的数字
  • 所以一个能够匹配任意表的内容URI格式就可以写成这个样子
content://com.example.app.provider/*
  • 一个能够匹配table1表中任意一行数据的内容URI就可以写成:
content://com.example.app.provider/table1/#
  • 接着再借助UriMatcher这个类就可以轻松的实现匹配内容URI的功能
  • UriMatcher中提供了addURI()方法,这个方法接收三个参数,分别把authority,path和一个自定义代码传进去
  • 这样当调用UriMatcher的match()方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所定义的自定义代码,利用这个代码我们就可以判断出调用方期望我们访问的是哪张表当中的数据了.
  • 修改MyProvider当中的代码,如下所示
class MyProvider : ContentProvider() {
    /**
     * 表示访问table1表中的所有数据
     */
    private val table1Dir = 0

    /**
     * 表示访问table1表中的单条数据
     */
    private val table1Item = 1
    private val table2Dir = 2
    private val table2Item = 3

    /**
     * 在MyProvider实例化的时候就创建UriMatcher实例
     */
    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)

    /**
     * 调用addURI()方法,将期望匹配的内容URI格式传递进去
     */
    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(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        when(uriMatcher.match(uri)) {
            table1Dir -> {
                //查询table1表中的所有数据
            }
            table1Item -> {
                //查询table1表当中的单条数据
            }
            table2Dir -> {
                //查询table2表中的所有数据
            }
            table2Item -> {
                //查询table2表当中的所有数据
            }
        }
    }

    override fun getType(uri: Uri): String? {
        TODO("Not yet implemented")
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        TODO("Not yet implemented")
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        TODO("Not yet implemented")
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        TODO("Not yet implemented")
    }
}
  • 在query()方法被调用的时候,就会通过UriMatcher的match()方法对传入的Uri对象进行匹配,如果发现UriMatcher中某个内容URI格式成功匹配了该Uri对象,则会返回相应的自定义代码,然后我们就可以判断出调用方法期望访问的到底是什么数据了.
  • 其他的一些方法(insert(),update(),delete())这几个方法的实现都是差不多的,它们都会携带uri参数,然后同样利用UriMatcher的match()方法判断出调用方法期望访问的是哪张表,再对该表中的数据进行操作就可以了
  • 在重写的几个方法当中有一个比较陌生的方法,getType()方法,它是所有的ContentProvider都必须提供的一个方法,用于获取Uri对象所对应的MIME类型,一个内容URI所对应的MIME字符串主要有3个部分组成,Android对这三个部分做了如下格式规定
  • 必须以vnd开头
  • 如果内容URI以路径结尾,则后接android.cursor.dir/;如果内容URI以id结尾,则后接android.cursor.item/
  • 最后街上vnd..
  • 所以对于content://com.example.app.provider/table1这个内容URI,它所对应的MIME类型就可以写成:
van.android.cursor.dir/vnd.com.example.app.provider.table1
  • 对于content://com.example.app.provider/table1/1这个内容URI,它所对应的MIME类型就可以写成
van.android.cursor.item/vnd.com.example.app.provider.table1
  • 所以现在就可以实现getType()方法当中的逻辑了,代码如下所示
    override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
        table1Dir -> "van.android.cursor.dir/vnd.com.example.app.provider.table1"
        table1Item -> "van.android.cursor.item/vnd.com.example.app.provider.table1"
        table2Dir -> "van.android.cursor.dir/vnd.com.example.app.provider.table2"
        table2Item -> "van.android.cursor.item/vnd.com.example.app.provider.table2"
        else -> null
    }
  • 现在我们就创建了一个完整的ContentProvider,现在任何一个应用程序都可以使用ContentProvider访问我们程序当中的数据了
  • 并且得益于ContentProvider的良好机制,能够保证隐私数据不会被泄露出去

实现跨程序数据共享

  • 在DatebaseTest项目当中创建一个DatebaseProvider
  • 修改DatebaseProvider当中的代码
class DatebaseProvider : ContentProvider() {
    private val bookDir = 0
    private val bookItem = 1
    private val categoryDir = 2
    private val categoryItem = 3
    private val authority = "com.zb.databasetest.provider"
    private var dbHelper: MyDatabaseHelper? = null

    /**
     * by lazy 懒加载机制对UriMatcher进行初始化操作
     */
    private val uriMather by lazy {
        val matcher = UriMatcher(UriMatcher.NO_MATCH)
        matcher.addURI(authority, "book", bookDir)
        matcher.addURI(authority, "book/#", bookItem)
        matcher.addURI(authority, "category", categoryDir)
        //代码块的最后一行代码,赋值给uriMather
        matcher
    }

    /**
     * 删除数据
     *
     * @param uri Uri
     * @param selection String?
     * @param selectionArgs Array<String>?
     */
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) =
        dbHelper?.let {
            //删除数据
            val db = it.writableDatabase
            val deletedRows = when (uriMather.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 -> 0
            }
            deletedRows
        } ?: 0

    override fun getType(uri: Uri) = when (uriMather.match(uri)) {
        bookDir -> "vnd.android.cursor.dir/vnd.com.zb.databasetest.provider.book"
        bookItem -> "vnd.android.cursor.item/vnd.com.zb.databasetest.provider.book"
        categoryDir -> "vnd.android.cursor.dir/vnd.com.zb.databasetest.provider.category"
        categoryItem -> "vnd.android.cursor.item/vnd.com.zb.databasetest.provider.category"
        else -> null
    }

    /**
     * 添加数据
     * @param uri Uri
     * @param values ContentValues?
     * @return Uri?
     */
    override fun insert(uri: Uri, values: ContentValues?) = dbHelper?.let {
        //添加数据
        val db = it.writableDatabase
        val uriReturn = when (uriMather.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/category/$newCategoryId")
            }
            else -> null
        }
        uriReturn
    }

    override fun onCreate() = context?.let {
        dbHelper = MyDatabaseHelper(it, "BookStore.db", 2)
        true
    } ?: false

    /**
     * 查询数据
     * @param uri Uri
     * @param projection Array<String>?
     * @param selection String?
     * @param selectionArgs Array<String>?
     * @param sortOrder String?
     * @return Cursor?
     */
    override fun query(
        uri: Uri, projection: Array<String>?, selection: String?,
        selectionArgs: Array<String>?, sortOrder: String?
    ) = dbHelper?.let {
        //查询数据
        val db = it.readableDatabase
        val cursor = when (uriMather.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
    }

    /**
     * 更新数据
     *
     * @param uri Uri
     * @param values ContentValues?
     * @param selection String?
     * @param selectionArgs Array<String>?
     */
    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array<String>?
    ) = dbHelper?.let {
        //更新数据
        val db = it.writableDatabase
        val updateRows = when (uriMather.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
}
  • 在类的一开始定义了四个变量,分别用于表示访问Book表中所有的数据,访问Book表中的单条数据,访问Category表中的所有/单挑数据
  • 然后在一个by lazy代码块里对UriMatcher进行了初始化操作,将希望匹配的几种URI格式添加进去
  • by lazy代码是Kotlin当中的一种懒加载技术,代码块中的代码一开始并不会执行,只有当uriMather变量首次被调用的时候才会执行,并且会将代码块中最后一行代码作为返回值赋给uriMather
  • 在onCreate()方法当中,调用了getContext()方法借助?.操作符和let函数判断它的返回值是否为空:如果为空就使用?:操作符返回fasle,表示ContentProvider初始化失败;如果不为空就执行let函数当中的代码.
  • 在let函数当中创建了一个MyDatebaseHelper的实例,然后返回true表示ContentProvider初始化成功.
  • 在query()方法当中先获取了SQLiteDatabase的实例,然后根据传入的Uri参数判断用户想要访问那种表,再调用SQLiteDatabase的query()进行查询,并将Cursor对象进行返回.
  • 然后就是insert()方法,它先是获取了SQLiteDatabase的实例,然后根据传入的Uri参数判断用户想要往那张表当中添加数据,再调用SQLiteDatabase的insert()方法进行添加就可以了.
  • 需要注意的就是insert方法需要返回一个能够表示这条新增数据的URI,所以我们还需要调用Uri.parse()方法,将一个内容URI解析成为Uri对象,当然这个内容URI是以新增数据的id结尾的.
  • 接下来就是update()方法,在这个方法当中也是先获取SQLiteDatabase的实例,然后根据传入的uri参数判断用户想要更新那张表当中的数据,再调用update()方法进行更新即可,受影响的行数将被作为返回值进行返回.
  • delete()方法,仍然是先获取SQLiteDatabase的实例,然后根据传入的uri参数判断用户想要删除那张表当中的数据,再调用SQLiteDatabase的delete()方法进行删除就可以啦,被删除的行数被作为返回值进行返回.
  • 还有一点需要注意的就是,ContentProvider一定要在AndroidManifest.xml文件中注册才可以使用,但是使用Android Studio的快捷方式注册的ContentProvider会自动帮我们进行注册,注册的形式如下所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LUJBZgEL-1671516835604)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221219215915047.png)]

  • 创建一个ProviderTest进行测试
  • 首先修改activity_main.xml当中的代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/addData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add To Book" />

    <Button
        android:id="@+id/queryData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Query From Book" />

    <Button
        android:id="@+id/updateData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Update Book" />

    <Button
        android:id="@+id/deleteData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete From Book" />

</LinearLayout>
  • 定义四个按钮
  • 在MainActivity当中编写按钮的点击事件,分别对应的就是增删改查的逻辑
class MainActivity : AppCompatActivity() {
    var bookId: String? = null

    @SuppressLint("Range")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        addData.setOnClickListener {
            //添加数据
            val uri = Uri.parse("content://com.zb.databasetest.provider/book")
            val values = contentValuesOf(
                "name" to "A Clash of Kings",
                "author" to "George Martin", "pages" to 1040, "price" to 22.5
            )
            val newUri = contentResolver.insert(uri, values)
            bookId = newUri?.pathSegments?.get(1)
        }
        queryData.setOnClickListener {
            //查询数据
            val uri = Uri.parse("content://com.zb.database")
            contentResolver.query(uri, null, null, null, null)?.apply {
                while (moveToNext()) {
                    val name = getString(getColumnIndex("name"))
                    val author = getString(getColumnIndex("author"))
                    val pages = getInt(getColumnIndex("pages"))
                    val price = getDouble(getColumnIndex("price"))
                    Log.d("MainActivity", "book name is $name")
                    Log.d("MainActivity", "book author is $author")
                    Log.d("MainActivity", "book pages is $pages")
                    Log.d("MainActivity", "book price is $price")
                }
                close()
            }
        }
        updateData.setOnClickListener {
            //更新数据
            bookId?.let {
                val uri = Uri.parse("content://com.zb.database.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.zb.databasetest.provider/book/$it")
                contentResolver.delete(uri, null, null)
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/103378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅拷贝深拷贝递归

常见的基本数据类型&#xff1a;Number、String 、Boolean、Null和Undefined 引用数据类型&#xff1a;Object、Array、Function 1&#xff09;基本数据类型&#xff1a;存储在栈内存中,可以直接访问到该变量的值。 2&#xff09;引用数据类型&#xff1a;存储在堆内存中,每…

有哪些数据统计软件适合初学者使用?

前段时间写过一篇“数据分析工具”的内容&#xff0c;周末有伙伴私信问我有没有什么适合初学者、业务人员的&#xff0c;更简单一点的数据可视化软件。 所以今天来分享下我在做数据分析时用过的几个简单易上手的数据可视化软件。 先放上目录&#xff1a; 数据统计收集类——简…

谷粒学院——Day12【整合阿里云短信服务、首页登录和注册】

用户登录业务介绍 一、单一服务器模式 早期单一服务器&#xff0c;用户认证。 缺点&#xff1a;单点性能压力&#xff0c;无法扩展。 二、SSO(single sign on)模式 分布式&#xff0c;SSO(single sign on)模式 优点&#xff1a; 用户身份信息独立管理&#xff0c;更好的…

关于安科瑞电气安全产品在医药工业洁净厂房的电气工程设计与应用

摘要&#xff1a; 近年来&#xff0c;医药工业洁净厂房的电气工程设计得到了快速发展和广泛关注&#xff0c;研究其相关课题有着重要意义。首先介绍了供电系统与配电设备的设置&#xff0c;分析了洁净厂房的电气照明设计&#xff0c;并结合相关实践经验&#xff0c;从探测器选…

智能无障碍轮椅——ESP8266总体介绍及ESP-01S入门调试

文章目录ESP8266 介绍ESP8266的多种型号1. DT-062. ESP-01和ESP-01S【左边ESP-01S&#xff0c;右边ESP-01】3. ESP-12F两种开发方式1. AT指令开发方式2. SDK开发方式固件烧录方法1. 硬件烧录工具2. 软件烧录工具WiFi模块工作模式&#xff1a;1. AP模式2. STA模式3. STAAP共存ES…

宏、条件编译(#ifdef)、#include(头文件包含)、#error和 #pragma的区别、#和##的含义和应用

1、在C语言预处理阶段&#xff0c;编译器首先对代码的处理时&#xff1a;先去注释&#xff0c;再宏替换。 2、在源文件的任何地方&#xff0c;宏都是可以定义的&#xff0c;与是否在函数内外无关。 3、宏的作用范围是&#xff1a;从定义处开始&#xff0c;往后的直到程序结束…

C++(老百科)

学了这么久的c,你们有没有想过一个问题:什么是c?(这还用问么,不大街上随便抓一个陌生人都知道)(那你现在给我抓个看看) C简介 C是一种计算机高级程序设计语言&#xff0c;由C语言扩展升级而产生 &#xff0c;最早于1979年由本贾尼斯特劳斯特卢普在AT&T贝尔工作室研发。––…

easyexcel读取excel将数据存到mysql【一个简单的例子】

读取excel 1 xml里面增加maven <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --> <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.1</version> </depen…

项目管理软件适用于初创公司吗

我注意到关于小型团队的一件事是&#xff0c;在这种热情的背后隐藏着一家需要帮助的公司。他们通常缺乏职责结构&#xff0c;但这可能不是他们的主要需求。太多的结构会扼杀那些在较小的初创型公司中经营节目的企业家精神的努力&#xff0c;但他们几乎总是需要更有效的流程和一…

分布式理论之CAP定理

CAP定理介绍 CAP定理&#xff08;CAP theorem&#xff09;&#xff0c;又被称作布鲁尔定理&#xff08;Brewers theorem&#xff09;&#xff0c;它指出对于一个分布式计算系统来说&#xff0c;不可能同时满足以下三点 选项具体意义一致性&#xff08;Consistency&#xff09…

linux TxBytes RxBytes 探究

测试平台 &#xff1a;NXP LS1043A ARM64 内核版本&#xff1a; 两台设备通过网口eth2 对接。设备1发包&#xff0c;设备2收包&#xff0c;观察两设备 TX RX 包数、字节数。 1、准备数据包 流量仪设置包长1024, 抓取从流量仪发包&#xff0c;wireshark显示length 1020字…

即时通讯音视频开发之音频编解码技术的学习

总是有人问我研究音频编解码要看什么书&#xff0c;其实这是一个很难回答的问题&#xff0c;原因有很多&#xff1a; 做工程首先一个问题就是和课本学习不同&#xff0c;不是看书能解决的。 音频编解码技术在国内研究的人很少&#xff0c;包括总体的音频技术国外也研究不多…

攻防世界ics-06

攻防世界ics-06 题目描述&#xff1a;云平台报表中心收集了设备管理基础服务的数据&#xff0c;但是数据被删除了&#xff0c;只有一处留下了入侵者的痕迹。 打开场景&#xff0c;查看页面。 但凡有超链接的都点一遍&#xff0c;发现只有“报表中心可以打开”。 选择日期范围&a…

go 库 viper 配置解析神器

文章目录1. 简介2. 安装3. 建立默认值4. 读取配置文件5. 获取 key/value 方法5.1 Get() 方法5.2 IsSet()、GetStringMap()、GetStringMap() 方法6. 命令行选项7. 访问嵌套的键8. 写入配置文件9. 监控并重新读取配置文件10. 从io.Reader中读取11. Unmarshal12. 环境变量13. 远程…

【Three.js入门】图形用户界面GUI、BufferGeometry创建矩形、随机生成多个随机颜色的三角形

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

第四章操作系统测试

一. 单选题&#xff08;共24 题&#xff0c;72.0分&#xff09; (单选题,3.0分)用户编写的程序中所使用的地址是&#xff08; C &#xff09;。 A. 内存地址 B. 物理地址 C. 逻辑地址 D. 绝对地址 解释&#xff1a;逻辑地址&#xff1a;用户空间中使用的一种地址又称相对地址 …

一行 Python 代码能实现什么丧心病狂的功能?

手头有 109 张头部 CT 的断层扫描图片&#xff0c;我打算用这些图片尝试头部的三维重建。基础工作之一&#xff0c;就是要把这些图片数据读出来&#xff0c;组织成一个三维的数据结构&#xff08;实际上是四维的&#xff0c;因为每个像素有 RGBA 四个通道&#xff09;。这个数据…

gitLab

GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的web服务。下面是使用步骤 1. 注册 进入注册页面&#xff08;需要公司的人提供地址&#xff09;例如&#xff1a; http://xx.xxx.xx.xx:18000/users/sign_in …

大漠插件最新版7.2248

工具名称:大漠插件最新版7.2248 工具简介:/ v7.2242更新时间2022年11月16日:/ v7.2248 1. 优化某些模式,在绑定时,有小概率会卡死在绑定函数里的问题. 2. 解决Assemble DisAssemble和GetRemoteApiAddress的COM版本的DLL&#xff0c;在E语言下用类库封装后调用时,对64位地址解析…

4.http模块

http模块是Node.js官方提供创建web服务器的模块&#xff0c;在使用http模块前首先导入http模块 目录 1 一些概念 1.1 IP 1.2 域名 1.3 端口 2 创建一个基本的web服务器 3 req请求对象 4 res响应对象 5 不同地址获取不同响应 6 在服务中加载html文件 6.1 基…