從原始碼角度看ContentProvider

CheapTalks發表於2019-03-01

簡介

本文原文在我的部落格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如何實現資料變化監聽

相關文章