簡介
本文原文在我的部落格CheapTalks,歡迎大家去看看~
ContentProvider相信大家都耳熟能詳了,在安卓系統越來越注重系統安全的前提下,不知道大家好不好奇provider是如何在Android層層安全措施之下使程式之間實現資料增刪改查操作的。仔細想來,我們大概能猜到,安卓程式間通訊慣用的”伎倆”是binder call,ContentProvider的實現很有可能就使用AMS當作provider的客戶端與服務端的中間人。本篇文章,我將詳細分析客戶端app是如何請求到在其他應用程式執行的ContentProvider的,文章最後會附帶著這期間AMS和APP兩端涉及到的資料結構。
由於篇幅有限,ContentProvider涉及到的其它知識我沒有細講。例如,資料的query很有可能涉及到大量的資料,而安卓的binder同步緩衝區也才1016K這麼大,所以provider的查詢操作實際上是混合了ashmem與binder這兩種跨程式通訊技術,其中binder的主要作用是傳送ashmem的fd;又如,應用開發常常使用到的ContentObserver,這塊的分析可以在我以前寫的ContentService的分析找到。
從CRUD之query說起
一般我們寫APP時,是通過ContentResolver來進行CRUD呼叫的。而這個操作一般是通過context.getContentResolver().xxx這樣的呼叫鏈過來的。Context這塊是個裝飾者模式,真實的執行者是ContextImp, 這塊我不多言了,我們從ContextImpl這塊的原始碼開始分析。
ContextImpl.java
private final ApplicationContentResolver mContentResolver;
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
...
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}複製程式碼
可以看到,最終ApplicationContentResolver負責了我們的APP ContentProvider CRUD這塊的操作。
ContentResolver.java
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
...
// 獲取一個unstableProvider,這個stable與unstable的概念在於AMS中會維護unstable, stable這兩個計數
// 如果當stable的計數大於0,當ContentProvider服務端程式死亡時,那麼使用這個服務的客戶端程式也會受牽連被殺死
// 這裡query先嚐試使用unstableProvider,表示AMS那邊只會增加unstable的計數,客戶端不會收到聯級誅殺的牽連
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();
...
try {
// binder call到服務端,返回了一個cursor物件
// 當呼叫cursor.close時,會呼叫調releaseProvider來釋放ContentProvider服務端與客戶端之間的引用
qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
// reference though, so we might recover!!! Let`s try!!!!
// This is exciting!!1!!1!!!!1
// 第一次使用unstable嘗試,服務端程式可能死亡了拋了異常
// 先釋放unstableProvider相關的引用
unstableProviderDied(unstableProvider);
// 第二次進行嘗試時,將使用stableProvider
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
// 注意,失敗一次後將使用stableProvider,這次如果服務端程式被殺
// 並且cursor還沒有呼叫close之前,那麼客戶端的程式會受到牽連也被殺死
qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
...
// 裝飾一下
CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
stableProvider != null ? stableProvider : acquireProvider(uri));
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
// 如果不為空才close
// 上面的操作如果成功了,qCursor是不會是空的,所以這個關閉操作交給了APP端來做
if (qCursor != null) {
qCursor.close();
}
...
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
if (stableProvider != null) {
releaseProvider(stableProvider);
}
}
}複製程式碼
有一點需要提一下,當binder call時丟擲DeadObjectException時,不一定是對端程式死亡了,有可能是對端binder緩衝區被佔滿之類的異常;這塊之前問過Google的工程師,他們貌似有細化這個DeadObjectException的計劃,不過現在來看DeadObjectException的丟擲並不代表者對端程式就死亡了。
程式碼註釋很詳細,大體流程可以看看圖。
核心操作acquireProvider
客戶端操作
在ContentResolver中呼叫acquireStableProvider和acquireUnstableProvider之後都會呼叫到ApplicationContextResolver這個子類中,之後再呼叫ActivityThread的acquireProvider的方法,區別stable與unstable的Provider在於這個stable欄位,若是true則是獲取stableProvider,反之是獲取unstableProvider。我們們看程式碼:
ApplicationContentResolver.java
private static final class ApplicationContentResolver extends ContentResolver {
private final ActivityThread mMainThread;
private final UserHandle mUser;
// stableProvider
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
// unstableProvider
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
return mMainThread.acquireProvider(c,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), false);
}
...複製程式碼
ApplicationContentResolver其實是ActivityThread的內部類,這裡為了方便看,這兩塊的程式碼還是分開分析吧
ActivityThread.java
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
// 現在本地尋找provider,如果沒有的話才像AMS去請求
final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
...
IActivityManager.ContentProviderHolder holder = null;
try {
// 向AMS請求ContentProvider,這塊是核心方法
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;
}
// "安裝"provider,說白了就是新建例項,增減引用這類操作
// 這塊的程式碼放到後面的scheduleInstallProvider再分析
holder = installProvider(c, holder, holder.info,
true /*noisy*/, holder.noReleaseNeeded, stable);
return holder.provider;
}複製程式碼
AMS.getContentProviderImpl (1)
ActivityThread binder call 到AMS之後,緊接著就會呼叫getContentProviderImpl,這個方法比較大,分拆進行分析
ActivityManagerService.java
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
ContentProviderRecord cpr;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
synchronized (this) {
long startTime = SystemClock.elapsedRealtime();
// 獲取呼叫者的ProcessRecord物件
ProcessRecord r = null;
if (caller != null) {
r = getRecordForAppLocked(caller);
...
}
boolean checkCrossUser = true;
...
// 通過uri authority name來獲取ContentProviderRecord
cpr = mProviderMap.getProviderByName(name, userId);
...
if (cpr == null && userId != UserHandle.USER_OWNER) {
// 檢查userId=0是否已存有ContentProviderRecord
cpr = mProviderMap.getProviderByName(name, UserHandle.USER_OWNER);
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_OWNER;
checkCrossUser = false;
} else {
cpr = null;
cpi = null;
}
}
}複製程式碼
這一段AMS會在providerMap中尋找正在執行的provider。如果找到,那麼說明這個provider已經被啟動了,隨後增加引用即可;如果沒有找到,那麼就需要呼叫installProvider在provider客戶端中進行provider的”安裝”,隨後AMS將等待這個客戶端publishProvider。
AMS.getContentProviderImpl (2)
// 根據先前的查詢,判斷當前的provider是否正在執行
boolean providerRunning = cpr != null;
if (providerRunning) {
cpi = cpr.info;
String msg;
...
// 如果provider能夠在客戶端進行直接執行,那麼在這裡就返回provider給客戶端
if (r != null && cpr.canRunHere(r)) {
ContentProviderHolder holder = cpr.newHolder(null);
holder.provider = null;
return holder;
}
final long origId = Binder.clearCallingIdentity();
...
// 增加引用
conn = incProviderCountLocked(r, cpr, token, stable);
// 如何stable和unstable的總引用計數為1,那麼更新LruProcess列表
if (conn != null && (conn.stableCount + conn.unstableCount) == 1) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
...
updateLruProcessLocked(cpr.proc, false, null);
checkTime(startTime, "getContentProviderImpl: after updateLruProcess");
}
}
if (cpr.proc != null) {
...
// 更新provider程式的adj
boolean success = updateOomAdjLocked(cpr.proc);
maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);
...
if (!success) {
...
// 如果不成功,那麼減少引用計數並殺死provider程式
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;
}
}
Binder.restoreCallingIdentity(origId);
}複製程式碼
AMS.getContentProviderImpl (3)
boolean singleton;
// 如果provider沒有正在執行
if (!providerRunning) {
try {
checkTime(startTime, "getContentProviderImpl: before resolveContentProvider");
// 獲取ProviderInfo
cpi = AppGlobals.getPackageManager().
resolveContentProvider(name,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
checkTime(startTime, "getContentProviderImpl: after resolveContentProvider");
} catch (RemoteException ex) {
}
// 如果為空,則說明沒有找到這個provider,直接返回空給客戶端
if (cpi == null) {
return null;
}
// Provider是否為單例
singleton = isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags)
&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
if (singleton) {
userId = UserHandle.USER_OWNER;
}
cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
...
// 通過ComponentName獲取providerMap中的cpr
ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
cpr = mProviderMap.getProviderByClass(comp, userId);
...
// 該provider是否是第一次被建立
final boolean firstClass = cpr == null;
if (firstClass) {
final long ident = Binder.clearCallingIdentity();
try {
ApplicationInfo ai =
AppGlobals.getPackageManager().
getApplicationInfo(
cpi.applicationInfo.packageName,
STOCK_PM_FLAGS, userId);
...
ai = getAppInfoForUser(ai, userId);
// 建立一個cpr
cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
} finally {
Binder.restoreCallingIdentity(ident);
}
}
...
if (r != null && cpr.canRunHere(r)) {
return cpr.newHolder(null);
}
...
final int N = mLaunchingProviders.size();
int i;
for (i = 0; i < N; i++) {
if (mLaunchingProviders.get(i) == cpr) {
break;
}
}
// provider還沒有被執行
if (i >= N) {
final long origId = Binder.clearCallingIdentity();
try {
// Content provider is now in use, its package can`t be stopped.
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);
}
// 獲取到執行provider的程式
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
// 如果這個程式已經啟動,那麼binder call給這個程式,建立provider
if (proc != null && proc.thread != null) {
if (!proc.pubProviders.containsKey(cpi.name)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
proc.pubProviders.put(cpi.name, cpr);
try {
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
} else {
// 如果程式沒有啟動,那麼就啟動這個程式
// 需要說的一點是,程式啟動完畢後,建立provider的操作將會在ActivityThread初始化時進行
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false);
// 程式沒有建立成功,直接返回空給客戶端
if (proc == null) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
+ name + ": process is bad");
return null;
}
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
...
// 如果第一次建立這個provider的例項,在providerMap中進行快取
if (firstClass) {
mProviderMap.putProviderByClass(comp, cpr);
}
mProviderMap.putProviderByName(name, cpr);
// 增加引用
conn = incProviderCountLocked(r, cpr, token, stable);
if (conn != null) {
conn.waiting = true;
}
}
}複製程式碼
AMS.getContentProviderImpl (4)
此時getContentProviderImpl的分析已經接近尾聲。我們看到,如果provider此時尚未在其程式中被建立,那麼AMS將會對這個provider進行例項化,也就是”publish”釋出。AMS會在這裡等待APP程式的完成,隨後才會返回。
synchronized (cpr) {
while (cpr.provider == null) {
if (cpr.launchingApp == null) {
...
return null;
}
try {
...
if (conn != null) {
conn.waiting = true;
}
// 等待provider"釋出"完成
cpr.wait();
} catch (InterruptedException ex) {
} finally {
if (conn != null) {
conn.waiting = false;
}
}
}
}
return cpr != null ? cpr.newHolder(conn) : null;複製程式碼
scheduleInstallProvider
應用provider的例項化有兩個入口,一個是當程式已經存在時,AMS binder call到APP例項化某個provider;一個是當程式與Application互相繫結時,批量對provider進行安裝。
ActivityThread.java
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<IActivityManager.ContentProviderHolder> results =
new ArrayList<IActivityManager.ContentProviderHolder>();
for (ProviderInfo cpi : providers) {
...
// 對provider進行例項化
IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}
try {
// 完成例項化,通知AMS,進行provider"釋出"
ActivityManagerNative.getDefault().publishContentProviders(
getApplicationThread(), results);
} catch (RemoteException ex) {
}
}複製程式碼
installProvider (1)
如果provider尚未例項化,則需要在這個宿主程式中進行”安裝”
private IActivityManager.ContentProviderHolder installProvider(Context context,
IActivityManager.ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
// 如果向AMS.getContentProviderImpl返回NULL或者需要app"安裝"provider,就會對provider在本地嘗試例項化
if (holder == null || holder.provider == null) {
...
Context c = null;
ApplicationInfo ai = info.applicationInfo;
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
}
}
...
try {
// 利用反射進行provider例項化
final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
...
localProvider.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 {
provider = holder.provider;
if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
+ info.name);
}複製程式碼
installProvider (2)
進行引用方面的操縱;在宿主程式進行provider的快取
IActivityManager.ContentProviderHolder retHolder;
synchronized (mProviderMap) {
// 獲取provider代理
IBinder jBinder = provider.asBinder();
// 如果這個provider在上一個操作剛被建立
if (localProvider != null) {å
ComponentName cname = new ComponentName(info.packageName, info.name);
ProviderClientRecord pr = mLocalProvidersByName.get(cname);
// 本地快取
if (pr != null) {
...
provider = pr.mProvider;
} else {
holder = new IActivityManager.ContentProviderHolder(info);
holder.provider = provider;
holder.noReleaseNeeded = true;
// 根據Uri authority name進行分類快取
pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
mLocalProviders.put(jBinder, pr);
mLocalProvidersByName.put(cname, pr);
}
retHolder = pr.mHolder;
} else {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
if (DEBUG_PROVIDER) {
Slog.v(TAG, "installProvider: lost the race, updating ref count");
}
...
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
try {
ActivityManagerNative.getDefault().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) {
prc = new ProviderRefCount(holder, client, 1000, 1000);
} else {
prc = stable
? new ProviderRefCount(holder, client, 1, 0)
: new ProviderRefCount(holder, client, 0, 1);
}
mProviderRefCountMap.put(jBinder, prc);
}
retHolder = prc.holder;
}
}
return retHolder;複製程式碼
AMS.publishContentProviders
在provider宿主程式進行例項化成功之後,就需要通知AMS,告訴它不需要再等待了。此後,訪問provider的應用程式的getContnetProviderImpl才真正的結束
public final void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
...
enforceNotIsolatedCaller("publishContentProviders");
synchronized (this) {
// 獲取provider宿主程式
final ProcessRecord r = getRecordForAppLocked(caller);
...
final long origId = Binder.clearCallingIdentity();
final int N = providers.size();
for (int i = 0; i < N; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
continue;
}
ContentProviderRecord dst = r.pubProviders.get(src.info.name);
if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);
if (dst != null) {
ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
// 根據類進行快取
mProviderMap.putProviderByClass(comp, dst);
String names[] = dst.info.authority.split(";");
// 根據uri authority name進行快取
for (int j = 0; j < names.length; j++) {
mProviderMap.putProviderByName(names[j], dst);
}
int launchingCount = mLaunchingProviders.size();
int j;
boolean wasInLaunchingProviders = false;
for (j = 0; j < launchingCount; j++) {
if (mLaunchingProviders.get(j) == dst) {
mLaunchingProviders.remove(j);
wasInLaunchingProviders = true;
j--;
launchingCount--;
}
}
// 這裡移除provider ANR的"定時炸彈"
if (wasInLaunchingProviders) {
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
}
synchronized (dst) {
dst.provider = src.provider;
dst.proc = r;
// 通知AMS provider已經"釋出"成功
dst.notifyAll();
}
updateOomAdjLocked(r);
maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
src.info.authority);
}
}
Binder.restoreCallingIdentity(origId);
}
}複製程式碼
ContentProvider隱藏陷阱之聯級誅殺
因為ContentProvider的設計,當provider的宿主程式死亡時,訪問它的程式如果正在做CRUD,那麼這個程式也會受到牽連。
程式被殺後
以下從ProcessRecord.kill為入口點進行分析
ProcessRecord.java
void kill(String reason, boolean noisy) {
if (!killedByAm) {
...
// 殺死程式
Process.killProcessQuiet(pid);
Process.killProcessGroup(uid, pid);
if (!persistent) {
killed = true;
killedByAm = true;
}
...
}
}複製程式碼
當程式死亡後,將會呼叫當初在attachApplication時註冊的死亡回撥
ActivityManagerService.java
private final class AppDeathRecipient implements IBinder.DeathRecipient {
final ProcessRecord mApp;
final int mPid;
final IApplicationThread mAppThread;
AppDeathRecipient(ProcessRecord app, int pid,
IApplicationThread thread) {
if (DEBUG_ALL) Slog.v(
TAG, "New death recipient " + this
+ " for thread " + thread.asBinder());
mApp = app;
mPid = pid;
mAppThread = thread;
}
@Override
public void binderDied() {
// 程式死亡後,將會呼叫到裡面
synchronized (ActivityManagerService.this) {
appDiedLocked(mApp, mPid, mAppThread, true);
}
}
}
final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
boolean fromBinderDied) {
...
handleAppDiedLocked(app, false, true);
...
}
private final void handleAppDiedLocked(ProcessRecord app,
boolean restarting, boolean allowRestart) {
int pid = app.pid;
boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
...
}
private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
boolean restarting, boolean allowRestart, int index) {
...
// 刪除和該程式有關的已"釋出"的provider
for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
ContentProviderRecord cpr = app.pubProviders.valueAt(i);
final boolean always = app.bad || !allowRestart;
boolean inLaunching = removeDyingProviderLocked(app, cpr, always);
if ((inLaunching || always) && cpr.hasConnectionOrHandle()) {
...
restart = true;
}
cpr.provider = null;
cpr.proc = null;
}
app.pubProviders.clear();複製程式碼
進行provider級聯誅殺
private final boolean removeDyingProviderLocked(ProcessRecord proc,
ContentProviderRecord cpr, boolean always) {
final boolean inLaunching = mLaunchingProviders.contains(cpr);
// 如果這個provider尚正在執行
if (!inLaunching || always) {
synchronized (cpr) {
cpr.launchingApp = null;
cpr.notifyAll();
}
mProviderMap.removeProviderByClass(cpr.name, UserHandle.getUserId(cpr.uid));
String names[] = cpr.info.authority.split(";");
for (int j = 0; j < names.length; j++) {
mProviderMap.removeProviderByName(names[j], UserHandle.getUserId(cpr.uid));
}
}
// 遍歷這個程式所有的Connection
for (int i = cpr.connections.size() - 1; i >= 0; i--) {
ContentProviderConnection conn = cpr.connections.get(i);
if (conn.waiting) {
// If this connection is waiting for the provider, then we don`t
// need to mess with its process unless we are always removing
// or for some reason the provider is not currently launching.
if (inLaunching && !always) {
continue;
}
}
// 獲取到這個provider的客戶端
ProcessRecord capp = conn.client;
conn.dead = true;
// 這個connection的stable計數大於0才會殺死其客戶端
if (conn.stableCount > 0) {
// 如果這個app不是常駐程式且正在執行中,那麼將會進行聯級誅殺
if (!capp.persistent && capp.thread != null
&& capp.pid != 0
&& capp.pid != MY_PID) {
capp.kill("depends on provider "
+ cpr.name.flattenToShortString()
+ " in dying proc " + (proc != null ? proc.processName : "??"), true);
}
} else if (capp.thread != null && conn.provider.provider != null) {
try {
capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
} catch (RemoteException e) {
}
// In the protocol here, we don`t expect the client to correctly
// clean up this connection, we`ll just remove it.
cpr.connections.remove(i);
if (conn.client.conProviders.remove(conn)) {
stopAssociationLocked(capp.uid, capp.processName, cpr.uid, cpr.name);
}
}
}
if (inLaunching && always) {
mLaunchingProviders.remove(cpr);
}
return inLaunching;
}複製程式碼
provider中,query操作首次是使用unstableProvider,失敗一次後會使用stableProvider;其餘insert, update, delete操作直接使用的是stableProvider
聯級存在的意義在於保護provider客戶端與服務端的資料一致性;因為插入,刪除這些操作會涉及到資料更新,所以如果provider出現了異常,為了保證客戶端維護的資料是正確了,只能強迫客戶端程式直接死亡再重新啟動恢復資料
releaseProvider
前面我們已經分析,acquireProvider會增加stable的引用計數,而provider服務端死亡時,如果stable計數大於0,那麼provider客戶端也會收到波及被殺死。那什麼時候會stable的計數會減少呢,答案在releaseProvider這個方法中
觸發時機
ContentResolver.java
public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
@Nullable String selection, @Nullable String[] selectionArgs,
@Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);
...
try {
...
try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
...
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
...
// 返回cursor,待客戶端呼叫close後才會呼叫到releaseProvider
return wrapper;
} catch (RemoteException e) {
return null;
} finally {
...
}
}
public final @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues values) {
Preconditions.checkNotNull(url, "url");
IContentProvider provider = acquireProvider(url);
if (provider == null) {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
long startTime = SystemClock.uptimeMillis();
Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
return createdRow;
} catch (RemoteException e) {
return null;
} finally {
// 直接在最後呼叫releaseProvider
releaseProvider(provider);
}
}
public final int update(@NonNull Uri uri, @Nullable ContentValues values,
@Nullable String where, @Nullable String[] selectionArgs) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider provider = acquireProvider(uri);
if (provider == null) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
long startTime = SystemClock.uptimeMillis();
int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
return rowsUpdated;
} catch (RemoteException e) {
return -1;
} finally {
releaseProvider(provider);
}
}
public final int delete(@NonNull Uri url, @Nullable String where,
@Nullable String[] selectionArgs) {
Preconditions.checkNotNull(url, "url");
IContentProvider provider = acquireProvider(url);
if (provider == null) {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
long startTime = SystemClock.uptimeMillis();
int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
return rowsDeleted;
} catch (RemoteException e) {
return -1;
} finally {
releaseProvider(provider);
}
}
public final @Nullable Bundle call(@NonNull Uri uri, @NonNull String method,
@Nullable String arg, @Nullable Bundle extras) {
Preconditions.checkNotNull(uri, "uri");
Preconditions.checkNotNull(method, "method");
IContentProvider provider = acquireProvider(uri);
if (provider == null) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
return provider.call(mPackageName, method, arg, extras);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
releaseProvider(provider);
}
}複製程式碼
以上,除了query外,其它操作都會在最後呼叫releaseProvider釋放provider的計數;query比較特殊,會在呼叫cursor的close後才呼叫;
這裡有個特殊的方法:call,它的不同之處在於資料傳輸的方式。其它的query, insert, update, delete這些操作是使用binder+ashmem結合的方式進行資料傳輸,而call純粹使用的是binder進行
App端資料結構
- ProviderKey: 包含URL authority字串
- ProviderClientRecord: 與AMS端的ContentProviderRecord對應,主要引用了provider proxy控制程式碼
- ProviderRefCount: 封裝了stable, unstable兩種引用
- ContentProviderHolder: 主要引用了ContentProviderConnection proxy控制程式碼,provider proxy控制程式碼
- ProviderInfo: provider的抽象儲存類
- ComponentName: 元件類的抽象類
system_server端資料結構
- ProviderMap: AMS端用來快取ContentProviderRecord的物件,提供四種不同的查詢方式
- ContentProviderRecord: provider在AMS中的封裝類,主要引用了provider binder proxy、程式資訊
- Association: 兩個程式相互關聯的抽象
- ProcessRecord: 程式的抽象,包含了程式優先順序、四大元件等等資訊
- ContentProviderConnection: 主要引用了provider客戶端的程式抽象
總結
本篇部落格詳細分析了provider代理物件是如果被獲取的,其中涉及了provider客戶端,system_server的AMS,provider服務端三個程式的通訊。同時也分析了contentprovider的聯級誅殺原理,這塊的知識點客戶端開發需要格外注意,否則自己的app無故死亡了都不知道。
關於provider客戶端如何同服務端使用ashmem與binder結合的方式來傳輸資料,因為本篇實在已經太長了,我沒有細講,以後再作分析。
關於ContentProvider的監聽,這塊感興趣的可以看我的部落格從原始碼層解析ContentService如何實現資料變化監聽