四大组件---ContentProvider

343 阅读3分钟

四大组件---ContentProvider

为什么要有内容提供者?

需求:一个应用去读取另一个应用中的数据库?

解决方案:使用内容提供者,把私有的数据库暴露出来

一、概念

【1】 内容提供者把数据进行封装然后提供出来,其他应用都是通过内容解析者访问

二、实现步骤

【1】 定义一个类,继承ContentProvider,在清单文件中注册,注意配置authorities和exported

<provider
        android:name=".AccountProvider"
        android:authorities="com.wzh.provider"
        android:exported="true" />

【2】 定义一个UriMatch

private val uriMatch: UriMatcher = UriMatcher(UriMatcher.NO_MATCH)

【3】 定义静态代码块,添加匹配规则

companion object {
    private val URI_MATCH: UriMatcher = UriMatcher(UriMatcher.NO_MATCH)
    private const val QUERY_SUCCESS = 1

    init {
        /**
         * authority:   与清单文件中定义的要一致
         * path:    路径
         * code:	自定义的返回值
         */
        URI_MATCH.addURI("com.wzh.provider", "accountQuery", QUERY_SUCCESS)
    }
}

【4】 暴露想要暴露的方法(增删改查),在对应的方法中实现数据库的增删改查等方法

@Override
override fun onCreate(): Boolean {
    val accountOpenHelper = AccountOpenHelper(context)
    database = accountOpenHelper.writableDatabase
    return false
}


@Override
override fun query(
    uri: Uri,
    projection: Array<String>?,
    selection: String?,
    selectionArgs: Array<String>?,
    sortOrder: String?
): Cursor? {
    val code = URI_MATCH.match(uri)
    // 说明路径匹配成功
    return if (code == QUERY_SUCCESS) {
        database.query(
            "accountInfo",
            projection,
            selection,
            selectionArgs,
            null,
            null,
            sortOrder
        )
    } else null
}

【5】 在其他应用中,通过获取getContentResolver方法,调用增删改查的方法即可

fun query(v: View) {
    val uri = Uri.parse("content://com.wzh.provider/accountQuery")
    val cursor = contentResolver.query(uri, null, null, null, null)

    if (cursor != null) {
        while (cursor.moveToNext()) {
            val name = cursor.getString(1)
            val age = cursor.getInt(2)
            println("$name---$age")
        }
    }
    cursor?.close()
}
注意uri的写法,协议头是content://

【6】 内容提供者代码

class AccountProvider : ContentProvider() {

private lateinit var database: SQLiteDatabase

private val tableName = "accountInfo"

companion object {
    private val URI_MATCH: UriMatcher = UriMatcher(UriMatcher.NO_MATCH)
    // 主机
    private const val AUTHORITY = "com.wzh.provider"

    // 查询的code
    private const val QUERY_SUCCESS = 1
    private const val INSERT_SUCCESS = 2
    private const val UPDATE_SUCCESS = 3
    private const val DELETE_SUCCESS = 4

    init {
        URI_MATCH.addURI(AUTHORITY, "accountQuery", QUERY_SUCCESS)
        URI_MATCH.addURI(AUTHORITY, "hahaInsert", INSERT_SUCCESS)
        URI_MATCH.addURI(AUTHORITY, "update", UPDATE_SUCCESS)
        URI_MATCH.addURI(AUTHORITY, "delete", DELETE_SUCCESS)
    }
}

@Override
override fun onCreate(): Boolean {
    val accountOpenHelper = AccountOpenHelper(context)
    database = accountOpenHelper.writableDatabase
    return false
}

/**
 * 插入数据
 */
@Override
override fun insert(uri: Uri, values: ContentValues?): Uri? {
    // 获取uri匹配的code
    val code = URI_MATCH.match(uri)
    if (code == INSERT_SUCCESS) {
        val insert = database.insert(tableName, "id", values)
        return Uri.parse("com.wzh.insert/$insert")
    }
    return null
}

/**
 * 查询数据
 */
@Override
override fun query(
    uri: Uri,
    projection: Array<String>?,
    selection: String?,
    selectionArgs: Array<String>?,
    sortOrder: String?
): Cursor? {
    val code = URI_MATCH.match(uri)
    // 说明路径匹配成功
    return if (code == QUERY_SUCCESS) {
        database.query(
            tableName,
            projection,
            selection,
            selectionArgs,
            null,
            null,
            sortOrder
        )
    } else null
}

/**
 * 更新数据
 */
@Override
override fun update(
    uri: Uri,
    values: ContentValues?,
    selection: String?,
    selectionArgs: Array<String>?
): Int {
    val code = URI_MATCH.match(uri)
    return if (code == UPDATE_SUCCESS) {
        database.update(tableName, values, selection, selectionArgs)
    } else 0
}

/**
 * 删除数据
 */
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
    val code = URI_MATCH.match(uri)
    return if (code == DELETE_SUCCESS) {
        database.delete(tableName, selection, selectionArgs)
    } else 0
}

override fun getType(uri: Uri): String? {
    return null
}
}

【7】 内容解析者代码

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}

fun query(v: View) {
    val uri = Uri.parse("content://com.wzh.provider/accountQuery")
    val cursor = contentResolver.query(uri, null, null, null, null)

    if (cursor != null) {
        while (cursor.moveToNext()) {
            val name = cursor.getString(1)
            val age = cursor.getInt(2)
            println("$name---$age")
        }
    }
    cursor?.close()
}

fun insert(v: View) {
    val contentValues = ContentValues()
    contentValues.put("name", "王五")
    contentValues.put("age", 44)
    contentResolver.insert(Uri.parse("content://com.wzh.provider/hahaInsert"), contentValues)
}

fun update(v: View) {
    val uri = Uri.parse("content://com.wzh.provider/update")
    val contentValues = ContentValues()
    contentValues.put("name", "街头的七号")
    contentValues.put("age", 12)
    val num = contentResolver.update(uri, contentValues, "id=?", arrayOf("2"))
    Toast.makeText(this, "影响了${num}行", Toast.LENGTH_SHORT).show()
}

fun delete(v: View) {
    val uri = Uri.parse("content://com.wzh.provider/delete")
    val num = contentResolver.delete(uri, "name=?", arrayOf("张三"))
    Toast.makeText(this, "影响了${num}行", Toast.LENGTH_SHORT).show()
}

}

三、系统内容提供者例子

【1】 短信数据库

android 7.0以前,该数据库放在data/data/com.android.providers.Telephony/databases/mmssms.db

android 7.0之后,该数据库放在data/user_de/userid/com.android.providers.Telephony/databases/mmssms.db

查看的是sms表

实现步骤

① 在清单文件中声明读取短信的权限

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

② 通过内容解析者读取短信数据库,在系统上层应用的源码中,查看TelephonyProvider中的清单文件,查找SmsProvider,获取android:authorities="sms",然后在对应的java文件中,查看对应的UriMartch即可

val uri = Uri.parse("content://sms/")
val cursor =
    contentResolver.query(uri, arrayOf("address", "body", "date"), null, null, null)
if (cursor != null && cursor.count > 0) {
    while (cursor.moveToNext()) {
        val address = cursor.getString(0)
        val body = cursor.getString(1)
        val date = cursor.getLong(2)
        println("$address---$body---$date")
    }
    cursor.close()
}

③ 备份短信数据库到xml

fun backupSms(v: View) {
    // 写入xml
    val xmlSerializer = Xml.newSerializer()
    val file = File(Environment.getExternalStorageDirectory().absolutePath, "smsBackup.xml")
    val fos = FileOutputStream(file, false)
    xmlSerializer.setOutput(fos, "utf-8")

    xmlSerializer.startDocument("utf-8", true)
    // 写入根节点
    xmlSerializer.startTag(null, "smss")

    val uri = Uri.parse("content://sms/")
    val cursor =
        contentResolver.query(uri, arrayOf("address", "body", "date"), null, null, null)
    if (cursor != null && cursor.count > 0) {
        while (cursor.moveToNext()) {
            val address = cursor.getString(0)
            val body = cursor.getString(1)
            val date = cursor.getLong(2)

            xmlSerializer.startTag(null,"sms")

            xmlSerializer.startTag(null, "address")
            // 写入内容
            xmlSerializer.text(address)
            xmlSerializer.endTag(null, "address")

            xmlSerializer.startTag(null, "body")
            xmlSerializer.text(body)
            xmlSerializer.endTag(null, "body")

            xmlSerializer.startTag(null, "date")
            xmlSerializer.text(date.toString())
            xmlSerializer.endTag(null, "date")

            xmlSerializer.endTag(null,"sms")
        }
        cursor.close()
    }
    xmlSerializer.endTag(null, "smss")
    xmlSerializer.endDocument()
}

【2】 联系人数据库

android 7.0以前,该数据库放在data/data/com.android.providers.contacts/databases/constacts2.db

android 7.0之后,该数据库放在data/user/userid/com.android.providers.contacts/databases/constacts2.db

三张重要的表

1.data表
  data1字段里面存的是所有联系人的信息
  raw_contact_id字段存的是raw_contact表中的contact_id
	读取contact_id字段,从而获取到手机中有几条联系人
  mimetype_id字段表示联系人存的字段类型
	读取mimetype_id字段,从而获取到信息的类型
2.raw_contacts表
  
3.mimetypes表

实现步骤

① 思路:先查询raw_contacts,获取对应的contact_id,从而获取有多少个联系人,再根据查询到的contact_id,查询data表中的data1字段和mimetype_id字段

② 使用contentResolver,调用query方法查询,查看系统上层应用源码,获取contactProvider2中的uri配置信息,还需要配置权限

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

③ 查询raw_contacts是

uri="content://com.android.contacts/raw_contacts"

④ 接着查询data表

uri="content://com.android.contacts/data"
这里有个小细节就是,系统实际上查询的是view_data这张表,可以通过打印列的名称来验证,所以在这里是查不到mimetype_id这个字段的,只能查到mimetype字段

④ 代码实现

/**
 * 获取联系人集合
 */
fun getContactList(context: Context): List<ContactInfo>? {
    val contentResolver = context.contentResolver;
    // 先获取raw_contacts表中的人数
    val uri = Uri.parse("${ContactsContract.AUTHORITY_URI}/raw_contacts")
    val rawContactCursor =
        contentResolver.query(uri, arrayOf("contact_id"), null, null, null)

    return if (rawContactCursor != null && rawContactCursor.count > 0) {
        val contactInfos = mutableListOf<ContactInfo>()
        // 遍历获取有多少个联系人
        while (rawContactCursor.moveToNext()) {
            // 获取联系人id
            val rawContactId = rawContactCursor.getString(0)
            if (rawContactId.isNotEmpty()) {
                // 查询data表
                val contactInfo = getContactById(context, rawContactId)
                if (contactInfo != null) contactInfos.add(contactInfo)
            }
        }
        rawContactCursor.close()
        contactInfos
    } else null
}

/**
 * 根据rawContactId获取对应的联系人
 */
private fun getContactById(context: Context, rawContactId: String): ContactInfo? {
    val uri = Uri.parse("${ContactsContract.AUTHORITY_URI}/data")
    val cursor = context.contentResolver.query(
        uri,
        arrayOf("data1", "mimetype"),
        "raw_contact_id=?",
        arrayOf(rawContactId),
        null
    )
    return if (cursor != null && cursor.count > 0) {
        val contactInfo = ContactInfo()
        while (cursor.moveToNext()) {
            val data1 = cursor.getString(0)
            val mimetype = cursor.getString(1)
            if (mimetype == "vnd.android.cursor.item/name") {// 名字
                contactInfo.contactName = data1
            } else if (mimetype == "vnd.android.cursor.item/phone_v2") {// 电话
                contactInfo.contactPhone = data1
            } else if (mimetype == "vnd.android.cursor.item/email_v2") {// 邮件
                contactInfo.contactEmail = data1
            }
        }
        cursor.close()
        contactInfo
    } else null
}

四、内容观察者

【1】 在另一个应用中,通过contentResolver注册内容观察者

/**
 * uri:Uri
 * notifyForDescendants:如果是false的话,则会精确匹配uri
 */
val uri = Uri.parse("content://com.wzh.provider")
contentResolver.registerContentObserver(uri, true, object : ContentObserver(Handler()) {
    override fun onChange(selfChange: Boolean) {
        println("数据库已改变")
    }
})

【2】 在内容提供者中的增删改查中,发送通知

// 发送内容观察者的通知
context?.contentResolver?.notifyChange(uri, null)

【3】 在内容解析者中通过调用提供者中的方法,在内容观察者中就能接收到