Android 进阶——Framework 核心之四大组件跨进程共享组件ContentProvider 工作机制源码详细分析(三)

引言

前面两篇文章,分别对ContentProvider的基本应用和ContentProvider的启动机制结合源码进行了详细的分析,这一篇对ContentProvider的工作原理进行分析。

源码基于API 28,源码有删减,只保留本专题相关的核心代码,仅供参考。

一、Context#getContentResolver()

涉及到的核心类有Candroid.app.ContextImpl、android.content.ContextWrapper、android.content.ContextWrapper、android.content.IContentProvider、android.content.ContentProviderNative

要使用ContentProvider 第一步就是通过Context#getContentResolver()方法来获取ContentResolver对象,本质上是通过android.app.ContextImpl#getContentResolver来获取的。

Context 是一个顶层抽象类,Candroid.app.ContextImpl是它的直接实现类,而ContextWrapper 知识代理android.app.ContextImpl,其中ContextImpl是个非公共类型类。

//android.content.ContextWrapper#getContentResolver,mBase 就是ContextImpl对象
@Override
 public ContentResolver getContentResolver() {
     return mBase.getContentResolver();
 }

android.app.ContextImpl#getContentResolver

@Override
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }

getContentResolver最终返回的是ContextImpl$ApplicationContentResolver对象。

	class ContextImpl extends Context {
		...
		private final ApplicationContentResolver mContentResolver;
	    private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
	            @NonNull LoadedApk packageInfo, @Nullable String splitName,
	            @Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
	            @Nullable ClassLoader classLoader) {
	        mOuterContext = this;
	        mMainThread = mainThread;
	        mActivityToken = activityToken;
	        mClassLoader = classLoader;
	        mResourcesManager = ResourcesManager.getInstance();
	
	        if (container != null) {
	            mBasePackageName = container.mBasePackageName;
	            mOpPackageName = container.mOpPackageName;
	            setResources(container.mResources);
	            mDisplay = container.mDisplay;
	        } else {
	            mBasePackageName = packageInfo.mPackageName;
	            ApplicationInfo ainfo = packageInfo.getApplicationInfo();
	        }
	        mContentResolver = new ApplicationContentResolver(this, mainThread);
	    }
	    ...
    }

ApplicationContentResolver 继承ContentResolver并实现对应的抽象方法,最终由ContentResolver的构造函数初始化和创建。

class ContextImpl extends Context {
	...
    private static final class ApplicationContentResolver extends ContentResolver {
        private final ActivityThread mMainThread;

        public ApplicationContentResolver(Context context, ActivityThread mainThread) {
            super(context);
            mMainThread = Preconditions.checkNotNull(mainThread);
        }
        ...
   }

android.content.ContentResolver#ContentResolver

    public ContentResolver(Context context) {
        mContext = context != null ? context : ActivityThread.currentApplication();
        mPackageName = mContext.getOpPackageName();
        mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
    }

所以当调用Context#getContentResolver时,当对应的ContentProvider所在的进程未启动时,首次访问时会触发对应的ContentProvider的创建,而ContentProvider的创建是由所在进程的启动时。无论通过ContentResolver对象首次调用CRUDQ方法时都会自动去启动所在的进程和对应ContentProvider。

二、ContentResolver对象进行CRUDQ

ContentResolver对象进行CRUDQ ,本质上就是进行Binder 通信,其他应用通过AMS访问这个ContentProvider。

1、获取IContentProvider对象

无论进行CRUDQ哪种操作,首先都是先获取IContentProvider对象IContentProvider 的具体实现是ContentProviderNative和ContentProvider.Transport类。

/**
 * The ipc interface to talk to a content provider.
 * @hide
 */
public interface IContentProvider extends IInterface {
    public Cursor query(String callingPkg, Uri url, @Nullable String[] projection,
            @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
            throws RemoteException;
    public String getType(Uri url) throws RemoteException;
    public Uri insert(String callingPkg, Uri url, ContentValues initialValues)
            throws RemoteException;
    public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
            throws RemoteException;
    public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
            throws RemoteException;
    public int update(String callingPkg, Uri url, ContentValues values, String selection,
            String[] selectionArgs) throws RemoteException;
    public ParcelFileDescriptor openFile(
            String callingPkg, Uri url, String mode, ICancellationSignal signal,
            IBinder callerToken)
            throws RemoteException, FileNotFoundException;
    public AssetFileDescriptor openAssetFile(
            String callingPkg, Uri url, String mode, ICancellationSignal signal)
            throws RemoteException, FileNotFoundException;
    public ContentProviderResult[] applyBatch(String callingPkg,
            ArrayList<ContentProviderOperation> operations)
                    throws RemoteException, OperationApplicationException;
    public Bundle call(
            String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras)
            throws RemoteException;
    public ICancellationSignal createCancellationSignal() throws RemoteException;

    public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
    public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException;

    public boolean refresh(String callingPkg, Uri url, @Nullable Bundle args,
            ICancellationSignal cancellationSignal) throws RemoteException;

    // Data interchange.
    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
    public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
            Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException;

    /* IPC constants */
    static final String descriptor = "android.content.IContentProvider";
    ...
}

最终都是通过ApplicationContentResolver#acquireProvider方法来获取的(因为这个方法在ContentResolver中是抽象的)

@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
    return mMainThread.acquireProvider(c,
            ContentProvider.getAuthorityWithoutUserId(auth),
            resolveUserIdFromAuthority(auth), false);
}

最终进入到ActivityThread#acquireProvider方法中,

   public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
				//首先查找是否已经存在目标ContentProvider
        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.
        ContentProviderHolder holder = null;
        try {
            synchronized (getGetProviderLock(auth, userId)) {
				//若未启动,则通过AMS来启动目标ContentProvider进程并启动ContentProvider,
                holder = ActivityManager.getService().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. 再通过installProvider来修改引用计数。
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

首先通过android.app.ActivityThread#acquireExistingProvider方法查找之前缓存的目标ContentProvider,存在则直接返回。

public final IContentProvider acquireExistingProvider(
            Context c, String auth, int userId, boolean stable) {
        synchronized (mProviderMap) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider;
            IBinder jBinder = provider.asBinder();
            if (!jBinder.isBinderAlive()) {
                // The hosting process of the provider has died; we can't
                // use this one.
                Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
                        + ": existing object's process dead");
                handleUnstableProviderDiedLocked(jBinder, true);
                return null;
            }

            // Only increment the ref count if we have one.  If we don't then the
            // provider is not reference counted and never needs to be released.
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                incProviderRefLocked(prc, stable);
            }
            return provider;
        }
    }

若不存在则通过Binder IPC通信,通过AMS来启动目标ContentProvider进程并启动ContentProvider,再通过installProvider来修改引用计数,进入到com.android.server.am.ActivityManagerService#getContentProvider:

    @Override
    public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String name, int userId, boolean stable) {
		...
        return getContentProviderImpl(caller, name, null, stable, userId);
    }

本质是执行这个com.android.server.am.ActivityManagerService#getContentProviderImpl

  private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, boolean stable, int userId) {
            ...
        ContentProviderRecord cpr;
        ContentProviderConnection conn = null;
        ProviderInfo cpi = null;

        synchronized(this) {
            // First check if this content provider has been published...
            cpr = mProviderMap.getProviderByName(name, userId);
            // If that didn't work, check if it exists for user 0 and then
            // verify that it's a singleton provider before using it.
            if (cpr == null && userId != UserHandle.USER_SYSTEM) {
                cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
                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_SYSTEM;
                        checkCrossUser = false;
                    } else {
                        cpr = null;
                        cpi = null;
                    }
                }
            }

            boolean providerRunning = cpr != null && cpr.proc != null && !cpr.proc.killed;
            if (providerRunning) {
                cpi = cpr.info;
                String msg;
                checkTime(startTime, "getContentProviderImpl: before checkContentProviderPermission");
                if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, checkCrossUser))
                        != null) {
                    throw new SecurityException(msg);
                }
            }
            if (!providerRunning) {
                // If the provider is a singleton AND
                // (it's a call within the same user || the provider is a
                // privileged app)
                // Then allow connecting to the singleton provider
                boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
                        cpi.name, cpi.flags)
                        && isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
                if (singleton) {
                    userId = UserHandle.USER_SYSTEM;
                }
                cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);

                // Make sure that the user who owns this provider is running.  If not,
                // we don't want to allow it to run.
                if (!mUserController.isUserRunning(userId, 0)) {
                    Slog.w(TAG, "Unable to launch app "
                            + cpi.applicationInfo.packageName + "/"
                            + cpi.applicationInfo.uid + " for provider "
                            + name + ": user " + userId + " is stopped");
                    return null;
                }

                ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
                checkTime(startTime, "getContentProviderImpl: before getProviderByClass");
                cpr = mProviderMap.getProviderByClass(comp, userId);
                checkTime(startTime, "getContentProviderImpl: after getProviderByClass");
                final boolean firstClass = cpr == null;
                if (firstClass) {
                    final long ident = Binder.clearCallingIdentity();

                    // If permissions need a review before any of the app components can run,
                    // we return no provider and launch a review activity if the calling app
                    // is in the foreground.
                    if (mPermissionReviewRequired) {
                        if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {
                            return null;
                        }
                    }

                    try {
                        checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
                        ApplicationInfo ai =
                            AppGlobals.getPackageManager().
                                getApplicationInfo(
                                        cpi.applicationInfo.packageName,
                                        STOCK_PM_FLAGS, userId);
                        checkTime(startTime, "getContentProviderImpl: after getApplicationInfo");
                        if (ai == null) {
                            Slog.w(TAG, "No package info for content provider "
                                    + cpi.name);
                            return null;
                        }
                        ai = getAppInfoForUser(ai, userId);
                        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)) {
                    // 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);
                }


                // This is single process, and our app is now connecting to it.
                // See if we are already in the process of launching this
                // provider.
                final int N = mLaunchingProviders.size();
                int i;
                for (i = 0; i < N; i++) {
                    if (mLaunchingProviders.get(i) == cpr) {
                        break;
                    }
                }
                // If the provider is not already being launched, then get it
                // started.
                if (i >= N) {
                    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);
                  
                        } catch (RemoteException e) {
                        } catch (IllegalArgumentException e) {
                        }

                        ProcessRecord proc = getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid, false);
                        if (proc != null && proc.thread != null && !proc.killed) {
                      
                            if (!proc.pubProviders.containsKey(cpi.name)) {
                                proc.pubProviders.put(cpi.name, cpr);
                                try {
                                    proc.thread.scheduleInstallProvider(cpi);
                                } catch (RemoteException e) {
                                }
                            }
                        } else {
                            //启动ContentProvider所在的进程
							proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0, "content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name), false, false, false);
                        }
                        cpr.launchingApp = proc;
                        mLaunchingProviders.add(cpr);
                    } finally {
                        Binder.restoreCallingIdentity(origId);
                    }
                }
            }
        }
        return cpr != null ? cpr.newHolder(conn) : null;
    }

如果ContentProvider没有启动则AMS会调用com.android.server.am.ActivityManagerService#startProcessLocked方法,启动ContentProvider所在的进程,然后再启动ContentProvider。

   @GuardedBy("this")
    private boolean startProcessLocked(String hostingType, String hostingNameStr, String entryPoint,
            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
            long startTime) {
		...
        if (mConstants.FLAG_PROCESS_START_ASYNC) {         
                try {

					// final String entryPoint = "android.app.ActivityThread";
                    final ProcessStartResult startResult = startProcess(app.hostingType, entryPoint,
                            app, app.startUid, gids, runtimeFlags, mountExternal, app.seInfo,
                            requiredAbi, instructionSet, invokeWith, app.startTime);
                    synchronized (ActivityManagerService.this) {
                        handleProcessStartedLocked(app, startResult, startSeq);
                    }
                } catch (RuntimeException e) {
                    synchronized (ActivityManagerService.this) {
                        Slog.e(TAG, "Failure starting process " + app.processName, e);
                        mPendingStarts.remove(startSeq);
                        app.pendingStart = false;
                        forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
                                false, false, true, false, false,
                                UserHandle.getUserId(app.userId), "start failure");
                    }
                }
            });
            return true;
        } else {
            try {
				//final String entryPoint = "android.app.ActivityThread";
                final ProcessStartResult startResult = startProcess(hostingType, entryPoint, app,
                        uid, gids, runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet,
                        invokeWith, startTime);
                handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
                        startSeq, false);
            } catch (RuntimeException e) {
                Slog.e(TAG, "Failure starting process " + app.processName, e);
                app.pendingStart = false;
                forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
                        false, false, true, false, false,
                        UserHandle.getUserId(app.userId), "start failure");
            }
            return app.pid > 0;
        }
    }

最终在com.android.server.am.ActivityManagerService#startProcess里调用 Process.start方法启动新进程,入口方法为ActivityThread.main方法,按照ContentProvider的创建和启动流程进行初始化(AMS与Zygote的通信是通过socket的)。

    public final Process.ProcessStartResult start(final String processClass,
                                                  final String niceName,
                                                  int uid, int gid, int[] gids,
                                                  int runtimeFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  String seInfo,
                                                  String abi,
                                                  String instructionSet,
                                                  String appDataDir,
                                                  String invokeWith,
                                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
                    zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
        }
    }

因此无论是单进程还是单线程模式下的ContentProvider 我们都可以随时使用,因为即使我们调用另一个未启动的进程的ContentProvider,系统也会为我们去启动并创建。

2、android.content.ContentProviderNative和android.content.ContentProvider.Transport

android.content.ContentProviderNative继承 Binder 实现 IContentProvider并重写了相关的CRUDQ方法,而ContentProvider.Transport又继承了ContentProviderNative,所以执行CRUDQ操作时本质上是调用ContentProvider.Transport对应的CRUD方法。

3、ContentProvider.Transport执行CRUDQ

首先是获取IContentProvider对象之后,剩下的就是通过AMS来访问ContentProvider(Binder类型的对象IContentProvider),,以query操作为例,android.content.ContentResolver#query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal),通过AMS 得到对应的ContentProvider实例ContentProvider.Transport。

    public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
            @Nullable String[] projection, @Nullable Bundle queryArgs,
            @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,
                        queryArgs, 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
                unstableProviderDied(unstableProvider);
                stableProvider = acquireProvider(uri);
                if (stableProvider == null) {
                    return null;
                }
                qCursor = stableProvider.query(
                        mPackageName, 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);

            // Wrap the cursor object into CursorWrapperInner object.
            final IContentProvider provider = (stableProvider != null) ? stableProvider
                    : acquireProvider(uri);
            final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
            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 {
            if (qCursor != null) {
                qCursor.close();
            }
            if (cancellationSignal != null) {
                cancellationSignal.setRemote(null);
            }
            if (unstableProvider != null) {
                releaseUnstableProvider(unstableProvider);
            }
            if (stableProvider != null) {
                releaseProvider(stableProvider);
            }
        }
    }

android.content.ContentProvider.Transport#query方法最终调用的还是ContentProvider的query方法,再通过Binder吧结果返回给调用者。

@Override
        public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
                @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
            validateIncomingUri(uri);
            uri = maybeGetUriWithoutUserId(uri);
            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                // The caller has no access to the data, so return an empty cursor with
                // the columns in the requested order. The caller may ask for an invalid
                // column and we would not catch that but this is not a problem in practice.
                // We do not call ContentProvider#query with a modified where clause since
                // the implementation is not guaranteed to be backed by a SQL database, hence
                // it may not handle properly the tautology where clause we would have created.
                if (projection != null) {
                    return new MatrixCursor(projection, 0);
                }

                // Null projection means all columns but we have no idea which they are.
                // However, the caller may be expecting to access them my index. Hence,
                // we have to execute the query as if allowed to get a cursor with the
                // columns. We then use the column names to return an empty cursor.
				// 最终还是调用当前ContentProvider类下对应的query方法
                Cursor cursor = ContentProvider.this.query(
                        uri, projection, queryArgs,
                        CancellationSignal.fromTransport(cancellationSignal));
                if (cursor == null) {
                    return null;
                }

                // Return an empty cursor for all columns.
                return new MatrixCursor(cursor.getColumnNames(), 0);
            }
            final String original = setCallingPackage(callingPkg);
            try {
                return ContentProvider.this.query(
                        uri, projection, queryArgs,
                        CancellationSignal.fromTransport(cancellationSignal));
            } finally {
                setCallingPackage(original);
            }
        }

其他的update、delete、insert方法也是类似的。

小结

ContentProvider的创建和启动都是伴随着进程启动,如果ContentProvider所在的进程未启动,会由AMS通过socket与Zygote进行通信申请创建新进程,并启动ActivityThread.main方法,由ActivityThread#attch方法发起,最终还是在handleBindApplication里完成与AMS通信创建和启动;而使用时本质上都是虽然是通过IContentProvider Binder对象去调用对应的方法,虽然转了一圈,但最终还是会回到ContentProvider自身的CURDQ方法,结果也会经由Binder返回给调用者,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值