有兩個App需要共享數(shù)據(jù),采用的是ContentProvider方案,在使用的過程中,發(fā)現(xiàn)在B應用關閉的時候,如果A想取B內(nèi)的數(shù)據(jù),大概率會失敗。一直很奇怪,ContentProvider設計是就是為數(shù)據(jù)共享而生,相冊,短信等系統(tǒng)服務,也是通過ContentProvider來完成數(shù)據(jù)共享的,為何自己實現(xiàn)的ContentProvider會出現(xiàn)訪問不到數(shù)據(jù)的情況呢?
整個方案也很簡單,B應用通過數(shù)據(jù)庫存儲數(shù)據(jù)并與ContentProvider建立關聯(lián),然后A應用通過指定的URI去訪問B的數(shù)據(jù),帶著疑問去查了下ContentProvider相關代碼,最終解開了謎底。

這里只分析A訪問B數(shù)據(jù)的邏輯
如下:
ContentResolver contentResolver =context.getContentResolver();
Uri uri = Uri.parse(Constant.SERVER_URI + path);
Cursor cursor = contentResolver.query(uri, null, null, null, null);
首先拿到當前應用的ContentResolver,然后調(diào)用query方法去查詢數(shù)據(jù),代碼其實很簡單,既然拿不到數(shù)據(jù),問題肯定出現(xiàn)在query方法上了,順著思路,我們點開query方法,看下內(nèi)部實現(xiàn)如何。
class ContextImpl extends Context {
public ContentResolver getContentResolver() {
return mContentResolver;
}
private ContextImpl(...) {
...
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
}
Context中調(diào)用getContentResolver,經(jīng)過層層調(diào)用來到ContextImpl類,返回值mContentResolver賦值是在ContextImpl對象創(chuàng)建過程完成賦值.
getContentResolver()方法返回的類型是android.app.ContextImpl.ApplicationContentResolver,這個類是抽象類android.content.ContentResolver的子類,resolver.query實際上是調(diào)用父類ContentResolver的query實現(xiàn):
public final @Nullable Cursor query(final @RequiresPermission.Read @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);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();
ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
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 provider死亡過程
unstableProviderDied(unstableProvider);
//unstable類型死亡后,再創(chuàng)建stable類型的provider
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
//再次執(zhí)行查詢操作
qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
......略去非關鍵代碼
}
- 調(diào)用acquireUnstableProvider(),嘗試獲取unstable的ContentProvider;
然后執(zhí)行query操作; - 當執(zhí)行query過程拋出DeadObjectException,即代表ContentProvider所在進程死亡,則嘗試獲取stable的ContentProvider:
- 調(diào)用unstableProviderDied(), 清理剛創(chuàng)建的unstable的ContentProvider;
調(diào)用acquireProvider(),嘗試獲取stable的ContentProvider;
由于這兩個acquire*都是抽象方法,我們可以直接看子類ApplicationContentResolver的實現(xiàn):
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
return mMainThread.acquireProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
@Override
protected IContentProvider acquireExistingProvider(Context context, String auth) {
return mMainThread.acquireExistingProvider(context,
ContentProvider.getAuthorityWithoutUserId(auth),
resolveUserIdFromAuthority(auth), true);
}
可以看到這兩個抽象方法最終都通過調(diào)用ActivityThread類的acquireProvider獲取到IContentProvider,接下來我們看看到底是如何獲取到ContentProvider的。
ContentProvider獲取過程
ActivityThread類的acquireProvider方法如下,方法的最后一個參數(shù)stable代表著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;
}
// There is a possible race here. Another thread may try to acquire
// the same provider at the same time. When this happens, we want to ensure
// that the first one wins.
// Note that we cannot hold the lock while acquiring and installing the
// provider since it might take a long time to run and it could also potentially
// be re-entrant in the case where the provider is in the same process.
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
}
// 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;
}
這個方法首先通過acquireExistingProvider嘗試從本進程中獲取ContentProvider,如果獲取不到,那么再請求AMS獲取對應ContentProvider,這里應該就是跨進程訪問數(shù)據(jù)了,也就符合我們當前遇到的問題,A如果需要訪問B,需要通過AMS獲取對應ContentProvider,如果手機廠商做了深度定制,禁止應用關聯(lián),此時是無法通過AMS拿到holder對象,從而無法完成我們后續(xù)的增刪改查。
ok,到此,我們基本了解為何無法訪問到B共享的數(shù)據(jù)了。這種屬于ROM做的定制,想要跨越這道坎,有點以卵擊石的感覺,既然這條路走不通,嘗試下別的方法吧。
考慮到Activity這個特殊的組件,應該不會被標記為關聯(lián)啟動吧,雖然不知道手機廠商做了什么,我可以試一試啊。方案大概這樣,在B應用聲明一個透明的activity,然后exported設置為true,當A無法讀取B共享的ContentProvider數(shù)據(jù)并且B進程死亡的前提下,A通過Activity跳轉調(diào)起B(yǎng),B進程啟動之后,對應的ContentProvider也起來了,這樣的話,應該就可以正常訪問B的數(shù)據(jù)了。話不多說,直接試吧
Intent intent = new Intent();
ComponentName comp = new ComponentName("com.swt.setting", "com.swt.setting.VirtualActivity");
intent.setComponent(comp);
intent.setAction("android.intent.action.MAIN");
intent.putExtra("flag", "flag");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
完美,B成功被啟動,并且A可以正常訪問B的數(shù)據(jù)了。。。