你手机里的通讯录,存储了所有联系人的信息。如果你想把这些联系人信息分享给其他App,就可以通过ContentProvider来实现。。
ContentProvider 是 Android 四大组件之一,负责实现跨应用程序的数据共享与访问,通过统一接口封装数据存储细节,提供标准化操作方式。其中主要功能包括:
- 数据抽象层:将应用内部的数据(如 SQLite 数据库、文件等)封装成统一的接口对外提供。
- 跨应用数据共享:允许其他应用安全地访问和操作本应用的数据。
- 数据权限控制:通过 URI 和权限机制,精确控制数据的访问范围。
- 统一数据访问:提供类似数据库的 CRUD 操作接口,简化数据使用。
URI(统一资源标识符)
content://authority/path/id
content://com.example.provider/users/1
authority
:标识 ContentProvider,通常为应用包名 + provider 名path
:标识要访问的数据集合id
:可选,标识具体记录ContentResolver
Cursor
以下是实现一个简单 ContentProvider 的完整步骤:
1.创建数据模型
// User.java
public class User {
private int id;
private String name;
private int age;
// getters and setters
}
2.创建 SQLiteOpenHelper 管理数据库
// DatabaseHelper.java
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "user.db";
private static final int DB_VERSION = 1;
public static final String TABLE_NAME = "users";
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT, " +
"age INTEGER);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
3.实现 ContentProvider
// UserProvider.java
public class UserProvider extends ContentProvider {
private DatabaseHelper dbHelper;
public static final String AUTHORITY = "com.example.provider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
return db.query(DatabaseHelper.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
long id = db.insert(DatabaseHelper.TABLE_NAME, null, values);
return ContentUris.withAppendedId(CONTENT_URI, id);
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
return db.update(DatabaseHelper.TABLE_NAME, values, selection, selectionArgs);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
return db.delete(DatabaseHelper.TABLE_NAME, selection, selectionArgs);
}
@Override
public String getType(Uri uri) {
return "vnd.android.cursor.dir/vnd.com.example.provider.users";
}
}
4.在 AndroidManifest.xml 中注册 Provider
其他应用通过 ContentResolver 访问该 Provider:
// 查询所有用户
Cursor cursor = getContentResolver().query(
UserProvider.CONTENT_URI,
null,
null,
null,
null
);
// 插入新用户
ContentValues values = new ContentValues();
values.put("name", "John");
values.put("age", 30);
Uri newUri = getContentResolver().insert(UserProvider.CONTENT_URI, values);
// 更新用户
ContentValues updateValues = new ContentValues();
updateValues.put("age", 31);
int count = getContentResolver().update(
UserProvider.CONTENT_URI,
updateValues,
"name=?",
new String[]{"John"}
);
// 删除用户
int deleted = getContentResolver().delete(
UserProvider.CONTENT_URI,
"age > ?",
new String[]{"40"}
);
配置目标 | 实现方式 |
跨应用调用权限 | 调用方声明 |
动态权限申请 | 针对dangerous级别权限,调用方需在运行时请求用户授权 |
路径级访问控制 | Provider方通过 |
1. 声明 Provider 权限
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.READ_USERS"
android:writePermission="com.example.WRITE_USERS" />
protectionLevel
设为dangerous
表示需用户手动授权。readPermission
/writePermission
:自定义权限控制。
2. 路径级权限细化(可选)
若 Provider 方通过
限制特定路径,调用方需确保拥有对应权限:
3. 调用方配置
4. 动态权限申请
在调用方的 Activity/Fragment 中实现动态权限申请流程:
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_READ_PERMISSION = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 检查权限
if (ContextCompat.checkSelfPermission(this, "com.example.READ_USERS")
!= PackageManager.PERMISSION_GRANTED) {
// 权限未授予,显示申请弹窗
ActivityCompat.requestPermissions(this,
new String[]{"com.example.READ_USERS"},
REQUEST_READ_PERMISSION);
} else {
// 已授权,执行数据访问
queryData();
}
}
// 处理权限申请结果
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_READ_PERMISSION) {
if (grantResults.length > 0 && grantResults[0]
== PackageManager.PERMISSION_GRANTED) {
queryData();
} else {
// 权限被拒绝,提示用户
Toast.makeText(this, "权限被拒绝,无法读取数据",
Toast.LENGTH_SHORT).show();
}
}
}
private void queryData() {
// 通过 ContentResolver 访问 Provider 数据
Cursor cursor = getContentResolver().query(
UserContract.CONTENT_URI,
null, null, null, null
);
// 处理查询结果...
}
}
同一权限组内的权限只需申请一次(如
READ_CONTACTS
和WRITE_CONTACTS
属于同一组)
4. 用户拒绝后引导设置
若用户勾选“不再询问”,需引导用户前往系统设置手动开启权限(可通过 shouldShowRequestPermissionRationale
判断)。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
"com.example.READ_USERS")) {
// 用户之前可能拒绝过权限但未勾选“不再询问”
// 展示解释性弹窗后再次申请
} else {
// 用户勾选“不再询问”或系统禁止权限(如厂商定制 ROM 限制)
// 跳转系统设置界面手动开启权限
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
若用户从未请求过该权限,shouldShowRequestPermissionRationale() 也会返回 false。但此时代码通常不会进入此分支(因首次请求时直接调用 requestPermissions())。
部分设备可能不支持 Settings.ACTION_APPLICATION_DETAILS_SETTINGS,需增加异常捕获并提示用户手动查找权限设置 。
角色 | 职责 |
客户端 | 注册ContentObserver并实现onChange回调逻辑(如刷新UI) |
ContentProvider | 数据变更时调用notifyChange触发通知 |
系统服务 | 通过ContentService统一管理观察者,完成消息分发 |
1. 客户端注册观察者
在使用数据的客户端(如 Activity、Fragment)中,通过 ContentResolver
注册 ContentObserver
,并指定监听的目标 URI,从而实时更新UI。
// 使用者(Activity)通过ContentResolver注册观察者
getContentResolver().registerContentObserver(
UserContract.CONTENT_URI,
true, // 是否监听子 URI
new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
// 数据变化时触发回调
}
}
);
registerContentObserver
是客户端主动调用的方法,用于绑定观察者与目标数据 URI。true
表示监听该 URI 及其所有子路径(如content://com.example.provider/users/
)的数据变更。
2. 提供者触发通知
在 ContentProvider 中,当数据发生变更(如 insert
、update
、delete
)时,需调用 notifyChange
方法触发回调:
// 在 Provider 的 insert/update/delete 方法中
getContext().getContentResolver().notifyChange(uri, null);
notifyChange
会通知所有注册了该 URI 的观察者。- 可通过第二个参数
observer
指定跳过特定观察者(通常设为null
)。
3. 系统级支持
Transport
代理),确保跨进程通信的可行性。客户端需主动注册观察者监听 URI,而通知触发由 Provider 发起,两者通过系统服务协同实现实时数据同步。
1.使用 SQLite 事务
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {
// 执行多个操作
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
索引优化
db.execSQL("CREATE INDEX IF NOT EXISTS idx_name ON users(name);");
避免在主线程进行耗时操作
getSupportLoaderManager().initLoader(0, null, this);
谨慎设置 android:exported
输入验证
private void validateProjection(String[] projection) {
if (projection != null) {
for (String col : projection) {
if (!allowedColumns.contains(col)) {
throw new IllegalArgumentException("Invalid column: " + col);
}
}
}
}