Files
mkdocs/docs/android面试/核心组件/ContentProvider详解.md
2026-01-15 11:53:37 +08:00

403 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ContentProvider详解
## 目录
- [ContentProvider作用](#contentprovider作用)
- [URI机制](#uri机制)
- [ContentResolver使用](#contentresolver使用)
- [数据访问权限](#数据访问权限)
- [ContentObserver](#contentobserver)
- [ContentProvider实现](#contentprovider实现)
- [ContentProvider最佳实践](#contentprovider最佳实践)
- [面试常见问题](#面试常见问题)
---
## ContentProvider作用
### 主要作用
1. **数据共享**:应用间共享数据
2. **数据封装**:封装数据访问逻辑
3. **统一接口**:提供统一的数据访问接口
4. **权限控制**:控制数据访问权限
### 使用场景
```java
// 场景1应用间共享数据
// 应用A提供联系人数据
// 应用B访问联系人数据
// 场景2封装数据访问
// 统一管理数据库访问
// 隐藏数据存储细节
// 场景3跨进程数据访问
// 通过 Binder 机制跨进程访问
```
---
## URI机制
### URI 格式
```java
// URI 格式
content://authority/path/id
// 示例
content://com.example.provider/user/1
content://com.example.provider/user
content://com.example.provider/user/1/name
```
### URI 组成
```java
// authorityContentProvider 的标识
// path数据路径
// id数据ID可选
// 解析 URI
Uri uri = Uri.parse("content://com.example.provider/user/1");
String authority = uri.getAuthority(); // com.example.provider
List<String> pathSegments = uri.getPathSegments(); // [user, 1]
String id = uri.getLastPathSegment(); // 1
```
### URI 匹配
```java
// 使用 UriMatcher 匹配 URI
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI("com.example.provider", "user", USER);
uriMatcher.addURI("com.example.provider", "user/#", USER_ID);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case USER:
// 查询所有用户
break;
case USER_ID:
// 查询指定用户
String id = uri.getLastPathSegment();
break;
}
}
```
---
## ContentResolver使用
### 查询数据
```java
// 查询数据
Uri uri = Uri.parse("content://com.example.provider/user");
Cursor cursor = getContentResolver().query(
uri,
new String[]{"id", "name", "email"}, // projection
"age > ?", // selection
new String[]{"18"}, // selectionArgs
"name ASC" // sortOrder
);
if (cursor != null) {
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
String email = cursor.getString(cursor.getColumnIndex("email"));
}
cursor.close();
}
```
### 插入数据
```java
// 插入数据
Uri uri = Uri.parse("content://com.example.provider/user");
ContentValues values = new ContentValues();
values.put("name", "John");
values.put("email", "john@example.com");
Uri newUri = getContentResolver().insert(uri, values);
```
### 更新数据
```java
// 更新数据
Uri uri = Uri.parse("content://com.example.provider/user/1");
ContentValues values = new ContentValues();
values.put("name", "Jane");
int count = getContentResolver().update(uri, values, null, null);
```
### 删除数据
```java
// 删除数据
Uri uri = Uri.parse("content://com.example.provider/user/1");
int count = getContentResolver().delete(uri, null, null);
```
---
## 数据访问权限
### 权限声明
```xml
<!-- AndroidManifest.xml -->
<provider
android:name=".MyProvider"
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.READ_PERMISSION"
android:writePermission="com.example.WRITE_PERMISSION" />
```
### 权限检查
```java
public class MyProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 检查读取权限
if (getContext().checkCallingOrSelfPermission(
"com.example.READ_PERMISSION") != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Permission denied");
}
// 查询数据
return null;
}
}
```
---
## ContentObserver
### ContentObserver 使用
```java
// 监听数据变化
public class MyObserver extends ContentObserver {
public MyObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// 数据变化时调用
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
// 指定 URI 的数据变化
}
}
// 注册观察者
Uri uri = Uri.parse("content://com.example.provider/user");
ContentObserver observer = new MyObserver(new Handler());
getContentResolver().registerContentObserver(uri, true, observer);
// 注销观察者
getContentResolver().unregisterContentObserver(observer);
```
---
## ContentProvider实现
### 完整实现
```java
public class UserProvider extends ContentProvider {
private static final String AUTHORITY = "com.example.provider";
private static final String TABLE_USER = "user";
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int USER = 1;
private static final int USER_ID = 2;
static {
uriMatcher.addURI(AUTHORITY, TABLE_USER, USER);
uriMatcher.addURI(AUTHORITY, TABLE_USER + "/#", USER_ID);
}
private SQLiteDatabase database;
@Override
public boolean onCreate() {
DatabaseHelper helper = new DatabaseHelper(getContext());
database = helper.getWritableDatabase();
return database != null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_USER);
switch (uriMatcher.match(uri)) {
case USER:
break;
case USER_ID:
queryBuilder.appendWhere("id = " + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
Cursor cursor = queryBuilder.query(database, projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
long id = database.insert(TABLE_USER, null, values);
if (id > 0) {
Uri newUri = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int count;
switch (uriMatcher.match(uri)) {
case USER:
count = database.update(TABLE_USER, values, selection, selectionArgs);
break;
case USER_ID:
String id = uri.getLastPathSegment();
count = database.update(TABLE_USER, values, "id = ?", new String[]{id});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count;
switch (uriMatcher.match(uri)) {
case USER:
count = database.delete(TABLE_USER, selection, selectionArgs);
break;
case USER_ID:
String id = uri.getLastPathSegment();
count = database.delete(TABLE_USER, "id = ?", new String[]{id});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case USER:
return "vnd.android.cursor.dir/user";
case USER_ID:
return "vnd.android.cursor.item/user";
default:
return null;
}
}
}
```
---
## ContentProvider最佳实践
### 1. 通知数据变化
```java
// 数据变化时通知观察者
getContext().getContentResolver().notifyChange(uri, null);
```
### 2. 使用事务
```java
database.beginTransaction();
try {
// 批量操作
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
```
### 3. 权限控制
```java
// 检查权限
if (getContext().checkCallingOrSelfPermission(permission)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Permission denied");
}
```
---
## 面试常见问题
### Q1: ContentProvider 的作用?
**答案:**
1. 数据共享:应用间共享数据
2. 数据封装:封装数据访问逻辑
3. 统一接口:提供统一的数据访问接口
4. 权限控制:控制数据访问权限
### Q2: URI 的格式?
**答案:**
`content://authority/path/id`
- authorityContentProvider 标识
- path数据路径
- id数据ID可选
### Q3: ContentResolver 和 ContentProvider 的关系?
**答案:**
- **ContentResolver**:客户端,用于访问数据
- **ContentProvider**:服务端,提供数据访问接口
- 通过 URI 和 Binder 机制通信
### Q4: ContentObserver 的作用?
**答案:**
- 监听 ContentProvider 数据变化
- 数据变化时自动通知
- 实现数据更新通知机制
---
*最后更新2024年*