1.1 什么是ContentProvider?
- Android四大组件之一
- 用于应用程序间的数据共享
- 提供统一的数据访问接口,封装底层数据存储细节
1.2 主要特性
- 跨进程数据访问:不同应用间安全共享数据
- 统一接口:使用URI标识数据资源
- 权限控制:通过权限机制保护数据访问
- 数据类型:支持多种数据类型(SQLite、文件等)
1.3 应用场景
- 系统内置数据访问(联系人、日历、媒体库等)
- 应用间数据共享(如社交应用分享数据)
- 插件化架构中的数据交互
- 数据备份和恢复
二、核心组件
2.1 URI(统一资源标识符)
// URI格式
content://<authority>/<path>/<id>
// 示例
content://com.example.provider/user/123
- Authority:唯一标识ContentProvider,格式为包名.provider
- Path:数据路径,通常对应表名
- ID:可选,标识特定记录
2.2 ContentProvider类
public class MyProvider extends ContentProvider {
// 必须实现的6个核心方法
public boolean onCreate() { }
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) { }
public Uri insert(Uri uri, ContentValues values) { }
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) { }
public int delete(Uri uri, String selection, String[] selectionArgs) { }
public String getType(Uri uri) { }
}
2.3 ContentResolver类
- 客户端应用通过ContentResolver访问ContentProvider
- 提供与ContentProvider对应的CRUD方法
- 系统服务,通过getContentResolver()获取
2.4 ContentObserver
- 观察数据变化的监听器
- 实现数据变更的通知机制
三、具体实现
3.1 创建ContentProvider步骤
步骤1:定义URI和常量
public final class UserContract {
public static final String AUTHORITY = "com.example.userprovider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");
public static final class UserEntry {
public static final String TABLE_NAME = "users";
public static final String _ID = "_id";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_AGE = "age";
public static final String COLUMN_EMAIL = "email";
}
}
步骤2:实现ContentProvider
public class UserProvider extends ContentProvider {
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int USERS = 100;
private static final int USER_ID = 101;
static {
URI_MATCHER.addURI(UserContract.AUTHORITY, "users", USERS);
URI_MATCHER.addURI(UserContract.AUTHORITY, "users/#", USER_ID);
}
private DatabaseHelper dbHelper;
@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();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(UserContract.UserEntry.TABLE_NAME);
int match = URI_MATCHER.match(uri);
switch (match) {
case USERS:
// 查询所有用户
break;
case USER_ID:
// 查询单个用户
queryBuilder.appendWhere(UserContract.UserEntry._ID + "=" +
uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
}
// 实现其他方法...
}
步骤3:配置AndroidManifest.xml
<provider
android:name=".UserProvider"
android:authorities="com.example.userprovider"
android:exported="true"
android:permission="com.example.READ_USER"
android:readPermission="com.example.READ_USER"
android:writePermission="com.example.WRITE_USER" >
<!-- 可选:为特定路径设置不同权限 -->
<path-permission
android:pathPrefix="/admin"
android:permission="com.example.ADMIN" />
</provider>
3.2 客户端使用示例
查询数据
// 查询所有用户
Cursor cursor = getContentResolver().query(
UserContract.CONTENT_URI,
new String[] { UserContract.UserEntry._ID,
UserContract.UserEntry.COLUMN_NAME },
null,
null,
UserContract.UserEntry.COLUMN_NAME + " ASC"
);
if (cursor != null && cursor.moveToFirst()) {
do {
String name = cursor.getString(
cursor.getColumnIndex(UserContract.UserEntry.COLUMN_NAME)
);
// 处理数据...
} while (cursor.moveToNext());
cursor.close();
}
插入数据
ContentValues values = new ContentValues();
values.put(UserContract.UserEntry.COLUMN_NAME, "张三");
values.put(UserContract.UserEntry.COLUMN_AGE, 25);
values.put(UserContract.UserEntry.COLUMN_EMAIL, "zhangsan@example.com");
Uri newUri = getContentResolver().insert(
UserContract.CONTENT_URI,
values
);
更新数据
ContentValues values = new ContentValues();
values.put(UserContract.UserEntry.COLUMN_AGE, 26);
String selection = UserContract.UserEntry.COLUMN_NAME + " = ?";
String[] selectionArgs = { "张三" };
int rowsUpdated = getContentResolver().update(
UserContract.CONTENT_URI,
values,
selection,
selectionArgs
);
删除数据
String selection = UserContract.UserEntry.COLUMN_AGE + " < ?";
String[] selectionArgs = { "18" };
int rowsDeleted = getContentResolver().delete(
UserContract.CONTENT_URI,
selection,
selectionArgs
);
四、进阶功能
4.1 批量操作
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
operations.add(ContentProviderOperation.newInsert(UserContract.CONTENT_URI)
.withValue(UserContract.UserEntry.COLUMN_NAME, "李四")
.withValue(UserContract.UserEntry.COLUMN_AGE, 30)
.build());
operations.add(ContentProviderOperation.newUpdate(UserContract.CONTENT_URI)
.withSelection(UserContract.UserEntry.COLUMN_NAME + " = ?",
new String[]{"张三"})
.withValue(UserContract.UserEntry.COLUMN_AGE, 27)
.build());
try {
ContentProviderResult[] results = getContentResolver().applyBatch(
UserContract.AUTHORITY,
operations
);
} catch (RemoteException | OperationApplicationException e) {
e.printStackTrace();
}
4.2 文件共享(FileProvider)
// 继承FileProvider
public class MyFileProvider extends FileProvider {
// 使用android.support.v4.content.FileProvider
}
// AndroidManifest配置
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
// XML配置(res/xml/file_paths.xml)
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="files" path="." />
<cache-path name="cache" path="." />
<external-files-path name="external_files" path="." />
<external-cache-path name="external_cache" path="." />
</paths>
4.3 数据变更通知
// 注册ContentObserver
getContentResolver().registerContentObserver(
UserContract.CONTENT_URI,
true, // 是否监听所有子URI
new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
// 数据发生变化,更新UI
refreshData();
}
}
);
// 在Provider中通知变化
getContext().getContentResolver().notifyChange(uri, null);
4.4 自定义权限
<!-- 定义权限 -->
<permission
android:name="com.example.READ_USER"
android:protectionLevel="dangerous"
android:label="读取用户数据"
android:description="允许应用读取用户数据" />
<permission
android:name="com.example.WRITE_USER"
android:protectionLevel="dangerous"
android:label="写入用户数据"
android:description="允许应用写入用户数据" />
五、最佳实践与优化
5.1 性能优化
- 使用索引:为查询条件创建数据库索引
- 批量操作:使用applyBatch减少事务开销
- 异步查询:使用CursorLoader或异步任务
- 分页查询:实现limit和offset支持
- 缓存策略:合理缓存频繁访问的数据
5.2 线程安全
// 使用单例确保线程安全
private static final Object sLock = new Object();
private DatabaseHelper mDbHelper;
private SQLiteDatabase getDatabase() {
synchronized (sLock) {
if (mDbHelper == null) {
mDbHelper = new DatabaseHelper(getContext());
}
return mDbHelper.getWritableDatabase();
}
}
5.3 安全防护
// 输入验证
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 验证URI
validateUri(uri);
// 防止SQL注入
if (selection != null) {
// 验证selection参数
validateSelection(selection);
}
// 权限检查
checkPermission(uri, "read");
// 实际查询逻辑...
}
5.4 错误处理
@Override
public Uri insert(Uri uri, ContentValues values) {
try {
// 插入逻辑
long id = db.insert(/* 参数 */);
if (id == -1) {
throw new SQLException("Failed to insert row into " + uri);
}
// 通知变化
getContext().getContentResolver().notifyChange(uri, null);
return ContentUris.withAppendedId(uri, id);
} catch (Exception e) {
Log.e(TAG, "Insert failed", e);
// 根据具体情况处理异常
if (e instanceof SQLiteConstraintException) {
throw new IllegalArgumentException("Data conflict: " + e.getMessage());
} else {
throw new RuntimeException("Database error", e);
}
}
}
六、常见问题与解决方案
6.1 权限问题
- 问题:Permission Denial accessing ContentProvider
- 解决:
- 检查AndroidManifest中权限声明
- 运行时权限请求(Android 6.0+)
- 使用grantUriPermissions临时授权
6.2 跨进程性能
- 优化:
- 减少跨进程调用次数(批量操作)
- 只传输必要数据
- 使用Binder传输优化
6.3 数据类型转换
- 注意:
- URI中ID为字符串类型
- 使用ContentValues进行数据包装
- 注意数据类型的兼容性
6.4 版本兼容性
- 策略:
- 保持URI格式向后兼容
- 数据库迁移时注意数据格式
- 使用@RequiresApi注解标注API要求
七、总结
ContentProvider是Android数据共享的核心组件,掌握它需要理解:
- 核心概念:URI、ContentResolver、权限机制
- 实现细节:CRUD操作、线程安全、性能优化
- 进阶功能:批