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.pswp.cn/news/286238.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/286238.shtml
英文地址,請注明出處:http://en.pswp.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;微軟官方對性能優化這一塊也不建議使用過多的…