作为 Android 四大组件之一的 ContentProvider 在应用开发的过程中存在感一直很弱,使用的场景也限于在应用间共享数据。官方推荐我们搭配ContentProvider和数据库进行数据的增删改查,本文提到的用法是把 ContentProvder 当成一个远程过程调用的中介,实现轻量级的进程间通信,这个方法在 WebViewActivity 独立进程的重构过程中被大量使用。
Manifest 注册
首先需要在AndroidManifest.xml文件中注册ContentProvider,由于内容提供器只是在应用内部使用不该对外暴露,所以设置属性android:exported="false"
<provider
android:authorities="${applicationId}.provider"
android:name=".interprocess.InterProcessDataProvider"
android:exported="false" />
ContentProvider 定义
我们需要在定义时禁用掉数据库增删改查操作,只保留基础的call方法。
/**
* 跨进程数据提供者,基于ContentProvider,运行于主进程
*/
public class InterProcessDataProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
throw new UnsupportedOperationException();
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
Processor processor = ProcessorPool.get(method);
return processor == null ? null : processor.process(arg, extras);
}
}
管理跨进程方法
上面的代码中出现了一个ProcessorPool
, 通过这个类去注册管理所有的跨进程调用方法,相关的类定义如下:
Processor.java 定义跨进程调用方法
public interface Processor {
String getMethodName();
Bundle process(@Nullable String arg, @Nullable Bundle extras);
}
ProcessorManger.java 统一管理某一类别的Processor
public interface ProcessorManager {
List<Processor> getItems();
}
ProcessorPool.java 跨进程方法池
public class ProcessorPool {
public static Map<String, Processor> sMethodMap = new HashMap<>();
public static Processor get(String name) {
return sMethodMap.get(name);
}
public static void register(Processor processor) {
sMethodMap.put(processor.getMethodName(), processor);
}
public static void register(ProcessorManager processors) {
for (Processor item : processors.getItems()) {
register(item);
}
}
}
一个使用的栗子
下面演示跨进程获取token的方法,注意上面manifest定义时ContentProvider运行在主进程中,也就是获取到的是主进程的数据。
1.首先定义处理器:
/**
* 获取登录用户的token
*/
static Processor sGetAccessToken = new Processor() {
@Override
public String getMethodName() {
return "get_user_token";
}
@Override
public Bundle process(@Nullable String arg, @Nullable Bundle extras) {
Bundle bundle = new Bundle();
// AccountManger主进程的用户管理
String accessToken = AccountManager.getInstance().getAccessToken();
// 通过bundle包装数据
bundle.putString(Constants.KEY_ACCESS_TOKEN, accessToken);
return bundle;
}
};
2.注册:在Application初始化时注册
public class MyApplication extends Application {
public void onCreate() {
ProcessorPool.register(sGetAccessToken);
}
}
3.调用:
public class TokenLet {
public static String getAccessToken() {
// 如果当前进程是主进程,则使用进程内调用
if (InterProcessUtils.isMainProcess) {
return FrodoAccountManager.getInstance().getAccessToken();
}
Bundle result = null;
// 与manifest中定义的authorities相对应
Uri providerUri = Uri.parse("content://" + context.getPackageName() + ".provider");
try {
result = AppContext.getApp().getContentResolver().call(providerUri,
"get_user_token", null, null);
} catch (Exception e) {
e.printStackTrace();
}
if (result != null) {
return result.getString(Constants.KEY_ACCESS_TOKEN);
}
return "";
}
}
优缺点
过去实现类似跨进程获取token字符串一般是使用aidl接口,但是aidl接口的定义十分繁琐,而且这种方式需要启动一个service去管理这些aidl调用。使用ContentProvider的方法优点很明显,使用简单,可以传递任何支持序列化的数据。从上面的代码中我们可以发现,所有方法的调用是同步的,并不支持aidl中的异步callback回调的方式。综上而言,ContentProvider实现RPC适合数据量小且调用简单的同步调用。