ContentProvider简单上手|青训营笔记

267 阅读6分钟

ContentProvider简单上手|青训营笔记

这是我参与[第四届青训营]笔记创作活动的第5天

前言

接着我们就来看看ContentProvider(内容提供者),作为Android四大组件之一的它,又有着怎样的作用,它的作用场景又在哪里❔带着这些疑问我们来简单学习一下ContentProvider❗️❗️

本文会从一下几个角度来介绍内容提供者的基础知识,好吧,接下来我们开始吧⬇️

  1. 基本概念
  2. 系统的ContentProvider
  3. 自定义ContentProvider

1.ContentProvider的基本概念

内容提供商是Android应用程序的主要构建块之一,为应用程序提供内容。并提供一种与其他应用程序共享数据的方法。它们封装数据,并提供定义数据安全性的机制。内容提供程序是将一个进程中的数据与另一个进程中运行的代码连接起来的标准接口,例如,联系人数据由多个应用程序使用,并且必须存储在内容提供程序中(官方

简单来说就是,两个应用程序之间需要互相访问其数据时,而提供的一个数据访问接口。而提供数据者的应用者会实现ContentProvider所提供的CRUD操作,并提供唯一标识的URI接口供访问者访问数据。

好处:既保证被访问程序的数据内部安全,不会被访问者随意篡改数据,也让两个应用程序之间的数据交流成为可能。或许大家还有些不懂,那就看图吧,相信看见此图,你就理解了😄

image-20220809150853779.png

这里又涉及到了一个新名词--URI

URI叫做统一资源标识符,你可以将它类比为网页的URL,这个就是用于定位我们访问的数据所处的路径。

举个例子吧,进一步理解URI的作用,例如

content://com.bytedance.provider/user/1

content:URI前缀,协议头类似于http,ftp一样规定的,对于contentprovider规定的就是content开头

com.bytedance.provider:自定义contentprovider唯一标识,也就是provider所在的权限定类名

user:自定义的数据库表名,代表着你想访问的user资源

1:表明你想访问的user中id为1的资源

2.系统提供的ContentProvider

了解了ContentProvider的作用,接下来我们就来敲一敲代码,调用系统的ContentProvider来读取一下系统app,信息,联系人,多媒体信息吧❗️

例如读取短信信息,首先我们先在AndroidManifest.xml中加入读取短信信息的权限

 <uses-permission android:name="android.permission.READ_SMS"/>

由于读取短信信息权限属于危险权限所以我们需要进行运行时授权,以及访问相关短信的代码如下

 class MainActivity : AppCompatActivity() {
 ​
     private lateinit var binding:ActivityMainBinding
     private val TAG:String = "readMessage"
 ​
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_main)
         //绑定控件
         binding = ActivityMainBinding.inflate(layoutInflater)
         val view = binding.root
         setContentView(view)
         //获取短信信息按钮
         binding.readMessage.setOnClickListener {
            //判断是否活动相关权限
           if(ContextCompat.checkSelfPermission(this,android.Manifest.permission.READ_SMS)
                 != PackageManager.PERMISSION_GRANTED){
             //则获取相关权限
                 ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.READ_SMS),1)
             }else{
                 getMsgs()
             }
         }
     }
     //根据用户是否授权来进行其他操作
     override fun onRequestPermissionsResult(
         requestCode: Int,
         permissions: Array<out String>,
         grantResults: IntArray
     ) {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults)
         when(requestCode){
             1 -> {
                 if (grantResults.isEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
                     getMsgs()
                 else{
                     Toast.makeText(this, "你需要获取相应权限", Toast.LENGTH_SHORT).show()
                 }
             }
         }
     }
     //获取短信信息
     private fun getMsgs() {
         val uri:Uri = Uri.parse("content://sms/")
         val resolver:ContentResolver = contentResolver;
         val cursor = resolver.query(uri, arrayOf("address","date","type","body"),
             null,null,null)
         var flag = 0
         while (cursor?.moveToNext() == true && flag != 4){
             var address = cursor.getString(0);
             var date = cursor.getString(1);
             var type = cursor.getString(2);
             var body = cursor.getString(3);
             flag++
             Log.d(TAG, "地址 $address")
             Log.d(TAG, "时间 $date")
             Log.d(TAG, "类型 $type")
             Log.d(TAG, "内容 $body")
             Log.d(TAG, "==========================")
         }
         cursor?.close()
     }
 }

接下来运行,并授予相关权限,就可以在日志中看见如下信息了。

image-20220809162402900.png

看是不是挺简单的,接下来我们就来自己定义一下自己的ContentProvider吧

3.自定义ContentProvider

说道自定义ContentProvider,会有一些麻烦,需要用到数据库,这里如果有人还不会SQL的话,建议先把基础的增删改查看看,那么接下来你就不至于看不懂了🛩

首先简单创建一个DBHelper类去继承SQLiteOpenHelper这个抽象类,并创建一个user表

 class DBHelper(val context: Context, name: String, version: Int) :
     SQLiteOpenHelper(context,name,null,version) {
     //建表SQL语句
     private val createUser = "create table User ( id integer primary key autoincrement," +
             "username text," +
             "age integer," +
             "address text," +
             "number text)"
      //建表 
     override fun onCreate(db: SQLiteDatabase) {
         db.execSQL(createUser)
         Toast.makeText(context, "用户表创建成功", Toast.LENGTH_SHORT).show()
     }
     //升级数据库操作
     override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
         TODO("Not yet implemented")
     }
 }

接下来就是重点了,创建自定义的ContentProvider,核心代码如下

 private val authority =  "com.bytedance.contentprovider"
 private var dbHelper: DBHelper? = null
 //初始化uriMatcher,当调用uriMatcher时进行加载
 private val uriMatcher by lazy {
      val matcher = UriMatcher(UriMatcher.NO_MATCH)
      matcher.addURI(authority,"user",1)
      matcher
 }
 //创建数据库
 override fun onCreate() = context?.let {
         dbHelper = DBHelper(it,"test.db",1)
         true
 } ?: false
 //查询操作
 override fun query(
         uri: Uri,
         projection: Array<out String>?,
         selection: String?,
         selectionArgs: Array<out String>?,
         sortOrder: String?
     ) = dbHelper?.let {
         val db = it.readableDatabase
         val cursor = when(uriMatcher.match(uri)){
             1 -> db.query("user",projection,selection,selectionArgs,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)){
             1 -> {
                 val newUserId = db.insert("User",null,values)
                 Uri.parse("content://$authority/user/$newUserId")
             }
             else -> null
         }
         uriReturn
 }
 //获取uri的MIME类型
 override fun getType(uri: Uri) = when(uriMatcher.match(uri)){
         1 -> "vnd.android.cursor.dir/vnd.com.bytedance.contentprovider.user"
         else -> null
 }

另外的delete和update就不在这里暂时了,这里主要演示如何添加以及查询,代码编写好了,接下来就让程序运行起来,效果如下

image-20220809200300017.png

首先我们添加了一个用户它的id为6,并获取到它的URI的MIME类型字符串,并将查询的结果也展示出来了,我们可以尝试着多次点击,看看效果如何😆

image-20220809200506081.png

效果不错,但我相信大家可能还不够了解代码中的一些函数具体的作用,那么接下来我就来一一介绍一下吧,让大家加深理解。

onCreate()

用于初始化ContentProvider时调用,通常会在这完成数据库的创建和升级操作,返回true则表明创建成功,反之失败。

query()

从ContentProvider中查询数据,uri参数用来确定查询哪张表,projection参数确定查询那些列,selection参数确定其约束条件比如 id > a,name = “b”,selectionArgs则是用于配合selection参数,确认a和b具体的值,sortOrder参数用于对结果的排序。最后查询的结果放在cursor中返回

insert()

从ContentProvider中添加数据,uri参数用来确定添加到哪张表,添加的数据保存在values中,添加成功后,返回表示这条记录的uri

update()

从ContentProvider中更新数据,uri参数用来确定更新哪张表的数据,数据依旧保存在values中,selection和selectionArags作用跟query()中一样,最后返回更新的数据行数。

delete()

从ContentProvider中删除数据,uri参数用来确定删除哪张表的数据,selection和selectionArags作用跟query()中一样,最后返回删除的数据行数

getType()

根据传入的URI返回相应的MIME类型

最后

至此,我们就将内容提供者简单介绍完了,更详细的内容可参考一下文章,本文所写的是本人根据所参考资料,以及网上所学的个人见解,如有错误或者不恰当之处,欢迎私信我,加以改正!同时期待您的关注,感谢您的阅读,谢谢!

参考

ContentProvider | Android Developers (google.cn)

内容提供程序 | Android 开发者 | Android Developers (google.cn)

ContentProvider的使用 - 掘金 (juejin.cn)