--- title: '四大組件 - ContentProvider' disqus: kyleAlien --- 四大組件 - ContentProvider === ## OverView of Content [TOC] ## ContentProvider 基本使用 ContentProvider 基本使用如下,可以透過 ContextProvider 來操作其他應用進程內的內部資料 (像是 DB 之類的) ```java= public class TestContentProvider extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Uri uri = Uri.parse("content://com.alien.providers.GameInfo"); ContentValues values = new ContentValues(); values.put("id", 9521); values.put("name", "HelloWorld"); values.put("phone", 123456789); // @ 查看 getContentResolver 方法 this.getContentResolver().insert(uri, values); } } ``` ## App 訪問指定 Provider 透過上述的 Context#getContentResolver 訪問指定 Provider ### Context 訪問 getContentResolver * 從 Activity#getContentResolver 開始看,他會先訪問 ContextWrapper#getContentResolver,而 ContextWrapper 只是裝飾類,實作類是 [**ContextImpl**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ContextImpl.java) ```java= // ContextWrapper.java @Override public ContentResolver getContentResolver() { return mBase.getContentResolver(); } // ------------------------------------------------------- // ContextImpl.java @Override public ContentResolver getContentResolver() { return mContentResolver; } ``` * mContentResolver 成員是在 ContextImpl 建構函數內創建,ApplicationContentResolver 是 ContextImpl 內的靜態內部類 ```java= // ContextImpl.java private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, @NonNull LoadedApk packageInfo, @NonNull ContextParams params, @Nullable String attributionTag, @Nullable AttributionSource nextAttributionSource, @Nullable String splitName, @Nullable IBinder token, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) { mOuterContext = this; ... 省略部分 // ApplicationContentResolver 是 ContextImpl 內的靜態內部類 mContentResolver = new ApplicationContentResolver(this, mainThread); } // 繼承於 ContentResolver 類 private static final class ApplicationContentResolver extends ContentResolver { private final ActivityThread mMainThread; public ApplicationContentResolver(Context context, ActivityThread mainThread) { super(context); mMainThread = Objects.requireNonNull(mainThread); } ... 省略部分方法 } ``` > ![](https://i.imgur.com/9LhbwmM.png) ### ContentResolver 類 - query 方法 * ApplicationContentResolver 繼承於 [**ContentResolver 類**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/ContentResolver.java) ```java= // ContentResolver.java public abstract class ContentResolver implements ContentInterface { @Override public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { // 檢查 Uri 不為 null Objects.requireNonNull(uri, "uri"); ... 省略部分 // @ 查看 acquireUnstableProvider 方法 IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { return null; } ... IContentProvider stableProvider = null; Cursor qCursor = null; try { ... 省略部分 try { // 調用 IContentProvider#query 方法 qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection, queryArgs, remoteCancellationSignal); } /* catch */ ... 省略部分 } catch (RemoteException e) { // Arbitrary and not worth documenting, as Activity // Manager will kill this process shortly anyway. return null; } finally { if (qCursor != null) { qCursor.close(); } if (cancellationSignal != null) { cancellationSignal.setRemote(null); } if (unstableProvider != null) { releaseUnstableProvider(unstableProvider); } if (stableProvider != null) { releaseProvider(stableProvider); } } } } ``` * ContentResolver#**acquireUnstableProvider** 方法 1. 檢查 Uri Scheme 是否是 **content**、Authority 是否合法 :::info * 這也就是我們在指定 Provider Uri 時為何要以 content 開頭 ```java= Uri uri = Uri.parse("content://com.alien.providers.GameInfo"); ``` ::: 2. 取得 authority 後傳入 acquireUnstableProvider 抽象方法,該方法由子類 ApplicationContentResolver 實現 ```java= // ContentResolver.java public static final String SCHEME_CONTENT = "content"; public final IContentProvider acquireUnstableProvider(Uri uri) { if (!SCHEME_CONTENT.equals(uri.getScheme())) { return null; } String auth = uri.getAuthority(); if (auth != null) { // 抽象方法,由子類 ApplicationContentResolver 實現 return acquireUnstableProvider(mContext, uri.getAuthority()); } return null; } protected abstract IContentProvider acquireUnstableProvider(Context c, String name); // ---------------------------------------------------------- // ContextImpl.java private static final class ApplicationContentResolver extends ContentResolver { private final ActivityThread mMainThread; public ApplicationContentResolver(Context context, ActivityThread mainThread) { super(context); mMainThread = Objects.requireNonNull(mainThread); } @Override protected IContentProvider acquireUnstableProvider(Context c, String auth) { // @ 呼叫 ActivityThread#acquireProvider 方法 return mMainThread.acquireProvider(c, ContentProvider.getAuthorityWithoutUserId(auth), resolveUserIdFromAuthority(auth), false); } } ``` * 藉由 [**ActivityThread**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java) 取得 IContentProvider 對象 1. 查看是否有 IContentProvider 緩存 2. 透過 ActivityManager 取得訪問 AMS 的代理類,並呼叫 AMS#getContentProvider 方法 :::danger * 非 Local Provider ? 若目標 Provider 並非本地 Provider,就需要等待遠端 Provider 建立 ::: ```java= // ActivityThread.java public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { // 1. 查看是否有 IContentProvider 緩存 final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); if (provider != null) { return provider; } ContentProviderHolder holder = null; final ProviderKey key = getGetProviderKey(auth, userId); try { synchronized (key) { // 訪問 AMS#getContentProvider holder = ActivityManager.getService().getContentProvider( getApplicationThread(), c.getOpPackageName(), auth, userId, stable); // 如果正常取得 holder,但沒有 provider,那代表 provider 並非本地 // 我們必須等待 provider 建立 if (holder != null && holder.provider == null && !holder.mLocal) { synchronized (key.mLock) { key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS); holder = key.mHolder; } if (holder != null && holder.provider == null) { // probably timed out holder = null; } } } } /* 省略 catch、finally */ if (holder == null) { ... return null; } holder = installProvider(c, holder, holder.info, true /*noisy*/, holder.noReleaseNeeded, stable); return holder.provider; } ``` > ![](https://i.imgur.com/0Dl1oJ2.png) ### [AMS](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/core/java/com/android/server/am/ActivityManagerService.java) - 取得 ContentProvider 對象 * AMS#getContentProvider 會呼叫到 [**ContentProviderHelper**](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/core/java/com/android/server/am/ContentProviderHelper.java)#getContentProvider 方法,最後我們分析的重點會放在 ContentProviderHelper#**getContentProviderImpl** ```java= // ActivityManagerService.java public ActivityManagerService(Injector injector, ServiceThread handlerThread) { ... mCpHelper = new ContentProviderHelper(this, false); } @Override public final ContentProviderHolder getContentProvider( IApplicationThread caller, String callingPackage, String name, int userId, boolean stable) { traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getContentProvider: ", name); try { // @ 呼叫 ContentProviderHelper#getContentProvider return mCpHelper.getContentProvider(caller, callingPackage, name, userId, stable); } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } // ---------------------------------------------------------------------- // ContentProviderHolder.java ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage, String name, int userId, boolean stable) { mService.enforceNotIsolatedCaller("getContentProvider"); if (caller == null) { String msg = "null IApplicationThread when getting content provider " + name; Slog.w(TAG, msg); throw new SecurityException(msg); } // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal // with cross-user grant. final int callingUid = Binder.getCallingUid(); if (callingPackage != null && mService.mAppOpsService.checkPackage( callingUid, callingPackage) != AppOpsManager.MODE_ALLOWED) { throw new SecurityException("Given calling package " + callingPackage + " does not match caller's uid " + callingUid); } // 分析 getContentProviderImpl 方法 return getContentProviderImpl(caller, name, null, callingUid, callingPackage, null, stable, userId); } ``` * [**ContentProviderHelper**](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/core/java/com/android/server/am/ContentProviderHelper.java)#getContentProviderImpl 方法 1. 檢查 Caller 進程是否正常運行 2. 透過傳入的 name 檢查是否已經有 AMS 是否已有 ContentProviderRecord > name 就是 Uri 的 authority,也就是 provider 路徑 3. **透過 PKMS#resolveContentProvider** 找到相對的 **ProviderInfo** :::success PKMS 會在安裝時分析 AndroidManifest,所以會有 APK 的 provider 訊息 ::: 4. 透過 ProviderInfo 創建 ComponentName 5. 查找 ProviderInfo 的進程,並根據不同狀況作相對行為: 5-1. Provider 所屬進程已經啟動,呼叫 IApplicationThread#**scheduleInstallProvider** 方法 > ![](https://i.imgur.com/Fp0Eb68.png) 5-2. Provider 所屬進程未啟動,呼叫 AMS#**startProcessLocked** 方法,先啟動 Provider 進程 > ![](https://i.imgur.com/edNLm3W.png) ```java= // ContentProviderHolder.java private ContentProviderHolder getContentProviderImpl(IApplicationThread caller, String name, IBinder token, int callingUid, String callingPackage, String callingTag, boolean stable, int userId) { // 目標 ContextProvider ContentProviderRecord cpr; ProviderInfo cpi = null; boolean providerRunning = false; ... synchronized (mService) { long startTime = SystemClock.uptimeMillis(); ProcessRecord r = null; // 1. 檢查呼叫者進程 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); } } // 2. 檢查目標 Provider 是否已有 (name 就是 Uri 的 authority) cpr = mProviderMap.getProviderByName(name, userId); // 如果沒有緩存,嘗試透過 USER_SYSTEM 取得 if (cpr == null && userId != UserHandle.USER_SYSTEM) { cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM); if (cpr != null) { cpi = cpr.info; // 並驗證 provider 是否是 singleton if (mService.isSingleton( cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags) && mService.isValidSingletonCall( r == null ? callingUid : r.uid, cpi.applicationInfo.uid)) { userId = UserHandle.USER_SYSTEM; checkCrossUser = false; } else { cpr = null; cpi = null; } } } // 第一次啟動, Provider 並不存在 if (!providerRunning) { try { ... // 3. 透過 PKMS 取得對應的 ProviderInfo cpi = AppGlobals.getPackageManager().resolveContentProvider(name, ActivityManagerService.STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId); ... } /* 省略 catch */ if (cpi == null) { return null; } ... 省略部分 // 4. 透過 ProviderInfo 創建 ComponentName ComponentName comp = new ComponentName(cpi.packageName, cpi.name); cpr = mProviderMap.getProviderByClass(comp, userId); ... 省略部分 // 這是單個進程,並且 APP 正在向該 Provider 進行連線,準備啟動該 Provider final int numLaunchingProviders = mLaunchingProviders.size(); int i; for (i = 0; i < numLaunchingProviders; i++) { // 抓取正在啟動的 Provider if (mLaunchingProviders.get(i) == cpr) { break; } } // 如果 Provider 尚未準備啟動,那就讓它啟動 if (i >= numLaunchingProviders) { final long origId = Binder.clearCallingIdentity(); try { ... // 5. 查找 ProviderInfo 的進程 ProcessRecord proc = mService.getProcessRecordLocked( cpi.processName, cpr.appInfo.uid); IApplicationThread thread; // Provider 進程已經啟動 if (proc != null && (thread = proc.getThread()) != null && !proc.isKilled()) { ... debug 訊息 final ProcessProviderRecord pr = proc.mProviders; if (!pr.hasProvider(cpi.name)) { pr.installProvider(cpi.name, cpr); try { // 呼叫到 Provider 進程,要求該進程安裝 Provider thread.scheduleInstallProvider(cpi); } catch (RemoteException e) { } } } else { // 先啟動 Provider 所在的進程 proc = mService.startProcessLocked( cpi.processName, cpr.appInfo, false, 0, new HostingRecord("content provider", new ComponentName( cpi.applicationInfo.packageName, cpi.name)), Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false); // Provider 進程啟動失敗 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); } } } } return cpr.newHolder(conn, false); } ``` ## App 啟動 Provider 以下分兩種情況,^1.^ Provider 進程尚未啟動、^2.^ Provider 已經啟動,接續啟動 Provider ### [AMS](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/services/core/java/com/android/server/am/ActivityManagerService.java) 啟動 Provider 所屬進程 * Provider 所屬進程尚未啟動,那會先呼叫 AMS#**startProcessLocked** 方法啟動指定進程 ```java= // ActivityManagerService.java final ProcessList mProcessList; final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated) { // @ 追蹤 startProcessLocked 方法 return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags, hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */, null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */, null /* crashHandler */); } ``` * ProcessList#startProcessLocked 是啟動新 Process 的入口,經過以下以系列的調用,最終會到達 Process#start 方法,接續 Process#start 請參考 [**Process.start - 啟動 ActivityThread**](https://hackmd.io/w-pdtemSTWeqAAc8PWzQzw?view#Processstart---%E5%95%9F%E5%8B%95-ActivityThread) * 先跳過中間調用,這裡我們簡單知道順序是 1. [**ActivityThread**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java)#main 呼叫 attach 方法 2. 透過 AcitivtyManager 取得代理類,訪問 AMS#attachApplication 方法 ```java= // ActivityThread.java public static void main(String[] args) { Looper.prepareMainLooper(); ... ActivityThread thread = new ActivityThread(); // 1. 呼叫 attach 方法 thread.attach(false, startSeq); ... Looper.loop(); } private void attach(boolean system, long startSeq) { ... if (!system) { ... final IActivityManager mgr = ActivityManager.getService(); try { // @ 透過 AcitivtyManager 取得代理類,訪問 AMS#attachApplication 方法 mgr.attachApplication(mAppThread, startSeq); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } } ``` * AMS 中間的調用我們就不特別分析了 (上面有分析過很多案例),這邊我們直接跳轉到 APP 進程,我們的 **目的是要看 Provider 適合時被啟動的** ```java= // ActivityThread.java class H extends Handler { public static final int BIND_APPLICATION = 110; public void handleMessage(Message msg) { case BIND_APPLICATION: AppBindData data = (AppBindData)msg.obj; // @ 分析 handleBindApplication handleBindApplication(data); break; } } private void handleBindApplication(AppBindData data) { ... 省略部分 Application app; try { app = data.info.makeApplication(data.restrictedBackupMode, null); ... 省略部分 if (!data.restrictedBackupMode) { // 若不是 Backup 模式,則啟動該進程中所有的 Provider if (!ArrayUtils.isEmpty(data.providers)) { // installContentProviders 我們下個小節分析 installContentProviders(app, data.providers); } } } } ``` :::warning * **若正在 backup 過程,就不啟動 Provider** ::: > ![](https://i.imgur.com/aLPXpz9.png) ### 處理 Provider 創建 - scheduleInstallProvider * Provider 所屬進程已經啟動,那 AMS 就會透過 Binder 機制呼叫到 [**ActivityThread**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java)#Application 的 **scheduleInstallProvider 方法** * AMS 透過 scheduleInstallProvider 方法,傳遞給 App 進程,並透過 Handler 傳遞 `INSTALL_PROVIDER` 呼叫 handleInstallProvider 方法 ```java= // ActivityThread.java public final class ActivityThread extends ClientTransactionHandler implements ActivityThreadInternal { private class ApplicationThread extends IApplicationThread.Stub { @Override public void scheduleInstallProvider(ProviderInfo provider) { sendMessage(H.INSTALL_PROVIDER, provider); } } class H extends Handler { public static final int INSTALL_PROVIDER = 145; public void handleMessage(Message msg) { ... 省略其他 case switch (msg.what) { case INSTALL_PROVIDER: handleInstallProvider((ProviderInfo) msg.obj); break; } } } public void handleInstallProvider(ProviderInfo info) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { installContentProviders(mInitialApplication, Arrays.asList(info)); } finally { StrictMode.setThreadPolicy(oldPolicy); } } } ``` * installContentProviders: 1. 透過 installProvider 方法創建 Provider 對象 2. 創建完 Provider 透過 AMS#publishContentProviders 通知 AMS Provider 創建完畢 ```java= // ActivityThread.java private void installContentProviders( Context context, List<ProviderInfo> providers) { final ArrayList<ContentProviderHolder> results = new ArrayList<>(); // 所有要安裝的 Provider for (ProviderInfo cpi : providers) { if (DEBUG_PROVIDER) { StringBuilder buf = new StringBuilder(128); buf.append("Pub "); buf.append(cpi.authority); buf.append(": "); buf.append(cpi.name); Log.i(TAG, buf.toString()); } ContentProviderHolder cph = installProvider(context, null, cpi, false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/); if (cph != null) { cph.noReleaseNeeded = true; results.add(cph); } } try { // 回復 AMS#publishContentProviders 已經完成 Provider 安裝 ActivityManager.getService().publishContentProviders( getApplicationThread(), results); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } ``` > ![](https://i.imgur.com/6Lhj85z.png) ### LoadedApk 創建 Provider 對象 * 最終會透過 LoadedApk (其實是 AppComponentFactory 類) 創建 Provider 對象 ```java= // ActivityThread.java private ContentProviderHolder installProvider(Context context, ContentProviderHolder holder, ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable) { ContentProvider localProvider = null; IContentProvider provider; // 尚未創建 Provider 對象 if (holder == null || holder.provider == null) { ApplicationInfo ai = info.applicationInfo; ... try { final java.lang.ClassLoader cl = c.getClassLoader(); LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); if (packageInfo == null) { // System startup case. packageInfo = getSystemContext().mPackageInfo; } // 透過 LoadedApk#instantiateProvider 創建 ContentProvider 對象 localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name); provider = localProvider.getIContentProvider(); if (provider == null) { Slog.e(TAG, "Failed to instantiate class " + info.name + " from sourceDir " + info.applicationInfo.sourceDir); return null; } localProvider.attachInfo(c, info); } /* 省略 catch */ } else { ... } ... 省略部分 return retHolder; } ``` 1. 透過 [**LoadedApk**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/LoadedApk.java) 創建 Provider 對象 ```java= // LoadedApk.java private AppComponentFactory mAppComponentFactory; public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { mActivityThread = activityThread; // Default ApplicationInfo mApplicationInfo = new ApplicationInfo(); ... // 透過 createAppFactory 方法創建 Factory mAppComponentFactory = createAppFactory(mApplicationInfo, mBaseClassLoader); } public AppComponentFactory getAppFactory() { return mAppComponentFactory; } private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) { // 若有自訂 Factory 則透過反射創建 if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) { try { return (AppComponentFactory) cl.loadClass(appInfo.appComponentFactory).newInstance(); } /* 省略部分 */ } // 預設 Factory return AppComponentFactory.DEFAULT; } ``` 2. 預設工廠 [**AppComponentFactory**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/AppComponentFactory.java):透過 ClassLoader#loadClass 讀取指定 Class,並透過反射創建指定類 ```java= // AppComponentFactory.java public class AppComponentFactory { ... 省略部分 public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl, @NonNull String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return (ContentProvider) cl.loadClass(className).newInstance(); } // 預設 Factory public static final AppComponentFactory DEFAULT = new AppComponentFactory(); } ``` > ![](https://i.imgur.com/oUOGCIJ.png) ## Appendix & FAQ :::info ::: ###### tags: `Android Framework`