在开发中,假如,A、B进程有部分信息需要同步,这个时候怎么处理呢?设想这么一个场景,有个业务复杂的Activity非常占用内存,并引发OOM,所以,想要把这个Activity放到单独进程,以保证OOM时主进程不崩溃。但是,两个整个APP有些信息需要保持同步,比如登陆信息等,无论哪个进程登陆或者修改了相应信息,都要同步到另一个进程中去,这个时候怎么做呢?

  • 第一种:一个进程里面的时候,经常采用SharePreference来做,但是SharePreference不支持多进程,它基于单个文件的,默认是没有考虑同步互斥,而且,APP对SP对象做了缓存,不好互斥同步,虽然可以通过FileLock来实现互斥,但同步仍然是一个问题。
  • 第二种:基于Binder通信实现Service完成跨进程数据的共享,能够保证单进程访问数据,不会有互斥问题,可是同步的事情仍然需要开发者手动处理。
  • 第三种:基于Android提供的ContentProvider来实现,ContentProvider同样基于Binder,不存在进程间互斥问题,对于同步,也做了很好的封装,不需要开发者额外实现。

因此,在Android开发中,如果需要多进程同步互斥,ContentProvider是一个很好的选择,本文就来看看,它的这个技术究竟是怎么实现的。

概述

Content providers are one of the primary building blocks of Android applications, providing content to applications. They encapsulate data and provide it to applications through the single ContentResolver interface. A content provider is only required if you need to share data between multiple applications. For example, the contacts data is used by multiple applications and must be stored in a content provider. If you don't need to share data amongst multiple applications you can use a database directly via SQLiteDatabase.

ContentProvider为Android数据的存储和获取抽象了统一的接口,并支持在不同的应用程序之间共享数据,Android内置的许多数据都是使用ContentProvider形式供开发者调用的 (如视频,音频,图片,通讯录等),它采用索引表格的形式来组织数据,无论数据来源是什么,ContentProvider都会认为是一种表,这一点从ContentProvider提供的抽象接口就能看出。

class XXX ContentProvider extends ContentProvider{@Overridepublic boolean onCreate() {return false;}@Nullable@Overridepublic Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {return null;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {return null;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {return null;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;}
}复制代码

可以看到每个ContentProvider都需要自己实现增、删、改、查的功能,因此,可以将ContentProvider看做Android提供一个抽象接口层,用于访问表格类的存储媒介,表格只是一个抽象,至于底层存储媒介到底如何组织,完全看用户实现,也就是说ContentProvider自身是没有数据更新及操作能力,它只是将这种操作进行了统一抽象。

ContentProvider抽象接口.jpg

了解了ContentProvider的概念及作用后,下面就从用法来看看ContentProvider是如何支持多进程同步通信的。

ContentProvider代理的同步获取

多进程对于ContentProvider的访问请求最终都会按照队列进入ContentProvider进程,而在单进程中,ContentProvider对于数据的访问很容易做到多线程互斥,一个Sycronized关键字就能搞定,看一下基本用法:

    ContentResolver contentResolver = AppProfile.getAppContext().getContentResolver();ContentValues contentValues = new ContentValues();contentValues.put(key, value);contentResolver.insert(FileContentProvider.CONTENT_URI, contentValues);contentResolver.notifyChange(FileContentProvider.CONTENT_URI, null);复制代码

getContentResolver 其实获取的是一个ApplicationContentResolver实例,定义在ContextImpl中,只有在真正操作数据的时候才会去获取Provider, 详细看一下插入操作:

    public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {<!--首先获取Provider代理-->IContentProvider provider = acquireProvider(url);try {<!--利用IContentProvider代理插入数据-->Uri createdRow = provider.insert(mPackageName, url, values);return createdRow;} }@Overrideprotected IContentProvider acquireUnstableProvider(Context c, String auth) {return mMainThread.acquireProvider(c,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), false);}复制代码

这里是一个典型的基于Binder通信的AIDL实现,IContentProvider的Proxy与Stub分别是ContentProviderProxy与ContentProvider的内部类

abstract public class ContentProviderNative extends Binder implements IContentProvider class Transport extends ContentProviderNative,复制代码

首先看一下ActivityThread的acquireProvider,对于当前进程而言acquireProvider是一个同步的过程,如果ContentProvider所处的进程已经启动,那么acquireProvider可以直接获取服务代理,如果未启动,则等待ContentProvider进程启动,再获取代理。

   public final IContentProvider acquireProvider(Context c, String auth, int userId, boolean stable) {final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);if (provider != null) {return provider;}IActivityManager.ContentProviderHolder holder = null;try {<!--关键点1 获取Provider,如果没有安装,则等待安装完毕-->holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);} catch (RemoteException ex) {}if (holder == null) {return null;}<!--关键点2 这里仅仅是增加计数 ,Provider到这里其实已经安装完毕-->// Install provider will increment the reference count for us, and break// any ties in the race.holder = installProvider(c, holder, holder.info,true /*noisy*/, holder.noReleaseNeeded, stable);return holder.provider;}复制代码

首先看一下关键点1,这里阻塞等待直到获取Provider代理,如果Provider未启动,则先启动,直接看一下ActivityManagerService(其实Android四大组件都归他管理),简单看一下获取流程(只描述个大概):

 private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,String name, IBinder token, boolean stable, int userId) {ContentProviderRecord cpr;ContentProviderConnection conn = null;ProviderInfo cpi = null;synchronized(this) {...<!--关键点1  查看是否已有记录-->// First check if this content provider has been published...cpr = mProviderMap.getProviderByName(name, userId);...boolean providerRunning = cpr != null;<!--如果有-->if (providerRunning) {cpi = cpr.info;String msg;<!--关键点2 是否允许调用进程自己实现ContentProvider-->if (r != null && cpr.canRunHere(r)) {// This provider has been published or is in the process// of being published...  but it is also allowed to run// in the caller's process, so don't make a connection// and just let the caller instantiate its own instance.ContentProviderHolder holder = cpr.newHolder(null);// don't give caller the provider object, it needs// to make its own.holder.provider = null;return holder;}final long origId = Binder.clearCallingIdentity();<!--关键点3 使用ContentProvider进程中的ContentProvider,仅仅增加引用计数-->                        // In this case the provider instance already exists, so we can// return it right away.conn = incProviderCountLocked(r, cpr, token, stable);...}boolean singleton;<!--如果provider未启动-->if (!providerRunning) {try {checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");cpi = AppGlobals.getPackageManager().resolveContentProvider(name,STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);} catch (RemoteException ex) {}...ComponentName comp = new ComponentName(cpi.packageName, cpi.name);cpr = mProviderMap.getProviderByClass(comp, userId);...<!--查看目标进程是否启动-->ProcessRecord proc = getProcessRecordLocked(cpi.processName, cpr.appInfo.uid, false);if (proc != null && proc.thread != null) {if (!proc.pubProviders.containsKey(cpi.name)) {proc.pubProviders.put(cpi.name, cpr);try {proc.thread.scheduleInstallProvider(cpi);} catch (RemoteException e) {}}} else {<!--如果未启动,启动进程,并安装-->proc = startProcessLocked(cpi.processName,cpr.appInfo, false, 0, "content provider",new ComponentName(cpi.applicationInfo.packageName,cpi.name), false, false, false);checkTime(startTime, "getContentProviderImpl: after start process");if (proc == null) {return null;}}cpr.launchingApp = proc;mLaunchingProviders.add(cpr);} finally {...// 线程阻塞等待,直到provider启动 published,Wait for the provider to be published...synchronized (cpr) {while (cpr.provider == null) {try {if (conn != null) {conn.waiting = true;}cpr.wait();} catch (InterruptedException ex) {} finally {if (conn != null) {conn.waiting = false;}}}}return cpr != null ? cpr.newHolder(conn) : null;}复制代码

ContentProvider的启动同Activity或者Service都是比较类似的,如果进程未启动,就去启动进程,在创建进程之后,调用ActivityThread的attach方法,通知AMS新的进程创建完毕,并初始化ProcessRecord,随后,查询所有和本进程相关的ContentProvider信息,并调用bindApplication方法,通知新进程安装并启动这些ContentProvider。ContentProvider有些不一样的就是: ContentProvider调用端会一直阻塞,直到ContentProvider published才会继续执行,这一点从下面可以看出:

  synchronized (cpr) {while (cpr.provider == null) {        复制代码

其次,这里有个疑惑的地方,ContentProvider一般都是随着进程启动的,不过为什么会存在进程启动,但是ContentProvider未published的问题呢?不太理解,难道是中间可能存在什么同步问题吗?下面这部分代码完全看不出为什么存在:

   if (proc != null && proc.thread != null) {<!--如果进程启动,发消息安装Providers-->if (!proc.pubProviders.containsKey(cpi.name)) {proc.pubProviders.put(cpi.name, cpr);try {proc.thread.scheduleInstallProvider(cpi);} catch (RemoteException e) {}}} 复制代码

ContentProvider数据的更新

通过ContentProvider对于数据的操作都是同步的,不过contentResolver.notifyChange通知是异步的

 contentResolver.insert(FileContentProvider.CONTENT_URI, contentValues);contentResolver.notifyChange(FileContentProvider.CONTENT_URI, null);复制代码

ContentProviderProxy会发消息给服务端,而服务端这里直接调用抽象的insert函数,如果需要insert操作是同步的,那么再实现ContentProvider的时候,就可以直接向数据库写数据,当然也可以实现Handler,自己做异步处理。

abstract public class ContentProviderNative extends Binder implements IContentProvider {@Overridepublic boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws RemoteException {...case INSERT_TRANSACTION:{data.enforceInterface(IContentProvider.descriptor);String callingPkg = data.readString();Uri url = Uri.CREATOR.createFromParcel(data);ContentValues values = ContentValues.CREATOR.createFromParcel(data);Uri out = insert(callingPkg, url, values);reply.writeNoException();Uri.writeToParcel(reply, out);return true;}复制代码

这里有一点要注意,Binder框架默认是不支持Stub端同步的,也就是说,即时基于ContentProvider,如果需要对一个文件进行完全互斥访问,在单个进程内同样需要处理互斥操作,不过单进程互斥好处理,Sycronized关键字就可以了。

ContentProvider数据变更通知

ContentProvider支持多进程访问,当一个进程操作ContentProvider变更数据之后,可能希望其他进程能收到通知,比如进程A往数据库插入了一条聊天信息,希望在进程B的UI中展现出来,这个时候就需要一个通知机制,Android也是提供了支持,不过它是一个通用的数据变更同步通知:基于ContentService服务:

<!--1 注册-->
public static void registerObserver(ContentObserver contentObserver) {ContentResolver contentResolver = AppProfile.getAppContext().getContentResolver();contentResolver.registerContentObserver(FileContentProvider.CONTENT_URI, true, contentObserver);
}<!--2 通知-->contentResolver.notifyChange(FileContentProvider.CONTENT_URI, null);复制代码

上面的两个可能在统一进程,也可能在不同进程,

public final void registerContentObserver(Uri uri, boolean notifyForDescendents,ContentObserver observer, int userHandle) {try {getContentService().registerContentObserver(uri, notifyForDescendents,observer.getContentObserver(), userHandle);} catch (RemoteException e) {}
}复制代码

其实这里跟ContentProvider的关系已经不是很大,这里牵扯到另一个服务:ContentService,它是Android平台中数据更新通知的执行者,由SystemServer进程启动,所有APP都能调用它发送数据变动通知,其实就是一个观察者模式,牵扯到另一个服务,不过多讲解。

android:multiprocess在ContentProvider中的作用

默认情况下是不指定android:process跟multiprocess的,它们的值默认为false,会随着应用启动的时候加载,如果对provider指定android:process和android:multiprocess,表现就会不一通了,如果设置android:process,那ContentProvider就不会随着应用启动,如果设置了android:multiprocess,则可能存在多个ContentProvider实例。

If the app runs in multiple processes, this attribute determines whether multiple instances of the content provder are created. If true, each of the app's processes has its own content provider object. If false, the app's processes share only one content provider object. The default value is false.
Setting this flag to true may improve performance by reducing the overhead of interprocess communication, but it also increases the memory footprint of each process.

android:multiprocess的作用是:是否允许在调用者的进程里实例化provider,如果android:multiprocess=false,则系统中只会存在一个provider实例,否则,可以存在多个,多个的话,可能会提高性能,因为它避免了跨进程通信,毕竟,对象就在自己的进程空间,可以直接访问,但是,这会增加系统负担,另外,对于单进程能够保证的互斥问题,也会无效,如果APP需要数据更新,还是保持不开启的好。

总结

  • ContentProvider只是Android为了跨进程共享数据提供的一种机制,
  • 本身基于Binder实现,
  • 在操作数据上只是一种抽象,具体要自己实现
  • ContentProvider只能保证进程间的互斥,无法保证进程内,需要自己实现

作者:看书的小蜗牛
Android ContentProvider支持跨进程数据共享与"互斥、同步"

仅供参考,欢迎指正

Android ContentProvider支持跨进程数据共享与互斥、同步 杂谈相关推荐

  1. 自定义ContentProvider实现跨进程数据共享

    最近要开发一个跨进程数据共享的功能,要实现这个功能,我脑海里第一个想到的是用SharedPreferences,但是发现在Android 7.0以上的版本Context.MODE_WORLD_READ ...

  2. Android中使用ContentProvider进行跨进程方法调用

    原文同一时候发表在我的博客 点我进入还能看到很多其它 需求背景 近期接到这样一个需求,须要和别的 App 进行联动交互,比方下载器 App 和桌面 App 进行联动.桌面的 App 能直接显示下载器 ...

  3. 微信小游戏直播在Android端的跨进程渲染推流实践

    本文由微信开发团队工程师"virwu"分享. 1.引言 近期,微信小游戏支持了视频号一键开播,将微信升级到最新版本,打开腾讯系小游戏(如跳一跳.欢乐斗地主等),在右上角菜单就可以看 ...

  4. 小红书图片剪裁框架+微信图片选择器+超高清大图预览+图片自定义比例剪裁,支持 UI 自定义、支持跨进程回调

    YImagePicker 项目地址:yangpeixing/YImagePicker 简介: 小红书图片剪裁框架+微信图片选择器+超高清大图预览+图片自定义比例剪裁,支持 UI 自定义.支持跨进程回调 ...

  5. Android中的跨进程通信方法实例及特点分析(二):ContentProvider

    1.ContentProvider简单介绍 在Android中有些数据(如通讯录.音频.视频文件等)是要供非常多应用程序使用的.为了更好地对外提供数据,Android系统给我们提供了Content P ...

  6. Android组件化跨进程通信框架Andromeda解析

    关于组件化 随着项目结构越来越庞大,模块与模块间的边界逐渐变得不清晰,代码维护越来越困难,甚至编译速度都成为影响开发效率的瓶颈. 组件化拆分是比较常见的解决方案,一方面解决模块间的耦合关系.将通用模块 ...

  7. Android中的跨进程调用技术AIDL

    什么是AIDL Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信. 为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用 ...

  8. Android开发之跨进程通信-广播跨进程实现方法(附源码)

    真的特别简单,简单概述下android的四大组件都可以跨进程. Activity,广播,服务,内容提供者都可以 先看下跨进程传递数据的效果图 下面是两个APP用于模拟跨进程 再看下跨进程效果,AIDL ...

  9. Android开发之跨进程通讯-AIDL实现方法 (附源码)

    先看效果图,下面是广播和AIDL跨进程的方法 我们先创建AIDL文件定义接口方法 定义好接口方法如下图: // ITokenAidlInterface.aidl package com.example ...

最新文章

  1. SDUT-2449_数据结构实验之栈与队列十:走迷宫
  2. idea教程--Maven 骨架介绍
  3. 让PHP支持页面后退的两种方法
  4. python课程索引-0222
  5. 微信小程序报错:Unhandled promise rejection TypeError: WebAssembly.instantiate(): Argument 0 must be a buffe
  6. 网络综合实验结课总结
  7. 肯辛通VeriMark指纹识别器 驱动下载 与 安装指南(含视频教程) 型号:K67977 K64704 K62330
  8. 什么是端口映射?内网端口映射工具推荐
  9. STM8S103定时器1,定时器2多路PWM波输出
  10. 视频缓存合成工具分享
  11. 《Hive权威指南》第六章:查询
  12. 黑客很忙:拿巨额奖金以及帮助警察蜀黍破案|宅客周刊
  13. 动漫人物人体结构难学么?衣物怎么画?
  14. Elasticsearch学习,请先看这一篇!
  15. 孩子想养宠物,该不该同意?
  16. 解决WES 7 中Composite Bus找不到驱动的bug
  17. 禁忌搜索算法求解TSP旅行商问题C++(2020.11.19)
  18. 生成随机数(高斯分布)
  19. 查看安装的kafka的版本的方法
  20. [分享] linux利用nat123发布网站及注意事项

热门文章

  1. Java正则表达式较验手机号、邮箱
  2. [python进阶]12.继承的优缺点
  3. figma下载_不用担心Figma中的间距
  4. 安全态势感知产品对比_设计中的对比和人的感知
  5. mongodb数组字段prefix匹配返回
  6. Exchange Server 2016管理系列课件50.DAG管理之激活数据库副本
  7. Asp.net mvc 知多少(一)
  8. java实现各种算法
  9. 分享:MetaModel 3.2.5 发布,数据库元模型
  10. 第5章 初识JQuery