初识ContentProvider

ContentProvider 设计

四大组件之一,主要用于在不同应用之间共享数据,ContentProvider 提供了一个一致的接口,应用能够以一种受控和安全的方式访问和修改数据,同时,通过 CP,数据可以被跨进程共享,而不必将数据直接暴露给其他应用。

ContentProvider 设计初衷

  1. 数据共享:Android 中,应用之间不允许直接访问数据,CP 提供了一种标准方式让应用安全共享数据
  2. 数据封装:通过 CP,数据存取逻辑可以封装在一个单独组件中,其他组件只需要通过 CP 提供的接口操作
  3. 统一接口:CP 提供了一个统一的接口,支持多种数据存取方式,并且支持对数据进行事务操作

ContentProvider 基本结构

  1. URI:统一资源标识符,用于定位 CP 中的数据
  2. MIME 类型:用于标识返回的数据类型
  3. 数据存储:实际存储数据的地方,如数据库、文件
  4. 数据操作方法:CRUD

ContentProvider 实现

继承 ContentProvider 并重写抽象方法

这一块不赘述了

高级特性

ContentObserver 监听数据变化

刚好最近做的需求中有用到,根据投影仪投射距离是否合适来展示正确的UI,我们就可以监听算法返回的距离状态去做更新UI操作

创建一个 ContentObserver 并注册它来监听数据变化

1
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
public class SettingsContentObserver extends ContentObserver {
private final Context context;
private final String settingKey;

public SettingsContentObserver(Handler handler, Context context, String settingKey) {
super(handler);
this.context = context;
this.settingKey = settingKey;
}

@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
handleSettingChange();
}

@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
handleSettingChange();
}

private void handleSettingChange() {
// 获取设置值
int value = Settings.Global.getInt(
context.getContentResolver(),
DISTANCE_STATUS,
1 // 默认值
);

LogUtils.d("SettingsObserver", DISTANCE_STATUS + " changed to: " + value);
if(value==0){
isDistanceCorrect = true;
}else {
isDistanceCorrect = false;
}
updatUI();

}
}

在合适位置注册和注销观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onResume() {
super.onResume();
// 注册观察者
settingsObserver = new SettingsContentObserver(new Handler(),this,DISTANCE_STATUS);
getContentResolver().registerContentObserver(Settings.Global.getUriFor(DISTANCE_STATUS), false, settingsObserver);
}
@Override
protected void onPause() {
super.onPause();
// 注销观察者(避免内存泄漏)
getContentResolver().unregisterContentObserver(settingsObserver);
}

数据同步

ContentProvider 可以与 SyncAdapter 结合,实现自动数据同步;例如,当远程服务器上的数据更新时,可以通过 SyncAdapter 同步到本地数据库,并通过 ContentProvider 提供的数据接口进行访问;

创建 SyncAdapter

1
2
3
4
5
6
7
8
9
10
11
public class ExampleSyncAdapter extends AbstractThreadedSyncAdapter {
public ExampleSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}

@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
// 同步逻辑
// 更新数据后通过 provider.notifyChange 通知变化
}
}

配置 SyncAdapter 和 ContentProvider 结合

1
2
3
4
5
6
7
<sync-adapter
android:contentAuthority="com.example.provider"
android:accountType="com.example.account"
android:userVisible="true"
android:supportsUploading="true"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true" />

权限控制

为保护敏感数据,CP 可以通过声明权限来控制数据访问

AndroidManifest.xml 中进行声明

1
2
3
4
5
6
7
8
<permission android:name="com.example.provider.READ" android:protectionLevel="signature" />
<permission android:name="com.example.provider.WRITE" android:protectionLevel="signature" />
<provider
android:name=".ExampleProvider"
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.provider.READ"
android:writePermission="com.example.provider.WRITE" />

代码中检查

1
2
3
4
5
6
7
8
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
if (getContext().checkCallingOrSelfPermission("com.example.provider.READ") == PackageManager.PERMISSION_DENIED) {
throw new SecurityException("Permission denied");
}
// 执行查询操作
}