ContentProvider解析-获取ContentProvider接口
本篇基于Android Framework 8.0的源碼
對ContentProvider的使用從根本上來說也是圍繞著Binder IPC。跟使用其他系統服務類似,APP對ContentProvider的使用可以分成三部分:
- Client APP如何從系統服務中獲取到ContentProvider的調用接口
- ContentProvider如何將自己的調用接口發布到系統服務當中。
- Client APP調用ContentProvider接口的具體流程。
本文的核心便是Client APP如何從系統服務中獲取到ContentProvider的調用接口。
重要的數據對象
1. ContentProviderRecord
在記錄ContentProvider重要信息的數據對象中,毫無疑問ContentProviderRecord是最重要的。無論是在ProcessRecord中記錄本APP內已經發布的ContentProvider,還是在AMS中記錄所有APP發布的ContentProvider。
ContentProviderRecord中記錄的重要內容:
2. ProviderClientRecord
另外一個重要的則是ProviderClientRecord,用于在Client APP中緩存已經獲得的ContentProvider信息(包括本地的ContentProvider)。在使用ContentProvider時,如果發現緩存中已經存在對應的ContentProvider信息,則不再向AMS請求獲取ContentProvider接口,而是直接使用緩存中記錄的IContentProvider接口。
ProviderClientRecord中記錄的IContentProvider是由ContentProviderHolder傳來,在AMS初始化ContentProviderHolder時會將其中的IContentProvider賦值為IContentProvider的代理對象——ContentProviderProxy。后續通過ContentResolver執行ContentProvider的CRUD操作時,便是通過ContentProviderProxy對象Binder IPC到實際的ContentProvider中。獲取ContentProvider的流程解析
在Client APP使用過程中,一般不會直接與ContentProvider進行通信,使用ContentProvider的請求都是交由ContentResolver來執行。平時獲取ContentResolver都是通過Context的getContentResolver()來執行,最終都會執行的是ContextImpl中的getContentResolver():
//ContextImpl.java public ContentResolver getContentResolver() {return mContentResolver; } 復制代碼返回的是已經保存在Context中的mContentResolver,這一實例的初始化在ContextImpl的相關初始化函數當中:( 8.0在私有構造器中,4.2在init函數當中 )
//ContextImpl.java ... mContentResolver = new ApplicationContentResolver(this, mainThread, user); ... 復制代碼這里賦值的實例ApplicationContentResolver類型,這是一個ContentResolver接口的實現類,里邊重寫了獲取和釋放ContentProvider的邏輯。
@Overrideprotected IContentProvider acquireProvider(Context context, String auth) {return mMainThread.acquireProvider(context,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), true);}@Overrideprotected IContentProvider acquireExistingProvider(Context context, String auth) {return mMainThread.acquireExistingProvider(context,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), true);}@Overridepublic boolean releaseProvider(IContentProvider provider) {return mMainThread.releaseProvider(provider, true);}@Overrideprotected IContentProvider acquireUnstableProvider(Context c, String auth) {return mMainThread.acquireProvider(c,ContentProvider.getAuthorityWithoutUserId(auth),resolveUserIdFromAuthority(auth), false);}@Overridepublic boolean releaseUnstableProvider(IContentProvider icp) {return mMainThread.releaseProvider(icp, false);}@Overridepublic void unstableProviderDied(IContentProvider icp) {mMainThread.handleUnstableProviderDied(icp.asBinder(), true);}@Overridepublic void appNotRespondingViaProvider(IContentProvider icp) {mMainThread.appNotRespondingViaProvider(icp.asBinder());} 復制代碼具體方法實現的內容只是相當于轉發了一下請求到ActivityThread當中去執行具體的邏輯,由ActivityThread來負責同AMS進行交互。
acquireExistingProvider、acquireProvider和acquireUnstableProvider這三個方法最終都是轉發執行的同一個方法ActivityThread.acquireProvider()。區別在于最后一個標識符,true代表stable provider,false代表unstable provider. 在ActivityThread中的實現:
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);if (provider != null) {return provider;}IActivityManager.ContentProviderHolder holder = null;try {holder = ActivityManagerNative.getDefault().getContentProvider(getApplicationThread(), auth, userId, stable);} catch (RemoteException ex) {}if (holder == null) {Slog.e(TAG, "Failed to find provider info for " + auth);return null;}holder = installProvider(c, holder, holder.info,true /*noisy*/, holder.noReleaseNeeded, stable);return holder.provider;復制代碼acquireProvider() 分為三部分:
- 1.首先嘗試獲取緩存的IContentProvider。
- 2.獲取不到才通過AMS獲取IContentProvider。
- 3.獲取成功后先執行ActivityThread.installProvider再返回provider給ContentResolver。
第一部分:獲取緩存中的IContentProvider的邏輯:
synchronized (mProviderMap) {final ProviderKey key = new ProviderKey(auth, userId);final ProviderClientRecord pr = mProviderMap.get(key);if (pr == null) {return null;}IContentProvider provider = pr.mProvider;IBinder jBinder = provider.asBinder();if (!jBinder.isBinderAlive()) {Log.i(TAG, "Acquiring provider " + auth + " for user " + userId+ ": existing object's process dead");handleUnstableProviderDiedLocked(jBinder, true);return null;}ProviderRefCount prc = mProviderRefCountMap.get(jBinder);if (prc != null) {incProviderRefLocked(prc, stable);}return provider;} 復制代碼- 由于接口可能會被多線程調用,因此這里先加了鎖。
- 首先通過auth、userId從mProviderMap中獲取provider。
- 如果該provider所在的進程已經死亡則調用handleUnstableProviderDiedLocked,并返回null。
- 在handleUnstableProviderDiedLocked()里,如果死亡的provider保存在需要釋放的Provider列表里,則對其進行釋放清理;并通知AMS該unstable provider已經死亡。
- 如果保存在需要釋放的Provider列表里,則對其引用計數+1。
第二部分:通過AMS.getContentProvider()獲取IContentProvider接口
最后都在 AMS.getContentProviderImpl() 中執行。(這里是Blocking執行,lock是AMS。這把鎖很多地方都用到,比方注冊廣播接收器時也是用的這把鎖)
ActivityManagerService's getContentProviderImpl()
long startTime = SystemClock.uptimeMillis();//獲取Caller的進程信息ProcessRecord r = null;if (caller != null) {r = getRecordForAppLocked(caller);if (r == null) {throw new SecurityException("Unable to find app for caller " + caller+ " (pid=" + Binder.getCallingPid()+ ") when getting content provider " + name);}}boolean checkCrossUser = true;checkTime(startTime, "getContentProviderImpl: getProviderByName");//檢查對應的ContentProvider是否已經存在cpr = mProviderMap.getProviderByName(name, userId);if (cpr == null && userId != UserHandle.USER_SYSTEM) {cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);if (cpr != null) {cpi = cpr.info;if (isSingleton(cpi.processName, cpi.applicationInfo,cpi.name, cpi.flags)&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {userId = UserHandle.USER_SYSTEM;checkCrossUser = false;} else {//不是單例則清空繼續執行獲取cpr = null;cpi = null;}}}// ContentProvider存在同時所在進程存在&該進程沒被殺boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed;// 后續的執行的內容分段講解...... 復制代碼AMS會先嘗試獲取緩存的ContentProvider信息,并判斷該ContentProvider是否保持運行,并以此為根據來進行后續的執行。后續方法體的執行分為三部分:
1.ContentProvider保持運行:
if (providerRunning) {cpi = cpr.info;String msg;checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))!= null) {throw new SecurityException(msg);}checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");//如果provider已經發布或者正在發布則直接返回。if (r != null && cpr.canRunHere(r)) {ContentProviderHolder holder = cpr.newHolder(null);holder.provider = null;return holder;}try {if (AppGlobals.getPackageManager().resolveContentProvider(name, 0 /*flags*/, userId) == null) {return null;}} catch (RemoteException e) {}final long origId = Binder.clearCallingIdentity();checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");// 增加引用計數conn = incProviderCountLocked(r, cpr, token, stable);if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {//更新LRUcheckTime(startTime, "getContentProviderImpl: before updateLruProcess");updateLruProcessLocked(cpr.proc, false, null);checkTime(startTime, "getContentProviderImpl: after updateLruProcess");}}checkTime(startTime, "getContentProviderImpl: before updateOomAdj");final int verifiedAdj = cpr.proc.verifiedAdj;//更新進程adjboolean success = updateOomAdjLocked(cpr.proc, true);if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) {success = false;}maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);checkTime(startTime, "getContentProviderImpl: after updateOomAdj");if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success);//進程已經被殺if (!success) {//確保自己不為同時殺掉Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString()+ " is crashing; detaching " + r);//減少引用計數boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);checkTime(startTime, "getContentProviderImpl: before appDied");appDiedLocked(cpr.proc);checkTime(startTime, "getContentProviderImpl: after appDied");if (!lastRef) {// This wasn't the last ref our process had on// the provider... we have now been killed, bail.return null;}providerRunning = false;conn = null;} else {cpr.proc.verifiedAdj = cpr.proc.setAdj;}Binder.restoreCallingIdentity(origId);} 復制代碼關鍵的步驟都在上面有中文注釋,值得注意的是如果減少了引用計數之后,Client和ContentProider之間還有Connection,會導致Client進程被殺掉,所以直接返回空。
2.ContentProvider沒有在運行
if (!providerRunning) {try {checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");//先嘗試從PackageManager中獲取ContentProviderInfocpi = AppGlobals.getPackageManager().resolveContentProvider(name,STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");} catch (RemoteException ex) {}if (cpi == null) {return null;}boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,cpi.name, cpi.flags)&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);if (singleton) {userId = UserHandle.USER_SYSTEM;}cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);checkTime(startTime, "getContentProviderImpl: got app info for user");String msg;checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))!= null) {throw new SecurityException(msg);}checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");if (!mProcessesReady&& !cpi.processName.equals("system")) {throw new IllegalArgumentException("Attempt to launch content provider before system ready");}//如果provider的提供用戶沒有在運行則直接返回。if (!mUserController.isUserRunningLocked(userId, 0)) {Slog.w(TAG, "Unable to launch app "+ cpi.applicationInfo.packageName + "/"+ cpi.applicationInfo.uid + " for provider "+ name + ": user " + userId + " is stopped");return null;}ComponentName comp = new ComponentName(cpi.packageName, cpi.name);checkTime(startTime, "getContentProviderImpl: before getProviderByClass");//嘗試從通過包名從緩存中獲取ContentProvidercpr = mProviderMap.getProviderByClass(comp, userId);checkTime(startTime, "getContentProviderImpl: after getProviderByClass");final boolean firstClass = cpr == null;//沒有緩存在進入處理,嘗試獲取新的ContentProviderRecord.if (firstClass) {final long ident = Binder.clearCallingIdentity();if (mPermissionReviewRequired) {if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {return null;}}try {checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");//根據包名和userId嘗試獲取新的ApplicationInfo.ApplicationInfo ai =AppGlobals.getPackageManager().getApplicationInfo(cpi.applicationInfo.packageName,STOCK_PM_FLAGS, userId);checkTime(startTime, "getContentProviderImpl: after getApplicationInfo");//安裝包中獲取不到對應的信息,直接返回if (ai == null) {Slog.w(TAG, "No package info for content provider "+ cpi.name);return null;}ai = getAppInfoForUser(ai, userId);//封裝在新的ContentProviderRecordcpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);} catch (RemoteException ex) {// pm is in same process, this will never happen.} finally {Binder.restoreCallingIdentity(ident);}}checkTime(startTime, "getContentProviderImpl: now have ContentProviderRecord");if (r != null && cpr.canRunHere(r)) {//如果ContentProvider聲明了多進程運行或者處于同一個進程或者擁有同樣的uid, 則直接返回。return cpr.newHolder(null);}if (DEBUG_PROVIDER) Slog.w(TAG_PROVIDER, "LAUNCHING REMOTE PROVIDER (myuid "+ (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): "+ cpr.info.name + " callers=" + Debug.getCallers(6));//如果該ContentProvider已經正在啟動中,則i < 正在啟動的ContentProvider數。final int N = mLaunchingProviders.size();int i;for (i = 0; i < N; i++) {if (mLaunchingProviders.get(i) == cpr) {break;}}//沒有在啟動中,啟動它。if (i >= N) {final long origId = Binder.clearCallingIdentity();try {//設置包狀態try {checkTime(startTime, "getContentProviderImpl: before set stopped state");AppGlobals.getPackageManager().setPackageStoppedState(cpr.appInfo.packageName, false, userId);checkTime(startTime, "getContentProviderImpl: after set stopped state");} catch (RemoteException e) {} catch (IllegalArgumentException e) {Slog.w(TAG, "Failed trying to unstop package "+ cpr.appInfo.packageName + ": " + e);}//獲取對應的進程信息checkTime(startTime, "getContentProviderImpl: looking for process record");ProcessRecord proc = getProcessRecordLocked(cpi.processName, cpr.appInfo.uid, false);if (proc != null && proc.thread != null && !proc.killed) {if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER,"Installing in existing process " + proc);if (!proc.pubProviders.containsKey(cpi.name)) {checkTime(startTime, "getContentProviderImpl: scheduling install");//provider對應的進程已經啟動,但是該provider還沒有被publish//因此發送消息觸發publish provider的操作。proc.pubProviders.put(cpi.name, cpr);try {proc.thread.scheduleInstallProvider(cpi);} catch (RemoteException e) {}}} else {//provider所在的進程沒有啟動,或者已經被殺//啟動provider對應的進程checkTime(startTime, "getContentProviderImpl: before start process");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) {//無法啟動,直接返回空Slog.w(TAG, "Unable to launch app "+ cpi.applicationInfo.packageName + "/"+ cpi.applicationInfo.uid + " for provider "+ name + ": process is bad");return null;}}//將ContentProviderRecord中保存的launchingApp設置為所在的進程,并將provider//保存到正在啟動的ContentProvider列表cpr.launchingApp = proc;mLaunchingProviders.add(cpr);} finally {Binder.restoreCallingIdentity(origId);}}checkTime(startTime, "getContentProviderImpl: updating data structures");//保存新的ContentProviderRecord到mProviderMap中。if (firstClass) {mProviderMap.putProviderByClass(comp, cpr);}mProviderMap.putProviderByName(name, cpr);//增加Client與ContentProvider之間的引用計數。conn = incProviderCountLocked(r, cpr, token, stable);if (conn != null) {conn.waiting = true;}} 復制代碼關鍵步驟在文中有注釋,主要流程是:
- 權限檢查
- 通過PM獲取對應provider信息
- 檢查provider是否能夠正常運行
- 啟動對應的進程,然后publish provider
- 更新包狀態、緩存、引用計數。
3. 輪詢確保provider被publish完成
synchronized (cpr) {while (cpr.provider == null) {//provider所在進程已經不存在則直接返回if (cpr.launchingApp == null) {Slog.w(TAG, "Unable to launch app "+ cpi.applicationInfo.packageName + "/"+ cpi.applicationInfo.uid + " for provider "+ name + ": launching app became null");EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,UserHandle.getUserId(cpi.applicationInfo.uid),cpi.applicationInfo.packageName,cpi.applicationInfo.uid, name);return null;}try {if (DEBUG_MU) Slog.v(TAG_MU,"Waiting to start provider " + cpr+ " launchingApp=" + cpr.launchingApp);//Client與ContentProvider之間有引用時才執行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; 復制代碼概要總結getContentProviderImpl是如何實現獲取ContentProvider: 總是先嘗試從緩存中獲取已經publish的provider,并返回;如果緩存中不存在才嘗試創建新的provider并啟動對應的進程。
第三部分:執行ActivityThread.installProvider()
private ContentProviderHolder installProvider(Context context,ContentProviderHolder holder, ProviderInfo info,boolean noisy, boolean noReleaseNeeded, boolean stable) {ContentProvider localProvider = null;IContentProvider provider;if (holder == null || holder.provider == null) {//本地providerif (DEBUG_PROVIDER || noisy) {Slog.d(TAG, "Loading provider " + info.authority + ": "+ info.name);}Context c = null;ApplicationInfo ai = info.applicationInfo;//provider與caller在同一個包內if (context.getPackageName().equals(ai.packageName)) {c = context;} else if (mInitialApplication != null &&mInitialApplication.getPackageName().equals(ai.packageName)) {c = mInitialApplication;} else {try {c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);} catch (PackageManager.NameNotFoundException e) {// Ignore}}if (c == null) {Slog.w(TAG, "Unable to get context for package " +ai.packageName +" while loading content provider " +info.name);return null;}if (info.splitName != null) {try {c = c.createContextForSplit(info.splitName);} catch (NameNotFoundException e) {throw new RuntimeException(e);}}//創建本地providertry {final java.lang.ClassLoader cl = c.getClassLoader();localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();provider = localProvider.getIContentProvider();if (provider == null) {Slog.e(TAG, "Failed to instantiate class " +info.name + " from sourceDir " +info.applicationInfo.sourceDir);return null;}if (DEBUG_PROVIDER) Slog.v(TAG, "Instantiating local provider " + info.name);// XXX Need to create the correct context for this provider.//創建本地ContentProvider的ContextlocalProvider.attachInfo(c, info);} catch (java.lang.Exception e) {if (!mInstrumentation.onException(null, e)) {throw new RuntimeException("Unable to get provider " + info.name+ ": " + e.toString(), e);}return null;}} else {//來自本App外的ContentProviderprovider = holder.provider;if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "+ info.name);}ContentProviderHolder retHolder;synchronized (mProviderMap) {if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider+ " / " + info.name);IBinder jBinder = provider.asBinder();if (localProvider != null) {//本地provider分支ComponentName cname = new ComponentName(info.packageName, info.name);ProviderClientRecord pr = mLocalProvidersByName.get(cname);if (pr != null) {//緩存中已經存在本地provider實例。if (DEBUG_PROVIDER) {Slog.v(TAG, "installProvider: lost the race, "+ "using existing local provider");}provider = pr.mProvider;} else {//不存在,創建新的Client provider record并install,//然后保存到本地的緩存中holder = new ContentProviderHolder(info);holder.provider = provider;holder.noReleaseNeeded = true;pr = installProviderAuthoritiesLocked(provider, localProvider, holder);mLocalProviders.put(jBinder, pr);mLocalProvidersByName.put(cname, pr);}//返回值為install過的本地Content Provider HolderretHolder = pr.mHolder;} else {ProviderRefCount prc = mProviderRefCountMap.get(jBinder);if (prc != null) {//不為空,說明本地provider 引用緩存中已經保存有該provider的引用計數。if (DEBUG_PROVIDER) {Slog.v(TAG, "installProvider: lost the race, updating ref count");}//外部provider則增加引用計數。if (!noReleaseNeeded) {incProviderRefLocked(prc, stable);try {ActivityManager.getService().removeContentProvider(holder.connection, stable);} catch (RemoteException e) {//do nothing content provider object is dead any way}}} else {ProviderClientRecord client = installProviderAuthoritiesLocked(provider, localProvider, holder);if (noReleaseNeeded) {//本地provider或者來自系統的providerprc = new ProviderRefCount(holder, client, 1000, 1000);} else {//其他App的providerprc = stable? new ProviderRefCount(holder, client, 1, 0): new ProviderRefCount(holder, client, 0, 1);}//在本App的緩存中保存provider的引用計數。mProviderRefCountMap.put(jBinder, prc);}retHolder = prc.holder;}}return retHolder;} 復制代碼對于本地ContentProvider, installProvider做的事情是先創建一個本地provider,并將相應的內容(IContentProvider接口、本地ContentProvider實例等)保存到本App的緩存當中;對于外部的ContentProvider,installProvider做的事情則是在本App的緩存當中保存provider的引用計數。
轉載于:https://juejin.im/post/5a9a57ef5188255589494058
總結
以上是生活随笔為你收集整理的ContentProvider解析-获取ContentProvider接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用Exchange 2000开发企业办公
- 下一篇: 试卷分析的四个度:难度、区分度、信度、效