四大组件---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】 在内容解析者中通过调用提供者中的方法,在内容观察者中就能接收到