ContentProvider 主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许—个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。不同于文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,ContentProvider 可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
运行时权限
在程序运行时申请权限
AndroidManifest.xml
文件
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById
访问其他程序中的数据
ContentProvider 的基本用法
內容 URI 给 ContentProvider 中的数据建立了唯一标识符,它主要由两部分组成:authority 和 path。 authority 是用于对不同的应用程序做区分的,一般为了避免冲突,会采用应用包名的方式进行命名。比如某个应用的包名是 com.example.app
,那么该应用对应的 authority 就可以命名为 com.example.app.provider
。 path 则是用于对同一应用程序中不同的表做区分的,通常会添加到 authority 的后面。比如某个应用的数据库里存在两张表 table1 和 table2,这时就可以将 path 分别命名为 /table1 和 /table2,然后把 authority 和 path 进行组合,内容 URI 就变成了com.example.app.provider/table1
和 com.example.app.provider/table2
。不过,目前还很难辨认出这两个字符串就是两个内容 URI,我们还需要在字符串的头部加上协议声明。因此,内容URI 最标准的格式如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
// 解析成 Uri 对象
val uri = Uri.parse("content://com.example.app.provider/table1")
// 查询 table1 表中的数据
val cursor = contentResolver.query(
uri,
projection,
selection,
selectionArgs,
sortOrder
)
- uri:对应SQL部分:
from table_name
。指定查询某个应用程序下的某一张表 - projection:对应SQL部分:
select column1, column2
。指定查询的列名 - selection:对应SQL部分:
where column = value
。指定where的约束条件 - selectionArgs:为where中的占位符提供具体的值
- sortOrder:对应SQL部分:
order by column1, column2
。指定查询结果的排序方式
取出每一行中相应列的数据
while (cursor.moveToNext()) {
val column1 = cursor.getString(cursor.getColumnIndex("column1"))
val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()
添加一条数据
val values = contentValuesOf("column1" to "text", "column2" to 1)
contentResolver.insert(uri, values)
更新数据
val values = contentValuesOf("column1" to "")
contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", 1))
删除数据
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
读取系统联系人
AndroidManifest.xml
添加权限
class MainActivity : AppCompatActivity() {
private val contactList = ArrayList()
private lateinit var adapter: ArrayAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactList)
findViewById(R.id.listView).adapter = adapter
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_CONTACTS
) != PackageManager.PERMISSION_GRANTED
) {
// 申请授权。第三个参数为请求吗,只要是唯一值就可以了
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.READ_CONTACTS),
99999
)
} else {
readContacts()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
99999 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts()
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
@SuppressLint("Range")
private fun readContacts() {
// 查询联系人数据
contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
null,
null,
null
)?.apply {
while (moveToNext()) {
// 获取联系人姓名
val displayName =
getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
// 获取联系人手机号
val number =
getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
contactList.add("$displayName\n$number")
}
adapter.notifyDataSetChanged()
close()
}
}
}
创建自己的 ContentProvider
想要实现跨程序共享数据的功能,可以通过新建一个类去继承 ContentProvider 的方式来实现。
class MyProvider : ContentProvider {
// 初始化 ContentProvider 的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回 true 表示 ContentProvider 初始化成功,返回 false 则表示失败。
override fun onCreate(): Boolean {
return false
}
// 从 ContentProvider 中查询数据。uri 参数用于确定查询哪张表,projection 参数用于确定查询哪些列,selection 和 selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在 Cursor 对象中返回。
override fun query(
p0: Uri,
p1: Array?,
p2: String?,
p3: Array?,
p4: String?
): Cursor? {
return null
}
// 根据传人的内容 URI 返回相应的 MIME 类型。
override fun getType(p0: Uri): String? {
return null
}
// 向 ContentProvider 中添加一条数据。uri 参数用于确定要添加到的表,待添加的数据保存在 values 参数中。添加完成后,返回一个用于表示这条新记录的 URI。
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
return null
}
// 从 ContentProvider 中删除数据。uri 参数用于确定删除哪一张表中的数据,selection 和 selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。
override fun delete(p0: Uri, p1: String?, p2: Array?): Int {
return 0
}
// 更新 ContentProvider 中已有的数据。uri 参数用于确定更新哪一张表中的数据,新数据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array?): Int {
return 0
}
}
一个标准的内容 URI 写法是 content://com.example.app.provider/table1
。这就表示调用方期望访问的是 com.example.app
这个应用的 table1
表中的数据。
在内容 URI 的后面加上一个id:content://com.example.app.provider/table1/1
。表示调用方期望访问的是 com.example.app
这个应用的 table1
表中 id 为 1 的数据。
-
*
表示匹配任意长度的任意字符 -
#
表示匹配任意长度的数字
content://com.example.app.provider/*
:表示一个能够匹配任意表的内容URI格式。
content://com.example.app.provider/table1/#
:表示一个能够匹配 table1 表中任意一行数据的内容URI格式。
使用 UriMatcher 实现匹配内容 URI 的功能
class MyProvider : ContentProvider {
private val tableDir = 0
private val tableItem = 1
private val table2Dir = 2
private val table2Item = 3
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
init {
uriMatcher.addURI("com.example.app.provider", "table1", tableDir)
uriMatcher.addURI("com.example.app.provider", "table1/#", tableItem)
uriMatcher.addURI("com.example.app.provider", "table12", table2Dir)
uriMatcher.addURI("com.example.app.provider", "table12/#", table2Item)
}
// insert()、delete()、update()
override fun query(
p0: Uri,
p1: Array?,
p2: String?,
p3: Array?,
p4: String?
): Cursor? {
when (uriMatcher.match(p0)) {
tableDir -> {
// 查询 table1 表中的所有数据
}
tableItem -> {
// 查询 table1 表中的单条数据
}
table2Dir -> {
// 查询 table2 表中的所有数据
}
table2Item -> {
// 查询 table2 表中的单条数据
}
......
}
......
}
}
getType()
用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由3部分组成:
- 必须以
vnd
组成 - 如果内容 URI 以路径结尾,则后接
android.cursor.dir/
;如果内容 URI 以 id 结尾,则后接android.cursor.item/
。 - 最后接上
vnd.
。.
content://com.example.app.provider/table1
对应的 MIME 类型为:vnd.android.cursor.dir/vnd.com.example.app.provider.table1
content://com.example.app.provider/table1/1
对应的 MIME 类型为:vnd.android.cursor.item/vnd.com.example.app.provider.table1
getType() 方法中的逻辑:
override fun getType(p0: Uri) = when (uriMatcher.match(p0)) {
tableDir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
tableItem -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
else -> null
}
实现跨程序数据共享
创建一个 ContentProvider:右击->New->Other->Content Provider
-
Exported
:表示是否允许外部程序访问我们的 ContentProvider -
Enable
:表示是否启用这个 ContentProvider
创建完毕后 AndroidManifest.xml
内容如下:
DatabaseProvider
class DatabaseProvider : ContentProvider() {
private val bookDir = 0
private val bookItem = 1
private val categoryDir = 2
private val categoryItem = 3
private val authority = "com.gaowenli.databasetest.provider"
private var dbHelper: MyDatabaseHelper? = null
private val uriMatcher by lazy {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(authority, "book", bookDir)
matcher.addURI(authority, "book/#", bookItem)
matcher.addURI(authority, "category", categoryDir)
matcher.addURI(authority, "category/#", categoryItem)
matcher
}
override fun onCreate() = context?.let {
dbHelper = MyDatabaseHelper(it, "BookStore.db", 1)
true
} ?: false
override fun query(
uri: Uri, projection: Array?, selection: String?,
selectionArgs: Array?, sortOrder: String?
) = dbHelper?.let {
val db = it.readableDatabase
val cursor = when (uriMatcher.match(uri)) {
bookDir -> db.query("Book", projection, selection, selectionArgs, null, null, sortOrder)
bookItem -> {
val bookId = uri.pathSegments[1]
db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
}
categoryDir -> db.query(
"Category",
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.query(
"Category",
projection,
"id = ?",
arrayOf(categoryId),
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)) {
bookDir, bookItem -> {
val newBookId = db.insert("Book", null, values)
Uri.parse("content://$authority/book/$newBookId")
}
categoryDir, categoryItem -> {
val categoryId = db.insert("Category", null, values)
Uri.parse("content://$authority/category/$categoryId")
}
else -> null
}
uriReturn
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array?
) = dbHelper?.let {
val db = it.writableDatabase
val updatedRows = when (uriMatcher.match(uri)) {
bookDir -> db.update("Book", values, selection, selectionArgs)
bookItem -> {
val bookId = uri.pathSegments[1]
db.update("Book", values, "id = ?", arrayOf(bookId))
}
categoryDir -> db.update("Category", values, selection, selectionArgs)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.update("Category", values, "id = ?", arrayOf(categoryId))
}
else -> 0
}
updatedRows
} ?: 0
override fun delete(uri: Uri, selection: String?, selectionArgs: Array?) =
dbHelper?.let {
val db = it.writableDatabase
val deletedRows = when (uriMatcher.match(uri)) {
bookDir -> db.delete("Book", selection, selectionArgs)
bookItem -> {
val bookId = uri.pathSegments[1]
db.delete("Book", "id = ?", arrayOf(bookId))
}
categoryDir -> db.delete("Category", selection, selectionArgs)
categoryItem -> {
val categoryId = uri.pathSegments[1]
db.delete("Category", "id = ?", arrayOf(categoryId))
}
else -> 0
}
deletedRows
} ?: 0
override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
bookDir -> "vnd.android.cursor.dir/vnd.com.gaowenli.databasetest.provider.book"
bookItem -> "vnd.android.cursor.item/vnd.com.gaowenli.databasetest.provider.book"
categoryDir -> "vnd.android.cursor.dir/vnd.com.gaowenli.databasetest.provider.category"
categoryItem -> "vnd.android.cursor.item/vnd.com.gaowenli.databasetest.provider.category"
else -> null
}
}
MyDatabaseHelper
class MyDatabaseHelper(context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
private val createBook = "create table Book (" +
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text," +
"catetory_id integer)"
override fun onCreate(p0: SQLiteDatabase?) {
p0?.execSQL(createBook)
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
}
}
创建一个新的项目 ProviderTest
AndroidManifest.xml
添加 queries
访问共享程序中的数据
class MainActivity : AppCompatActivity() {
var bookId: String? = null
@SuppressLint("Range")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById
《第一行代码Android》
FileProvider 应用安装
AndroidManifest.xml
file_paths.xml
PermissionUtil
public class PermissionUtil {
// 检查多个权限。返回true表示已完全启用权限
public static boolean checkPermission(Activity activity, String[] permissions, int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int check = PackageManager.PERMISSION_GRANTED;
for (String permission : permissions) {
check = ContextCompat.checkSelfPermission(activity, permission);
if (check != PackageManager.PERMISSION_GRANTED) {
break;
}
}
if (check != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
return false;
}
}
return true;
}
}
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String[] PERMISSIONS = new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE
};
private static final int PERMISSION_REQUEST_CODE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
installApk();
break;
}
}
@RequiresApi(api = Build.VERSION_CODES.R)
private void checkAndInstall() {
// 检查是否拥有 MANAGE_EXTERNAL_STORAGE 权限,没有则跳转到设置页面
if (!Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.fromParts("package", getPackageName(), null));
} else {
installApk();
}
}
private void installApk() {
String apkPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/app-release.apk";
// 获取应用包管理器
PackageManager packageManager = getPackageManager();
// 获取apk文件的包信息
PackageInfo info = packageManager.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
if (info == null) { // 安装文件损坏
return;
}
Uri uri = Uri.parse(apkPath);
// 兼容Android7.0,把访问文件的Uri方式改为FileProvider
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 通过FileProvider获得文件的Uri访问方式
uri = FileProvider.getUriForFile(this, "com.example.mycontentprovider.fileProvider", new File(apkPath));
}
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 设置Uri的数据类型为APK文件
intent.setDataAndType(uri, "application/vnd.android.package-archive");
// 启动系统自带的应用安装程序
startActivity(intent);
}
@Override
public void onClick(View v) {
// Android11之后获取 MANAGE_EXTERNAL_STORAGE 权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
checkAndInstall();
} else {
// 如果有权限,直接安装,没有权限则获取权限
if (PermissionUtil.checkPermission(this, PERMISSIONS, PERMISSION_REQUEST_CODE)) {
installApk();
}
}
}
}