一、概述
Content Provider(内容提供者)是安卓四大组件之一,用于在不同应用之间共享数据的机制。它封装了数据源,并提供了一套标准的接口,允许其他应用以一种统一的方式访问和操作这些数据。其特点如下:
1.数据共享:Content Provider允许不同的应用共享数据。例如,一个应用可以通过Content Provider访问另一个应用的联系人、图片、视频等数据。
2.数据封装:Content Provider封装了底层的数据存储实现,可以是SQLite数据库、文件系统、网络资源等。应用只需通过Content Provider接口进行数据操作,而不需要关心数据的具体存储方式。
3.标准化接口:Content Provider使用一套标准的CRUD(Create、Read、Update、Delete)操作接口来访问数据,包括insert()、query()、update()和delete()方法。这些方法类似于SQL操作,开发者可以通过这些接口对数据进行增删改查。
使用Content Provider(内容提供者)可以让外部应用以安全的方式访问数据。它不仅允许程序访问另一个程序中的数据,也可以规定该程序能够访问哪些数据,因此能够避免隐私泄露。
但是,程序不能直接操作Content Provider,而是借助名为Content Resolver的类,如下图所示。通过Content Resolver B程序才能访问A程序暴露给Content Provider的数据。
除了Content Resolver外,使用Content Provider还涉及数据模型和URI。
1.数据模型
Content Provider 使用基于数据库模型的简单表格来提供需要共享的数据,在该表格中,每一行表示一条记录,而每一列代表特定类型和含义的数据,并且其中每一条数据记录都包含一个名为“_ID”的字段类标识每条数据。
2.URI
数据模型是一种类似于数据表的数据结构,是Content Provider的数据组织形式。因此,显然也存在一种增删改查机制,对数据进行处理。Content Resolver通过URI实现增删改查。URI(统一资源标识符,Uniform Resource Identifier)是一种用于标识互联网资源的字符串。它提供了一种标准化的方法来表示和访问网络资源。在这里,URI用于为Content Provider中的数据提供唯一标识。
二、创建Content Provider
可以通过Android Studio 创建Content Provider,类似于其它组件
在创建时必须指定URI,一般选取包名即可。(包名一般是公司域名取反,因此可确保唯一)
Content Provider的表现形式依然是安卓中的类。某个内容提供者需继承自Content Provider,并且要实现不同的方法。
on Create:创建内容提供者时触发
insert delete update query:当Content Resolver根据URI实现增删改查时触发的回调
get Type:返回某URI的MIME,即数据类型,例如txt、jpg等
三、访问其他应用程序
通过构建好的Content Provider获取数据;
(1)A程序通过Content Provider暴露自己的数据
(2)B程序通过Content Resolver对暴露的数据进行操作
实例:通过Content Provider获取联系人信息,假设有位联系人叫做“张三”,现在通过Content Provider获取其联系人信息
首先,创建一个Activity,当点击其中的按钮后,获取联系人信息
因为要读取手机中的联系人,所以需要获取对手机联系人的读取权限
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Demo01Activity">
<Button
android:id="@+id/btn_get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取联系人"
android:textSize="26sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
在主配置文件中声明联系人读取权限
在程序中也需要动态地获取读取权限
首先在点击按钮时,动态地获取联系人读取权限
package com.example.review05
import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.ContactsContract
import android.widget.Button
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class Demo01Activity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_demo01)
val btn = findViewById<Button>(R.id.btn_get)
btn.setOnClickListener {
// 检查是否已获得权限
if (ContextCompat.checkSelfPermission(
this, Manifest.permission.READ_CONTACTS
) == PackageManager.PERMISSION_GRANTED) {
// 权限已经被授予
getContacts()
} else {
// 权限没有被授予,请求权限
requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS), 1)
}
}
}
// 处理权限请求的回调
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被授予
getContacts()
} else {
// 权限被拒绝
// 可以提示用户权限被拒绝
}
}
}
// 获取联系人数据的逻辑
private fun getContacts() {
// 这里可以编写获取联系人信息的逻辑
}
//读取联系人信息
fun readContacts() {
val uri = Uri.parse("content://com.android.contacts/contacts")
val resolver = contentResolver
val cursor = resolver.query(uri,null,null,null,null)
if(cursor != null) {
while (cursor.moveToNext()) {
//处理查询结果
val idValue = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
val name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME))
println("id=${idValue}\tname=${name}")
}
cursor.close()
}
}
}
运行:
点击“获取联系人”之后:
四、内容观察者
目前,我们已经可以通过Content Resolver获取Content Provider中的数据。但是该过程是主动地读取数据。有没有一种方法,可以被动地读取数据,即数据变化时主动通知呢?
比如,我们开发了一款联系人管理工具,在联系人被修改时能够及时同步联系人信息。这时候就需要内容观察者 Content Observer。
举例:A程序通过Content Provider插入一条数据,并通知B程序
1)创建内容提供者 使用Android Studio创建一个内容提供者,并实现下述回调
创建内容提供者SQLite Helper类定义如下
2)在Activity中,向Content Provider插入数据
3)创建Content Observer
内容提供者提供了不同应用(进程)间分享数据的机制。并且定义了一套规范的数据操作接口。例如,用户只需知道有一个insert方法可以插入数据,并且向该方法传递待插入的数据即可,其并不关心数据是如何插入的,也不关心数据的具体组织形式。因此,使用内容提供者封装了数据操作的具体细节。