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

在开发中,假如,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支持跨进程数据共享与"互斥、同步"

仅供参考,欢迎指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/286238.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C#控件详解】对话框类控件(打开文件,保存文件,选择字体和颜色)

在实际应用中,我们会用到各种各样的对话框,如打开文件,保存文件,选择字体和颜色等等。本文详细讲解C#中的颜色对话框、打开文件对话框、字体对话框、浏览文件夹对话框和保存文件对话框。 文章目录 1. ColorDialog对话框2. FolderBrowserDialog对话框3. FontDialog对话框4. …

动物界的再生一个故事

猪月薪5千&#xff0c;打算用20万建一个窝&#xff0c;老虎不允许&#xff0c;说私自建就是违章建筑&#xff0c;只允许向狼买。 狼是搞工程的&#xff0c;先用20万贿赂老虎取得开发权&#xff0c;再用50万元向老虎买这块地&#xff0c; 花10万元把猪圈盖好&#xff0c;向猪要价…

C语言试题三十之请编写函数function对长度位7个字符的字符串,除首尾字符外,将其余5个字符按ascii码降序排列。

📃个人主页:个人主页 🔥系列专栏:C语言试题200例目录 💬推荐一款刷算法、笔试、面经、拿大公司offer神器 👉 点击跳转进入网站 ✅作者简介:大家好,我是码莎拉蒂,CSDN博客专家(全站排名Top 50),阿里云博客专家、51CTO博客专家、华为云享专家 1、题目 请编写函数…

基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计

系列文章基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客&#xff1f;基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目基于.NetCore开发博客项目 StarBlog - (3) 模型设计基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入基于.N…

【SPSS统计分析】SPSS19.0简体中文版安装教程(附SPSS19.0简体中文版下载)

spss19.0中文版是一款专业数据统计软件,受到各种社会学科的研究生、研究员的欢迎。软件能够对信息的采集、处理、分析进行全面评估和预测。spss19.0中文版新增加了广义线性混合模型、自动线性模型、一个统计网页入口portal和直复营销direct marketing功能等。另外,IBM 针对SP…

Java 比较两个版本号的大小 (通用)

/*** 版本号比较** param v1* param v2* return 0代表相等&#xff0c;1代表左边大&#xff0c;-1代表右边大* Utils.compareVersion("1.0.358_20180820090554","1.0.358_20180820090553")1*/public static int compareVersion(String v1, String v2) {if …

四、登录注册页功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 首先打开在线编辑器进入我们的项目&#xff1a;https://editor.ivx.cn/ 一、登录页功能实现 上一节中已经完成了登录页的页面制作&#xff0c;那么这一节就开始对应的完成登录页的功能实现。 登录页的功能实现主要是对用户…

Oracle 12c应用连接VIP轮训负载均衡?

目前很多生产数据库都是两节点RAC&#xff0c;应用连接的数据库通常会连接SCAN IP或者VIP,这里做一个小实验&#xff0c;如果我们使用VIP连接&#xff0c; 应该怎样配置我们的客户端&#xff0c;负载均衡是否是严格意义上的轮训机制&#xff0c;只有通过自己的测试才能得到准备…

C语言试题三十一之判断字符串是否为回文?若是则函数返回1,主函数中输出yes,否则返回0,主函数中输出no。回文是指顺读和倒读都是一样的字符串。

📃个人主页:个人主页 🔥系列专栏:C语言试题200例目录 💬推荐一款刷算法、笔试、面经、拿大公司offer神器 👉 点击跳转进入网站 ✅作者简介:大家好,我是码莎拉蒂,CSDN博客专家(全站排名Top 50),阿里云博客专家、51CTO博客专家、华为云享专家 1、题目 请编写函数…

关于html5

html5 是用来 将 js 和 css 结合起来 从而实现 各种功能 javascript 用来定义 html5 页面的逻辑 css 来定义 html5 中的显示样式

【SignalR全套系列】之在.Net6中实现SignalR分组通信

微信公众号&#xff1a;趣编程ACE关注可了解更多的.NET日常实战开发技巧&#xff0c;如需源码 请公众号后台留言 源码;[如果觉得本公众号对您有帮助&#xff0c;欢迎关注]前文回顾【SignalR全套系列】之在.Net6中实SignalR通信SignalR中给客户端分组调用演示服务端代码实现1.Pr…

【ArcGIS风暴】中国756个气象台站分布Shapefile数据下载

在写论文时,通常要做研究区概况图,需要添加气象站点分布,或者在做气温或降水空间插值时,需要将气温和降水数据链接到气象台站上。气象数据通常可以到资源环境科学与数据中心去下载,为了使用的方便,本文分享中国区域756个气象台站shp格式矢量数据,如果你的研究区是西北五…

web上传大文件的配置

1、项目本身的webconfig 在<system.web>字段下 <httpRuntime targetFramework"4.5" requestLengthDiskThreshold"256" maxRequestLength"1000000000" executionTimeout"120"/> 2、找到C:\Windows\System32\inetsrv\confi…

七、文章管理页面及功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、文章管理页页面制作 文章管理页的基本结构与首页类似&#xff0c;我们复制一个首页&#xff0c;并且重命名首页的名称为文章管理页&#xff1a; 我们接着删除如下图所框选部分内容&#xff1a; 接着重命名导航为内容…

Android 发起加入QQ群、打开网址、启动拨打电话界面

/****************** 发起添加群流程。 ** param key 由官网生成的key* return 返回true表示呼起手Q成功&#xff0c;返回fals表示呼起失败******************/public static void joinQQGroup(Activity _this, String key){Intent intent new Intent();intent.setData(Ur…

C语言试题三十二之编写函数function,它的功能是:将一个字符串转换为一个整数(不得调用c语言提供的将字符串转换为整数的函数)。

📃个人主页:个人主页 🔥系列专栏:C语言试题200例目录 💬推荐一款刷算法、笔试、面经、拿大公司offer神器 👉 点击跳转进入网站 ✅作者简介:大家好,我是码莎拉蒂,CSDN博客专家(全站排名Top 50),阿里云博客专家、51CTO博客专家、华为云享专家 1、题目 请编写函数…

【ArcGIS风暴】ArcGIS快捷键大全

使用快捷键能提高工作效率,本文总结了ArcGIS快捷键大全。当然了,为了个性化定制,也可以在ArcGIS中自定义快捷键。 扩展阅读: 【ArcGIS微课1000例】0015:ArcGIS如何创建/自定义快捷键? 文章目录 1. 访问 ArcMap 菜单命令2. 窗口操纵3. 刷新或暂停地图绘制4. 通过拖放进行…

游戏引擎cocos2d-android使用大全

做手机游戏需要三个核心的类&#xff0c;即&#xff1a;SurfaceView&#xff0c;SurfaceHolder&#xff0c;Thread。帧数要在30帧左右是最好的。 cocos2d游戏引擎 封装好的框架&#xff0c;可直接使用 cocos2d-android &#xff08;用java编程&#xff09; 导演&#xff1a;控…

禁用CMFCRibbonApplicationButton的单击和双击事件

为了禁用CMFCRibbonApplicationButton的单击和双击事件&#xff0c;我重载了CMFCRibbonApplicationButton如下: 1. MyRibbonApplicationButton.h文件内容&#xff1a; #pragma once class CMyRibbonApplicationButton : public CMFCRibbonApplicationButton{public: CMyRibbonA…

.net core 抛异常对性能影响的求证之路

一、前言在.net 社区中曾经听到过很多关于大量抛异常会影响性能这样的结论&#xff0c;心中一直就存在各种疑问。项目中使用自定义异常来处理业务很爽&#xff0c;但是又担心大量抛业务异常存在性能问题。查阅了各种文档&#xff0c;微软官方对性能优化这一块也不建议使用过多的…