日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

ContentProvider客戶端處理provider邏輯分析_Android

作者:大胃粥 ? 更新時間: 2022-11-28 編程語言

引言

前面一篇文章分析了 AMS 端處理 provider 的邏輯,請讀者務必仔細閱讀前面一篇文章,否則看本文,你可能有很多疑惑。

以查詢 provider 為例來分析客戶端是如何處理 provider,它調用的是 ContentResolver#query()

// ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
        @Nullable String[] projection, @Nullable Bundle queryArgs,
        @Nullable CancellationSignal cancellationSignal) {
    Objects.requireNonNull(uri, "uri");
    // ApplicationContentResolver 的 mWrapped 為 null
    try {
        if (mWrapped != null) {
            return mWrapped.query(uri, projection, queryArgs, cancellationSignal);
        }
    } catch (RemoteException e) {
        return null;
    }
    // 1. 獲取 unstable provider
    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 {
            // 2. 執行操作
            qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection,
                    queryArgs, remoteCancellationSignal);
        } catch (DeadObjectException e) {
            // 處理 unstable provider 進程掛掉的情況
            // 通知 AMS,provider 進程掛掉了
            unstableProviderDied(unstableProvider);
            // 獲取 stable provider,再次嘗試獲取數據
            stableProvider = acquireProvider(uri);
            if (stableProvider == null) {
                return null;
            }
            qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection,
                    queryArgs, remoteCancellationSignal);
        }
        if (qCursor == null) {
            return null;
        }
        // Force query execution.  Might fail and throw a runtime exception here.
        qCursor.getCount();
        long durationMillis = SystemClock.uptimeMillis() - startTime;
        maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);
        // 注意,這里最終還是從 stable provider 獲取 provider 接口
        final IContentProvider provider = (stableProvider != null) ? stableProvider
                : acquireProvider(uri);
        final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
        stableProvider = null;
        qCursor = null;
        // 3. 返回數據
        return wrapper;
    } catch (RemoteException e) {
        return null;
    } finally {
        // ...
    }
}

縱觀整個 provider 的查詢過程,其實就是三步

  • 獲取 provider。
  • 從獲取到的 provider 執行查詢操作。
  • 返回查詢的結果。

我們注意到,代碼中出現了兩種 provider,unstable provider 和 stable provider。這兩者的區別是,如果 provider 進程掛掉了,對于 stable provider,會殺死客戶端進程,而 unstable 不會。這個我們會在后面分析。

現在我們要抓住重點,來分析如何獲取 provider 。unstable provider 和 stable provider 的獲取方式其實是一樣的,本文只分析獲取 unstbale provider。

1. 獲取 provider

對于 app 進程來說,ContentResolver 接口的實現類為 ApplicationContentResolver,獲取 unstable provider 的操作最終會調用 ApplicationContentResolver#acquireUnstableProvider()

//ContextImpl.java
class ContextImpl {
    private static final class ApplicationContentResolver extends ContentResolver {
        private final ActivityThread mMainThread;
        @Override
        protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), false);
        }
    }
}

原來最終是交給 ActivityThread 來獲取 provider

// ActivityThread.java
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;
    }
    ContentProviderHolder holder = null;
    // 合成一個 KEY
    final ProviderKey key = getGetProviderKey(auth, userId);
    try {
        synchronized (key) {
            // 1. 獲取 ActivityManagerService 獲取
            holder = ActivityManager.getService().getContentProvider(
                    getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
            // 2. 等待 provider 發布完成
            // holder != null 表示 provider 存在 
            // holder.provider == null 表示 provider 正在發布中
            // holder.mLocal 為 false,表示 provider 不是安裝在客戶端
            if (holder != null && holder.provider == null && !holder.mLocal) {
                synchronized (key.mLock) {
                    // 2.1 超時等等 provider 發布
                    // 超時時間一般為 20s
                    key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                    // 這里可能因為超時被喚醒,獲取的數據為空
                    // 也可以是因為provider發布完成,被AMS喚醒,holder 為AMS返回的數據
                    holder = key.mHolder;
                }
                // 2.2 確認是否是超時喚醒
                if (holder != null && holder.provider == null) {
                    // probably timed out
                    holder = null;
                }
            }
        }
    } 
    // ...
    // 這里記錄了獲取provider失敗的日志
    if (holder == null) {
        if (UserManager.get(c).isUserUnlocked(userId)) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
        } else {
            Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)");
        }
        return null;
    }
    // 3. 成功從服務端獲取 provider,本地安裝它
    holder = installProvider(c, holder, holder.info,
            true /*noisy*/, holder.noReleaseNeeded, stable);
    return holder.provider;
}

客戶端獲取 provider 的過程大致分為如下幾步

  • 從 AMS 獲取 provider。
  • 如果 provider 還是發布的過程中,那么就超時等待它發布完成。 但是等待是有時間限制的,大約為 20s。超時等待的過程中被喚醒,有兩種可能,一種是因為超時了,另外一種是因為 provider 成功發布,AMS 喚醒了客戶端。因此需要判斷到底是哪一種情況,檢測的條件是被喚醒后,是否獲取到 provider binder,也就是 holder.provider。詳見【1.1 等待 provider 發布
  • 從 AMS 成功獲取到 provider 后,那就在本地“安裝”。這個方法的命令起的并不是很好,如果成功從 AMS 獲取到 provider,其實這里的邏輯是保存數據。而如果 AMS 通知客戶端,provider 可以安裝在客戶端進程中,客戶端會在這個方法中創建 ContentProvider 對象并保存,這才叫安裝。詳見【1.2 安裝 provider

1.1 等待 provider 發布

從前面的文章可知,當 provider 發布超時 或者 成功發布時,都會調用 ContentProviderRecord#onProviderPublishStatusLocked(boolean status) 來通知客戶端 provider 的發布狀態。參數 status 如果為 true,表示發布成功,如果為 false,表示發布超時。

// ContentProviderRecord.java
void onProviderPublishStatusLocked(boolean status) {
    final int numOfConns = connections.size();
    for (int i = 0; i < numOfConns; i++) {
        // 遍歷所有等待 provider 發布的客戶端連接
        final ContentProviderConnection conn = connections.get(i);
        if (conn.waiting && conn.client != null) {
            final ProcessRecord client = conn.client;
            // 記錄發布超時的日志
            if (!status) {
                // 從這里可以看出status為false時,不一定表示發布超時,還可能因為進程掛掉了
                if (launchingApp == null) {
                    Slog.w(TAG_AM, "Unable to launch app "
                            + appInfo.packageName + "/"
                            + appInfo.uid + " for provider "
                            + info.authority + ": launching app became null");
                    EventLogTags.writeAmProviderLostProcess(
                            UserHandle.getUserId(appInfo.uid),
                            appInfo.packageName,
                            appInfo.uid, info.authority);
                } else {
                    Slog.wtf(TAG_AM, "Timeout waiting for provider "
                            + appInfo.packageName + "/"
                            + appInfo.uid + " for provider "
                            + info.authority
                            + " caller=" + client);
                }
            }
            // 通知客戶端
            final IApplicationThread thread = client.getThread();
            if (thread != null) {
                try {
                    thread.notifyContentProviderPublishStatus(
                            newHolder(status ? conn : null, false),
                            info.authority, conn.mExpectedUserId, status);
                } catch (RemoteException e) {
                }
            }
        }
        conn.waiting = false;
    }
}

很簡單,通過遍歷所有等待 provider 發布的客戶端連接,然后通過客戶端 attach 的 thread 來通知它們。

// ActivityThread.java
public void notifyContentProviderPublishStatus(@NonNull ContentProviderHolder holder,
        @NonNull String authorities, int userId, boolean published) {
    final String auths[] = authorities.split(";");
    for (String auth: auths) {
        final ProviderKey key = getGetProviderKey(auth, userId);
        synchronized (key.mLock) {
            // 保存服務端傳過來的數據
            key.mHolder = holder;
            // 喚醒等待provider的線程
            key.mLock.notifyAll();
        }
    }
}

客戶端收到信息后,喚醒了等待的線程,誰在等待呢?這里是不是有點熟悉,其實就是前面分析獲取 provider 時,超時等待,部分代碼如下

// ActivityThread.java
public final IContentProvider acquireProvider(
        Context c, String auth, int userId, boolean stable) {
    // ...
    try {
        synchronized (key) {
            holder = ActivityManager.getService().getContentProvider(
                    getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
            // 等待 provider 發布完成
            if (holder != null && holder.provider == null && !holder.mLocal) {
                synchronized (key.mLock) {
                    // 超時等待
                    key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                    // 這里可能因為超時被喚醒,獲取的數據為空
                    // 也可以是因為provider發布完成,被AMS喚醒,holder 為AMS返回的數據
                    holder = key.mHolder;
                }
                // 確認是否是超時喚醒
                if (holder != null && holder.provider == null) {
                    // probably timed out
                    holder = null;
                }
            }
        }
    } 
    // ...
}

超時等待 provider 發布時,如果一旦被喚醒,再次獲取 key.mHolder,因為如果成功發布,holder.provider 是不為空的,因為它就是 provider binder,否則就是超時喚醒。

1.2 安裝 provider

客戶端如果成功從 AMS 獲取到 provider,那么就會安裝它,其實這里的操作是保存數據,其實最主要的就是保存 provider 接口,同時也是保存 provider binder.

private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    // 成功從 AMS 獲取 provider,下面兩個條件都是不成立 
    if (holder == null || holder.provider == null) {
        // ...
    } else {
        // 獲取 provider 接口,其實就是獲取 provider binder
        provider = holder.provider;
    }
    ContentProviderHolder retHolder;
    synchronized (mProviderMap) {
        // 從 provider 接口中獲取 binder 對象
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) {
            // ...
        } else {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                // ...
            } else {
                // 1. 創建 provider 記錄,并保存
                ProviderClientRecord client = installProviderAuthoritiesLocked(
                        provider, localProvider, holder);
                // persistent app 的 provider 是不需要釋放的
                if (noReleaseNeeded) {
                    prc = new ProviderRefCount(holder, client, 1000, 1000);
                } else {
                    prc = stable
                            ? new ProviderRefCount(holder, client, 1, 0)
                            : new ProviderRefCount(holder, client, 0, 1);
                }
                // 2. 保存 provider 計數
                mProviderRefCountMap.put(jBinder, prc);
            }
            retHolder = prc.holder;
        }
    }
    return retHolder;
}
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
        ContentProvider localProvider, ContentProviderHolder holder) {
    final String auths[] = holder.info.authority.split(";");
    final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
    // ...
    //  創建一條 provider 記錄
    final ProviderClientRecord pcr = new ProviderClientRecord(
            auths, provider, localProvider, holder);
    // 一個 ContentProvider 可以聲明多個 authority
    for (String auth : auths) {
        final ProviderKey key = new ProviderKey(auth, userId);
        //  mProviderMap 保存
        final ProviderClientRecord existing = mProviderMap.get(key);
        if (existing != null) {
            Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
                    + " already published as " + auth);
        } else {
            mProviderMap.put(key, pcr);
        }
    }
    return pcr;
}

很簡單,就是用兩個數據結構保存數據。

2. provider 實現多進程實例

前面我們總是隱隱約約地提到,provider 可以安裝在客戶端進程,那么什么樣的條件下,provider 可以安裝在客戶端進程中? 前面一篇文章的分析中有提到過,現在展示出部分代碼

// ContentProviderHelper.java
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
        String name, IBinder token, int callingUid, String callingPackage, String callingTag,
        boolean stable, int userId) {
    // ...
    synchronized (mService) {
        // 獲取客戶端的進程實例
        ProcessRecord r = null;
        if (caller != null) {
            r = mService.getRecordForAppLOSP(caller);
            if (r == null) {
                throw new SecurityException("Unable to find app for caller " + caller
                        + " (pid=" + Binder.getCallingPid() + ") when getting content provider "
                        + name);
            }
        }
        // ...
        // provider 正在運行
        if (providerRunning) {
            cpi = cpr.info;
            if (r != null &amp;&amp; cpr.canRunHere(r)) {
                // This provider has been published or is in the process
                // of being published...  but it is also allowed to run
                // in the caller's process, so don't make a connection
                // and just let the caller instantiate its own instance.
                ContentProviderHolder holder = cpr.newHolder(null, true);
                // don't give caller the provider object, it needs to make its own.
                holder.provider = null;
                return holder;
            }
            // ...
        }
        // provider 沒有運行
        if (!providerRunning) {
            // ...
            if (r != null &amp;&amp; cpr.canRunHere(r)) {
                // If this is a multiprocess provider, then just return its
                // info and allow the caller to instantiate it.  Only do
                // this if the provider is the same user as the caller's
                // process, or can run as root (so can be in any process).
                return cpr.newHolder(null, true);
            }
            // ...
        }
        // ...
    }
    // ...
}

可以看到,無論 provider 是否已經運行,都有機會在客戶端進程中創建 provider 實例,而這個機會就在 ContentProviderRecord#canRunHere()

provider 已經運行,居然還可以運行在客戶端進程中,也就是在客戶端進程中創建 ContentProvider 實例,這樣的設計又是為了什么呢?

public boolean canRunHere(ProcessRecord app) {
    // info 為 provider 信息,也就是在 AndroidManifest 中聲明的 provider 信息
    // provider 可以 運行在客戶端進程中的條件
    // 1. provider 所在的 app 的 uid 與客戶端 app 的 uid 相同
    // 2. provider 支持多進程 或者 provider 的進程名與客戶端 app 的進程名相同
    return (info.multiprocess || info.processName.equals(app.processName))
            && uid == app.info.uid;
}

這里的條件可要看清楚了,首先 provider 所在 app 和 客戶端 app 的 uid 相同,其實就是下面這個玩意要一樣

<manifest 
    android:sharedUserId="">

然后,還需要 provider 支持多進程,其實就是下面這個玩意

<provider 
    android:multiprocess="true"/>

如果 provider 不支持多進程,只要 provider 的進程名與客戶端 app 的進程名一樣,provider 也是可以運行在客戶端進程中。那么 provider 進程名是什么呢? provider 可以聲明自己的進程名,如下

<provider
    android:process=""
   />

而如果 provider 沒有聲明自己的進程名,那么 provider 進程名取自 app 的進程名。

現在 provider 怎樣運行在客戶端進程中,大家會玩了嗎?如果會玩了,那么繼續看下客戶端如何安裝 provider,這一次可就是真的安裝 provider 了

private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    // 此時 holder.provider == null 是成立的
    if (holder == null || holder.provider == null) {
        // ...
        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
            LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
            if (packageInfo == null) {
                // System startup case.
                packageInfo = getSystemContext().mPackageInfo;
            }
            // 1. 通過反射創建 ContentProvider 對象
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);
            // 獲取 provider 接口,其實就是獲取 provider binder
            provider = localProvider.getIContentProvider();
            if (provider == null) {
                return null;
            }
            // 2. 為 ContentProvider 對象保存 provider 信息,并且調用 ContentProvider#onCreate()
            localProvider.attachInfo(c, info);
        } catch (java.lang.Exception e) {
            // ...
        }
    } else {
        // ...
    }
    ContentProviderHolder retHolder;
    synchronized (mProviderMap) {
        if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
                + " / " + info.name);
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) {
            ComponentName cname = new ComponentName(info.packageName, info.name);
            ProviderClientRecord pr = mLocalProvidersByName.get(cname);
            if (pr != null) {
                // ...
            } else {
                // 本地創建 ContentProviderHolder
                holder = new ContentProviderHolder(info);
                // 保存 provider binder
                holder.provider = provider;
                // 本地安裝的 provider,不需要釋放
                holder.noReleaseNeeded = true;
                // 3. 創建 provider 記錄,并保存
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                mLocalProviders.put(jBinder, pr);
                mLocalProvidersByName.put(cname, pr);
            }
            retHolder = pr.mHolder;
        } else {
            // ...
        }
    }
    return retHolder;
}

其實這一部分代碼在前面文章中已經分析過,這里簡單介紹下過程

  • 客戶端自己創建 ContentProvider 對象,然后保存 provider 信息,并調用 ContentProvider#onCreate() 方法。
  • 創建 provider 記錄,也就是 ContentProviderRecord 對象,然后用數據結構保存。

3. 兩種 provider 區別

前面我們提到過 unstable provider 和 stable provider 的區別,現在我們用代碼來解釋下這兩者的區別

假設我們通過 ActivityManager#forceStopPackage() 來殺掉 provider 進程,在 AMS 的調用如下

public void forceStopPackage(final String packageName, int userId) {
    // ...
    try {
        IPackageManager pm = AppGlobals.getPackageManager();
        synchronized(this) {
            int[] users = userId == UserHandle.USER_ALL
                    ? mUserController.getUsers() : new int[] { userId };
            for (int user : users) {
                // ...
                if (mUserController.isUserRunning(user, 0)) {
                    // 殺掉進程
                    forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                    // 發送廣播
                    finishForceStopPackageLocked(packageName, pkgUid);
                }
            }
        }
    } finally {
        Binder.restoreCallingIdentity(callingId);
    }
}

最終調用如下代碼

final boolean forceStopPackageLocked(String packageName, int appId,
        boolean callerWillRestart, boolean purgeCache, boolean doit,
        boolean evenPersistent, boolean uninstalling, int userId, String reason) {
    // ...
    // 獲取 app 的所有 provider
    ArrayList<ContentProviderRecord> providers = new ArrayList<>();
    if (mCpHelper.getProviderMap().collectPackageProvidersLocked(packageName, null, doit,
            evenPersistent, userId, providers)) {
        if (!doit) {
            return true;
        }
        didSomething = true;
    }
    // 移除 provider
    for (i = providers.size() - 1; i >= 0; i--) {
        mCpHelper.removeDyingProviderLocked(null, providers.get(i), true);
    }
    // ...
}

不出意外,最終由 ContentProviderHelper 來移除 provider

boolean removeDyingProviderLocked(ProcessRecord proc, ContentProviderRecord cpr,
        boolean always) {
    // ...
    for (int i = cpr.connections.size() - 1; i >= 0; i--) {
        ContentProviderConnection conn = cpr.connections.get(i);
        // ...
        ProcessRecord capp = conn.client;
        final IApplicationThread thread = capp.getThread();
        conn.dead = true;
        // 1. 如有 stable provider 的客戶端
        if (conn.stableCount() > 0) {
            final int pid = capp.getPid();
            // 注意,要排除 persistent app 進程,以及 system_server 進程
            if (!capp.isPersistent() && thread != null
                    && pid != 0 && pid != ActivityManagerService.MY_PID) {
                // 殺掉客戶端進程
                capp.killLocked(
                        "depends on provider " + cpr.name.flattenToShortString()
                        + " in dying proc " + (proc != null ? proc.processName : "??")
                        + " (adj " + (proc != null ? proc.mState.getSetAdj() : "??") + ")",
                        ApplicationExitInfo.REASON_DEPENDENCY_DIED,
                        ApplicationExitInfo.SUBREASON_UNKNOWN,
                        true);
            }
        } 
        // 2. 如果只有 unstable provider 客戶端
        else if (thread != null && conn.provider.provider != null) {
            try {
                // 通知客戶端移除數據
                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.mProviders.removeProviderConnection(conn)) {
                mService.stopAssociationLocked(capp.uid, capp.processName,
                        cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
            }
        }
    }
    // ...
}

看到了吧,對于 stable provider,如果 provider 進程掛掉了,那么客戶端也會受牽連被殺掉。

而對于 unstable provider,如果 provier 進程掛掉了,客戶端只是移除了保存了的數據而已,并不會被殺掉。

最后,我們再來看看文章開頭獲取 provider 時關于兩種 provider 代碼

// ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
        @Nullable String[] projection, @Nullable Bundle queryArgs,
        @Nullable CancellationSignal cancellationSignal) {
    // ...
    // 獲取 unstable provider
    IContentProvider unstableProvider = acquireUnstableProvider(uri);
    if (unstableProvider == null) {
        return null;
    }
    IContentProvider stableProvider = null;
    Cursor qCursor = null;
    try {
        // ...
        // 注意,這里獲取的 stable provider 并返回
        final IContentProvider provider = (stableProvider != null) ? stableProvider
                : acquireProvider(uri);
        final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
        stableProvider = null;
        qCursor = null;
        // 返回數據
        return wrapper;
    } catch (RemoteException e) {
        return null;
    } finally {
        // ...
    }
}

我們可以看到,查詢的時候使用的是 unstable provier,但是返回的結果 Curosr 使用的是 stable provider。這說明了什么? 它說明了,在 Cursor 沒有被 close 之前,只要 provider 進程掛掉了,那么客戶端也會受牽連,會被殺掉。

結束

原文鏈接:https://juejin.cn/post/7151359999107661855

欄目分類
最近更新