403 lines
10 KiB
Markdown
403 lines
10 KiB
Markdown
# 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
|
||
// authority:ContentProvider 的标识
|
||
// 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`
|
||
- authority:ContentProvider 标识
|
||
- path:数据路径
|
||
- id:数据ID(可选)
|
||
|
||
### Q3: ContentResolver 和 ContentProvider 的关系?
|
||
|
||
**答案:**
|
||
- **ContentResolver**:客户端,用于访问数据
|
||
- **ContentProvider**:服务端,提供数据访问接口
|
||
- 通过 URI 和 Binder 机制通信
|
||
|
||
### Q4: ContentObserver 的作用?
|
||
|
||
**答案:**
|
||
- 监听 ContentProvider 数据变化
|
||
- 数据变化时自动通知
|
||
- 实现数据更新通知机制
|
||
|
||
---
|
||
|
||
*最后更新:2024年*
|