Android ContentProvider支持跨进程数据共享与互斥、同步 杂谈
在開發(fā)中,假如,A、B進(jìn)程有部分信息需要同步,這個(gè)時(shí)候怎么處理呢?設(shè)想這么一個(gè)場景,有個(gè)業(yè)務(wù)復(fù)雜的Activity非常占用內(nèi)存,并引發(fā)OOM,所以,想要把這個(gè)Activity放到單獨(dú)進(jìn)程,以保證OOM時(shí)主進(jìn)程不崩潰。但是,兩個(gè)整個(gè)APP有些信息需要保持同步,比如登陸信息等,無論哪個(gè)進(jìn)程登陸或者修改了相應(yīng)信息,都要同步到另一個(gè)進(jìn)程中去,這個(gè)時(shí)候怎么做呢?
- 第一種:一個(gè)進(jìn)程里面的時(shí)候,經(jīng)常采用SharePreference來做,但是SharePreference不支持多進(jìn)程,它基于單個(gè)文件的,默認(rèn)是沒有考慮同步互斥,而且,APP對SP對象做了緩存,不好互斥同步,雖然可以通過FileLock來實(shí)現(xiàn)互斥,但同步仍然是一個(gè)問題。
- 第二種:基于Binder通信實(shí)現(xiàn)Service完成跨進(jìn)程數(shù)據(jù)的共享,能夠保證單進(jìn)程訪問數(shù)據(jù),不會有互斥問題,可是同步的事情仍然需要開發(fā)者手動處理。
- 第三種:基于Android提供的ContentProvider來實(shí)現(xiàn),ContentProvider同樣基于Binder,不存在進(jìn)程間互斥問題,對于同步,也做了很好的封裝,不需要開發(fā)者額外實(shí)現(xiàn)。
因此,在Android開發(fā)中,如果需要多進(jìn)程同步互斥,ContentProvider是一個(gè)很好的選擇,本文就來看看,它的這個(gè)技術(shù)究竟是怎么實(shí)現(xiàn)的。
概述
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數(shù)據(jù)的存儲和獲取抽象了統(tǒng)一的接口,并支持在不同的應(yīng)用程序之間共享數(shù)據(jù),Android內(nèi)置的許多數(shù)據(jù)都是使用ContentProvider形式供開發(fā)者調(diào)用的 (如視頻,音頻,圖片,通訊錄等),它采用索引表格的形式來組織數(shù)據(jù),無論數(shù)據(jù)來源是什么,ContentProvider都會認(rèn)為是一種表,這一點(diǎn)從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;} }復(fù)制代碼可以看到每個(gè)ContentProvider都需要自己實(shí)現(xiàn)增、刪、改、查的功能,因此,可以將ContentProvider看做Android提供一個(gè)抽象接口層,用于訪問表格類的存儲媒介,表格只是一個(gè)抽象,至于底層存儲媒介到底如何組織,完全看用戶實(shí)現(xiàn),也就是說ContentProvider自身是沒有數(shù)據(jù)更新及操作能力,它只是將這種操作進(jìn)行了統(tǒng)一抽象。
ContentProvider抽象接口.jpg了解了ContentProvider的概念及作用后,下面就從用法來看看ContentProvider是如何支持多進(jìn)程同步通信的。
ContentProvider代理的同步獲取
多進(jìn)程對于ContentProvider的訪問請求最終都會按照隊(duì)列進(jìn)入ContentProvider進(jìn)程,而在單進(jìn)程中,ContentProvider對于數(shù)據(jù)的訪問很容易做到多線程互斥,一個(gè)Sycronized關(guān)鍵字就能搞定,看一下基本用法:
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);復(fù)制代碼getContentResolver 其實(shí)獲取的是一個(gè)ApplicationContentResolver實(shí)例,定義在ContextImpl中,只有在真正操作數(shù)據(jù)的時(shí)候才會去獲取Provider, 詳細(xì)看一下插入操作:
public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {<!--首先獲取Provider代理-->IContentProvider provider = acquireProvider(url);try {<!--利用IContentProvider代理插入數(shù)據(jù)-->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);}復(fù)制代碼這里是一個(gè)典型的基于Binder通信的AIDL實(shí)現(xiàn),IContentProvider的Proxy與Stub分別是ContentProviderProxy與ContentProvider的內(nèi)部類
abstract public class ContentProviderNative extends Binder implements IContentProvider class Transport extends ContentProviderNative,復(fù)制代碼首先看一下ActivityThread的acquireProvider,對于當(dāng)前進(jìn)程而言acquireProvider是一個(gè)同步的過程,如果ContentProvider所處的進(jìn)程已經(jīng)啟動,那么acquireProvider可以直接獲取服務(wù)代理,如果未啟動,則等待ContentProvider進(jìn)程啟動,再獲取代理。
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 {<!--關(guān)鍵點(diǎn)1 獲取Provider,如果沒有安裝,則等待安裝完畢-->holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);} catch (RemoteException ex) {}if (holder == null) {return null;}<!--關(guān)鍵點(diǎn)2 這里僅僅是增加計(jì)數(shù) ,Provider到這里其實(shí)已經(jīng)安裝完畢-->// 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;}復(fù)制代碼首先看一下關(guān)鍵點(diǎn)1,這里阻塞等待直到獲取Provider代理,如果Provider未啟動,則先啟動,直接看一下ActivityManagerService(其實(shí)Android四大組件都?xì)w他管理),簡單看一下獲取流程(只描述個(gè)大概):
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,String name, IBinder token, boolean stable, int userId) {ContentProviderRecord cpr;ContentProviderConnection conn = null;ProviderInfo cpi = null;synchronized(this) {...<!--關(guān)鍵點(diǎn)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;<!--關(guān)鍵點(diǎn)2 是否允許調(diào)用進(jìn)程自己實(shí)現(xiàn)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();<!--關(guān)鍵點(diǎn)3 使用ContentProvider進(jìn)程中的ContentProvider,僅僅增加引用計(jì)數(shù)--> // 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);...<!--查看目標(biāo)進(jìn)程是否啟動-->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 {<!--如果未啟動,啟動進(jìn)程,并安裝-->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;}復(fù)制代碼ContentProvider的啟動同Activity或者Service都是比較類似的,如果進(jìn)程未啟動,就去啟動進(jìn)程,在創(chuàng)建進(jìn)程之后,調(diào)用ActivityThread的attach方法,通知AMS新的進(jìn)程創(chuàng)建完畢,并初始化ProcessRecord,隨后,查詢所有和本進(jìn)程相關(guān)的ContentProvider信息,并調(diào)用bindApplication方法,通知新進(jìn)程安裝并啟動這些ContentProvider。ContentProvider有些不一樣的就是: ContentProvider調(diào)用端會一直阻塞,直到ContentProvider published才會繼續(xù)執(zhí)行,這一點(diǎn)從下面可以看出:
synchronized (cpr) {while (cpr.provider == null) { 復(fù)制代碼其次,這里有個(gè)疑惑的地方,ContentProvider一般都是隨著進(jìn)程啟動的,不過為什么會存在進(jìn)程啟動,但是ContentProvider未published的問題呢?不太理解,難道是中間可能存在什么同步問題嗎?下面這部分代碼完全看不出為什么存在:
if (proc != null && proc.thread != null) {<!--如果進(jìn)程啟動,發(fā)消息安裝Providers-->if (!proc.pubProviders.containsKey(cpi.name)) {proc.pubProviders.put(cpi.name, cpr);try {proc.thread.scheduleInstallProvider(cpi);} catch (RemoteException e) {}}} 復(fù)制代碼ContentProvider數(shù)據(jù)的更新
通過ContentProvider對于數(shù)據(jù)的操作都是同步的,不過contentResolver.notifyChange通知是異步的
contentResolver.insert(FileContentProvider.CONTENT_URI, contentValues);contentResolver.notifyChange(FileContentProvider.CONTENT_URI, null);復(fù)制代碼ContentProviderProxy會發(fā)消息給服務(wù)端,而服務(wù)端這里直接調(diào)用抽象的insert函數(shù),如果需要insert操作是同步的,那么再實(shí)現(xiàn)ContentProvider的時(shí)候,就可以直接向數(shù)據(jù)庫寫數(shù)據(jù),當(dāng)然也可以實(shí)現(xiàn)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;}復(fù)制代碼這里有一點(diǎn)要注意,Binder框架默認(rèn)是不支持Stub端同步的,也就是說,即時(shí)基于ContentProvider,如果需要對一個(gè)文件進(jìn)行完全互斥訪問,在單個(gè)進(jìn)程內(nèi)同樣需要處理互斥操作,不過單進(jìn)程互斥好處理,Sycronized關(guān)鍵字就可以了。
ContentProvider數(shù)據(jù)變更通知
ContentProvider支持多進(jìn)程訪問,當(dāng)一個(gè)進(jìn)程操作ContentProvider變更數(shù)據(jù)之后,可能希望其他進(jìn)程能收到通知,比如進(jìn)程A往數(shù)據(jù)庫插入了一條聊天信息,希望在進(jìn)程B的UI中展現(xiàn)出來,這個(gè)時(shí)候就需要一個(gè)通知機(jī)制,Android也是提供了支持,不過它是一個(gè)通用的數(shù)據(jù)變更同步通知:基于ContentService服務(wù):
<!--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);復(fù)制代碼上面的兩個(gè)可能在統(tǒng)一進(jìn)程,也可能在不同進(jìn)程,
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,ContentObserver observer, int userHandle) {try {getContentService().registerContentObserver(uri, notifyForDescendents,observer.getContentObserver(), userHandle);} catch (RemoteException e) {} }復(fù)制代碼其實(shí)這里跟ContentProvider的關(guān)系已經(jīng)不是很大,這里牽扯到另一個(gè)服務(wù):ContentService,它是Android平臺中數(shù)據(jù)更新通知的執(zhí)行者,由SystemServer進(jìn)程啟動,所有APP都能調(diào)用它發(fā)送數(shù)據(jù)變動通知,其實(shí)就是一個(gè)觀察者模式,牽扯到另一個(gè)服務(wù),不過多講解。
android:multiprocess在ContentProvider中的作用
默認(rèn)情況下是不指定android:process跟multiprocess的,它們的值默認(rèn)為false,會隨著應(yīng)用啟動的時(shí)候加載,如果對provider指定android:process和android:multiprocess,表現(xiàn)就會不一通了,如果設(shè)置android:process,那ContentProvider就不會隨著應(yīng)用啟動,如果設(shè)置了android:multiprocess,則可能存在多個(gè)ContentProvider實(shí)例。
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的作用是:是否允許在調(diào)用者的進(jìn)程里實(shí)例化provider,如果android:multiprocess=false,則系統(tǒng)中只會存在一個(gè)provider實(shí)例,否則,可以存在多個(gè),多個(gè)的話,可能會提高性能,因?yàn)樗苊饬丝邕M(jìn)程通信,畢竟,對象就在自己的進(jìn)程空間,可以直接訪問,但是,這會增加系統(tǒng)負(fù)擔(dān),另外,對于單進(jìn)程能夠保證的互斥問題,也會無效,如果APP需要數(shù)據(jù)更新,還是保持不開啟的好。
總結(jié)
- ContentProvider只是Android為了跨進(jìn)程共享數(shù)據(jù)提供的一種機(jī)制,
- 本身基于Binder實(shí)現(xiàn),
- 在操作數(shù)據(jù)上只是一種抽象,具體要自己實(shí)現(xiàn)
- ContentProvider只能保證進(jìn)程間的互斥,無法保證進(jìn)程內(nèi),需要自己實(shí)現(xiàn)
作者:看書的小蝸牛
Android ContentProvider支持跨進(jìn)程數(shù)據(jù)共享與"互斥、同步"
僅供參考,歡迎指正
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Android ContentProvider支持跨进程数据共享与互斥、同步 杂谈的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unhandled event loop
- 下一篇: Android Bootloader L