關聯(lián)啟動禁用導致無法通過CP共享數(shù)據(jù)分析

有兩個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相關代碼,最終解開了謎底。


image.png

這里只分析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ù)了。。。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容