ContentProvider
以下文档汇总了 Android ContentProvider 的核心概念、注册与使用流程、生命周期、权限管理等要点,并附带一个带权限校验的示例(含 AndroidManifest.xml 配置与代码实现)。
在 Android 中,ContentProvider 是应用间共享数据的标准接口,它把底层数据访问(如 SQLite、文件或网络)封装起来,通过统一的 URI 方式进行增删改查操作,并提供多种权限控制手段,确保只有获准的客户端才能访问。
# 1. 概述与定位
- 作用:内置或自定义的
ContentProvider能让一个应用将其私有数据安全地暴露给其他应用,或在自身内部提供统一的数据访问接口 - 接口:通过
ContentResolver客户端以 URI(content://authority/path)方式调用;ContentProvider通过覆写query()、insert()、update()、delete()和getType()等方法处理请求
# 2. 注册与 URI
# 2.1 Manifest 中声明
- 在
<application>标签内使用<provider>元素声明,指定:android:authorities:全局唯一的授权字符串android:exported:是否允许外部应用访问android:grantUriPermissions:是否允许临时 URI 访问授权
# 2.2 创建 Provider
- 继承自
android.content.ContentProvider并实现抽象方法。 - 在
onCreate()中初始化底层数据源(如打开数据库)。 - 使用
UriMatcher匹配客户端提交的 URI,并在各个 CRUD 方法中根据匹配结果操作数据
# 3. 生命周期与调用流程
- 进程启动:进程一旦需要访问 Provider,系统先加载其
ContentProvider类并调用onCreate() - 客户端请求:客户端通过
ContentResolver发起 IPC 调用,Binder 机制将 URI 与参数发送给 Provider。 - 方法执行:Provider 在调用线程中执行对应的 CRUD 方法,并返回
Cursor或操作结果。 - 关闭清理:客户端和 Provider 均需在适当时机关闭 Cursor、释放资源。
# 4. 权限管理
# 4.1 Manifest 权限声明
定义权限:在 Provider 应用的
AndroidManifest.xml中,用<permission>定义独有权限,如:<permission android:name="com.example.app.provider.permission.READ_DATA" android:protectionLevel="signature" />1
2引用权限:在
<provider>标签中使用android:readPermission/android:writePermission强制客户端具有相应权限才能调用客户端授权:请求权限的应用在其 Manifest 中用
<uses-permission>声明:<uses-permission android:name="com.example.app.provider.permission.READ_DATA" />1
# 4.2 动态检查与临时授权
grantUriPermissions:若启用此属性,Provider 可在运行时调用
Context.grantUriPermission()授予某个包对特定 URI 的访问权限,且可在 Intent 中携带FLAG_GRANT_READ_URI_PERMISSION、FLAG_GRANT_WRITE_URI_PERMISSION运行时校验:在 Provider 方法中可进一步使用:
enforceCallingPermission("com.example.app.provider.permission.READ_DATA", "Must have READ_DATA permission");1
2或
if (getContext().checkCallingOrSelfPermission( "com.example.app.provider.permission.READ_DATA") != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Permission denied"); }1
2
3
4
5进行二次校验,防止恶意绕过
# 5. 示例:带权限校验的简单 Provider
# 5.1 AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.providerapp">
<!-- 定义保护权限 -->
<permission android:name="com.example.providerapp.permission.READ_ITEMS"
android:protectionLevel="signature" />
<application
android:allowBackup="true"
android:label="@string/app_name">
<!-- 声明 ContentProvider -->
<provider
android:name=".ItemProvider"
android:authorities="com.example.providerapp.items"
android:exported="true"
android:readPermission="com.example.providerapp.permission.READ_ITEMS"
android:grantUriPermissions="true">
<!-- 可选:定义可授权的 URI 路径 -->
<grant-uri-permission android:pathPrefix="/items" />
</provider>
</application>
</manifest>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
客户端需在其 Manifest 中声明:
<uses-permission android:name="com.example.providerapp.permission.READ_ITEMS" />
# 5.2 ItemProvider.java
package com.example.providerapp;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class ItemProvider extends ContentProvider {
public static final String AUTHORITY = "com.example.providerapp.items";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/items");
private static final int ITEMS = 1;
private static final int ITEM_ID = 2;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private DatabaseHelper dbHelper;
static {
uriMatcher.addURI(AUTHORITY, "items", ITEMS);
uriMatcher.addURI(AUTHORITY, "items/#", ITEM_ID);
}
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selArgs,
@Nullable String sortOrder) {
// 权限二次校验
if (getContext().checkCallingOrSelfPermission(
"com.example.providerapp.permission.READ_ITEMS")
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires READ_ITEMS permission");
}
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor;
switch (uriMatcher.match(uri)) {
case ITEMS:
cursor = db.query(DatabaseHelper.TABLE_NAME,
projection, selection, selArgs,
null, null, sortOrder);
break;
case ITEM_ID:
long id = ContentUris.parseId(uri);
cursor = db.query(DatabaseHelper.TABLE_NAME,
projection, "_id=?",
new String[]{ String.valueOf(id) },
null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 通知观察者数据可用
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
// insert/update/delete 等方法可类似添加权限校验并实现
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
以上即 Android ContentProvider 的核心要点与权限校验示例,帮助你快速上手并能在生产环境中安全地暴露数据。若有更深入的优化(如性能调优、批量操作、跨进程缓存等)需求,可在此基础上扩展。