# ContentProvider 必会必知

本节给大家带来的是 Android 四大组件中的最后一个 ——ContentProvider (内容提供者),可能部分读者 有疑问了,说到这个 ContentProvider,我们什么时候 会用到他呢?有下面这两种:

  • **1.** 我们想在自己的应用中访问别的应用,或者说一些 ContentProvider 暴露给我们的一些数据, 比如手机联系人,短信、相册等!我们想对这些数据进行读取或者修改,这就需要用到 ContentProvider 了!
  • **2.** 我们自己的应用,想把自己的一些数据暴露出来,给其他的应用进行读取或操作,我们也可以用到 ContentProvider,另外我们可以选择要暴露的数据,就避免了我们隐私数据的的泄露!

好像好流弊的样子,其实用起来也很简单

ContentProvider

# 权限申请

从 android6.0 开始,凡是涉及用户隐私的权限 (读写短信,读写联系人,拍摄,录音等等),都需要运行时申请,弹窗提醒用户是否授权。用户不授权则无法继续操作,而且今年工信部对于违规收集,申请用户权限的 APP 查的非常严格,不定期抽查,抽查有问题的必须按期整改,否则强制下架。

# 首先在 AndroidManifest.xml 中声明读取短信的权限

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

# 运行时动态申请权限,请求用户授权

  • ActivityCompat.checkSelfPermission ():检查权限是否已授权,如果没授权则需要向用户申请
  • ActivityCompat.requestPermissions ():发起权限申请,会弹出对话框permission
  • ActivityCompat.shouldShowRequestPermissionRationale (): 检查用户是否已经永久拒绝,如果用户已永久拒绝某个权限的申请,即便再调用 ActivityCompat.requestPermissions,系统也不会弹框向用户申请了。此时需要自己弹对话框,引导用户去开启授权
  • onRequestPermissionsResult:处理权限授权的结果
  • 完整的权限申请案例
class PermissionActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (ActivityCompat.checkSelfPermission(this,
                android.Manifest.permission.READ_SMS
            ) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions( this, arrayOf(android.Manifest.permission.READ_SMS),100)
        } else {
            // 如果已授权则可以直接读取通讯录了
            getContacts()
        }
    }
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
         // 处理权限申请的结果
        if (requestCode==100&&permissions[0]== android.Manifest.permission.READ_SMS){
            if (grantResults[0]==PackageManager.PERMISSION_GRANTED){
                 // 如果请求结果是授权了,则可以继续操作
                 getContacts()
            }else{
                // 如果本次申请权限,未得到授权,则 toast 提示,终止读取的操作。
                Toast.makeText(this, "通讯录权限被拒绝,无法读取联系人", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

# 读取通讯录联系人

表名说明
content://com.android.contacts/data/phones读取联系人的表的名字
字段说明
------------------
display_name用户名
Data1手机号
fun getContacts() {
        //①查询 raw_contacts 表获得联系人的 id
        val resolver = contentResolver
        val uri =Uri.parse("content://com.android.contacts/data/phones")
        // 查询联系人数据
        val cursor = resolver.query(uri, null, null, null, null)!!
        while (cursor.moveToNext()) {
            // 获取联系人姓名,手机号码
            val cName: String =
                cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
            val cNum: String =
                cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
            Log.e("ContentProvider", "姓名:$cName")
            Log.e("ContentProvider", "号码:$cNum")
            Log.e("ContentProvider", "======================")
        }
        cursor.close()
    }

运行结果:

姓名:china unicom
号码:10010
======================
姓名:zhang san
号码:10086

# 通信录插入联系人

  • AndroidManifest.xml 声明权限
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
  • 插入联系人
表名说明
content://com.android.contacts/data/data插入联系人的表的名字
content://com.android.contacts/data/raw_contacts插入联系人的原始表的名字
fun insertContact() {
        val values = ContentValues()
        /*
         * 首先向 RawContacts.CONTENT_URI 执行一个空值插入,目的是获得系统返回的 rawContactId
         * 这时后面插入 data 表的数据,才能使插入的联系人在通讯录里面可见
         */
        val rawContactUri: Uri = contentResolver!!.insert(RawContacts.CONTENT_URI, values)!!
        val rawContactId = ContentUris.parseId(rawContactUri)
        // 往 data 表里写入姓名数据
        values.clear()
        values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
        values.put(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) // 内容类型
        values.put(StructuredName.GIVEN_NAME, "李四")
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, values)
        // 往 data 表里写入电话数据
        values.clear()
        values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
        values.put(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
        values.put(Phone.NUMBER, "13921009789")
        values.put(Phone.TYPE, Phone.TYPE_MOBILE)
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, values)
        // 往 data 表里写入 Email 的数据
        values.clear()
        values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId)
        values.put(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
        values.put(Email.DATA, "lisi@qq.com")
        values.put(Email.TYPE, Email.TYPE_WORK)
        contentResolver
            .insert(ContactsContract.Data.CONTENT_URI, values)
    }

# 更新联系人信息

  • 根据手机号获取联系人在通讯录的 contact_id
fun getContactIdByPhone(phone:Long): String {
        val uri =Uri.parse("content://com.android.contacts/data/phones/filter/$phone")
        val cursor = contentResolver.query(
            uri,
            arrayOf(ContactsContract.Data.CONTACT_ID),
            null,
            null,
            null
        )!! 
        if (cursor.moveToFirst()) {
            return cursor.getString(0)
        }
        return ""
    }
  • 更新联系人的姓名
fun update(){
   val contact_id = getContactIdByPhone(100001)
   val values = ContentValues()
   values.put(ContactsContract.Data.MIMETYPE,
            ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
        ) // 内容类型
        values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "lisi")
        contentResolver.update(
            Uri.parse("content://com.android.contacts/data"),
            values,
            "${ContactsContract.Data.CONTACT_ID}=?",
            arrayOf(contact_id)
        )
}

# 删除联系人

  • 根据姓名删除联系人
contentResolver.delete(RawContacts.CONTENT_URI,CommonDataKinds.Phone.DISPLAY_NAME+"=?", arrayOf("test"));
  • 根据手机号删除联系人
val contact_id = getContactIdByPhone(1111)
contentResolver.delete(RawContacts.CONTENT_URI,CommonDataKinds.Phone.CONTACT_ID+"=?", arrayOf(contact_id));

# 读取收件箱所有短信

字段说明
address发件人地址,即手机号,如 + 8613811810000
person发件人地址,即手机号,如 + 8613811810000
date日期,long 型,如 1256539465022,可以对日期显示格式进行设置
protocol协议 0 :SMS_RPOTO 短信,1:MMS_PROTO 彩信
read是否阅读 0: 未读,1: 已读
type短信类型 1: 接收到的短信,2: 发出的短信
body短信具体内容
fun getMsgs() {
        val uri: Uri = Uri.parse("content://sms/")
        val resolver = contentResolver
        // 获取的是哪些列的信息
        val cursor: Cursor = resolver.query(
            uri,
            arrayOf("address", "date", "type", "body"),
            null,
            null,
            null
        )!!
        while (cursor.moveToNext()) {
            val address: String = cursor.getString(0)
            val date: String = cursor.getString(1)
            val type: String = cursor.getString(2)
            val body: String = cursor.getString(3)
            Log.e("ContentProvider", "地址:$address")
            Log.e("ContentProvider", "时间:$date")
            Log.e("ContentProvider", "类型:$type")
            Log.e("ContentProvider", "内容:$body")
            Log.e("ContentProvider", "======================")
        }
        cursor.close()
    }

note

短信相关的其它操作 uri

content://sms/ 所有短信

content://sms/inbox 收件箱

content://sms/sent 已发送

content://sms/draft 草稿

content://sms/outbox 发件箱

content://sms/failed 发送失败

content://sms/queued 待发送列表

运行结果:

收件人:10010
发送时间:Mon Jun 07 22:13:50 GMT+08:00 2021
短信内容:I'm Android ,I want. to. send message to you 
======================
收件人:10086
发送时间:Mon Jun 07 22:13:53 GMT+08:00 2021
短信内容:this message is send to 10086 by Android

note

我们能不能运行时往收件箱里写入一条短信呢?

从 5.0 开始第三方 APP 无法插入短信,所以死了这条心吧