网站开发是什么意思啊,壹像素网站,wordpress 插件使用,潍坊网站建设尚荣在开发中#xff0c;假如#xff0c;A、B进程有部分信息需要同步#xff0c;这个时候怎么处理呢#xff1f;设想这么一个场景#xff0c;有个业务复杂的Activity非常占用内存#xff0c;并引发OOM#xff0c;所以#xff0c;想要把这个Activity放到单独进程#xff0c…在开发中假如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 dont 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;}NullableOverridepublic Cursor query(NonNull Uri uri, Nullable String[] projection, Nullable String selection, Nullable String[] selectionArgs, Nullable String sortOrder) {return null;}NullableOverridepublic String getType(NonNull Uri uri) {return null;}NullableOverridepublic 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 callers process, so dont make a connection// and just let the caller instantiate its own instance.ContentProviderHolder holder cpr.newHolder(null);// dont 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启动 publishedWait 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 apps processes has its own content provider object. If false, the apps 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:multiprocessfalse则系统中只会存在一个provider实例否则可以存在多个多个的话可能会提高性能因为它避免了跨进程通信毕竟对象就在自己的进程空间可以直接访问但是这会增加系统负担另外对于单进程能够保证的互斥问题也会无效如果APP需要数据更新还是保持不开启的好。 总结ContentProvider只是Android为了跨进程共享数据提供的一种机制本身基于Binder实现在操作数据上只是一种抽象具体要自己实现ContentProvider只能保证进程间的互斥无法保证进程内需要自己实现作者看书的小蜗牛Android ContentProvider支持跨进程数据共享与互斥、同步 仅供参考欢迎指正