美文网首页我爱编程
ContentProvider原理

ContentProvider原理

作者: gczxbb | 来源:发表于2018-04-11 13:53 被阅读55次

    ContentProvider提供了一种向外部进程暴露数据的方式。在数据提供者进程中,数据可以是数据库,也可以是sharedpreferences,文件或xml存储方式。
    外部进程通过ContentProvider统一的数据访问接口实现访问,解耦,并且不需要关心具体存储方式。

    数据提供者ContentProvider

    数据操作的四种方法:增删查改。
    ContentProvider是抽象类,子类需要实现数据操作增删查改方法。

    //增
    public abstract  Uri insert(Uri uri, ContentValues values);
    //删
    public abstract int delete(Uri uri, String selection,
                 String[] selectionArgs);
    //查
    public abstract Cursor query(Uri uri, String[] projection,
                String selection, String[] selectionArgs,
                String sortOrder);
    //改
    public abstract int update(Uri uri, ContentValues values,
                String selection, String[] selectionArgs);
    

    以数据库为例,ContentProvider子类实现四个方法就是数据提供者进程sqlite的操作,通过Uri访问。


    数据访问者ContentResolver

    Activity提供了获取ContentResolver的getContentResolver方法。

    @Override
    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }
    
    //ContextImpl#getContentResolver方法。
    @Override
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }
    

    在构造方法中初始化ContentResolver。

    private ContextImpl(ContextImpl container, ActivityThread mainThread,
                LoadedApk packageInfo, ...) {
        mOuterContext = this;
        mMainThread = mainThread;
        ...
        if (user == null) {
            user = Process.myUserHandle();
        }
        ....
        mContentResolver = new ApplicationContentResolver(this, mainThread, user);
    }
    

    ApplicationContentResolver继承ContentResolver抽象类,实现方法是acquireProvider。
    当在进程中访问ContentResolver的增删查改方法时,通过传入的Uri定位到ContentProvider。下面是ContentResolver#insert方法插入数据。

    public final Uri insert(Uri url, ContentValues values) {
        //先ContentResolver的final方法
        IContentProvider provider = acquireProvider(url);
        //provider为空说明Uri无法识别,抛出异常
        try {
            long startTime = SystemClock.uptimeMillis();
            Uri createdRow = provider.insert(mPackageName, url, values);
            long durationMillis = SystemClock.uptimeMillis() - startTime;
            return createdRow;
        } catch (RemoteException e) {
            return null;
        } finally {
            releaseProvider(provider);
        }
    }
    

    首先acquireProvider方法获取到IContentProvider,然后触发insert插入数据,返回成功插入的index,最后释放。
    ApplicationContentResolver实现了acquireProvider方法。

    public final IContentProvider acquireProvider(Uri uri) {
        if (!SCHEME_CONTENT.equals(uri.getScheme())) {
            return null;
        }
        final String auth = uri.getAuthority();
        if (auth != null) {
            //触发子类实现的acquireProvider方法
            return acquireProvider(mContext, auth);
        }
        return null;
    }
    

    若统一资源标识符协议不是content,不符合Uri,返回空。getAuthority获取的解析Uri得到的authorities字符串。协议://域名/目录/文件#片段标示符,在数据源ContentProvider中,通过UriMatcher的addURI添加需匹配的Uri,包括authority,存储路径或者表名,以及匹配码。在触发UriMatcher#match方法时,如果传入Uri匹配,则返回匹配码。
    子类acquireProvider方法触发ActivityThread的acquireProvider方法。

    protected IContentProvider acquireProvider(Context context, String auth) {
        return mMainThread.acquireProvider(context,
                        ContentProvider.getAuthorityWithoutUserId(auth),
                        resolveUserIdFromAuthority(auth), true);
    }
    

    ActivityThread#acquireProvider方法。
    acquireExistingProvider根据auth和userId查找本地保存的IContentProvider,直接返回。

    //存储本地IContentProvider
    //userId和auth代表ProviderKey,ProviderClientRecord封装IContentProvider。
    ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap。
    
    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;
        }
        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                        getApplicationThread(), auth, userId, stable);
        } catch (RemoteException ex) {
        }
        if (holder == null) {
            //无法找到auth对应的provider
            return null;
        }
        holder = installProvider(c, holder, holder.info,
                    true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }
    

    若本地不存在,委托给Ams#getContentProvider获取。

    数据通信原理

    IContentProvider是一个Binder代理,从Ams获得。当触发IContentProvider的数据插入方法时,数据提供者进程端触发Transport的insert方法。
    Transport是ContentProvider内部类,继承ContentProviderNative,实现IContentProvider具体的数据操作业务。下面是数据插入Transport#insert方法。

    @Override
    public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
        validateIncomingUri(uri);
        int userId = getUserIdFromUri(uri);
        uri = getUriWithoutUserId(uri);
        if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
            return rejectInsert(uri, initialValues);
        }
        final String original = setCallingPackage(callingPkg);
        try {
            //重写的ContentProvider的insert方法
            return maybeAddUserId(ContentProvider.this.insert(uri, initialValues), userId);
        } finally {
            setCallingPackage(original);
        }
    }
    

    通过Transport的insert方法会进入我们重写的ContentProvider子类的insert,从而达到控制数据插入的目的。

    ContentProvider实现进程数据共享的底层逻辑基于Binder通通信机制。


    任重而道远

    相关文章

      网友评论

        本文标题:ContentProvider原理

        本文链接:https://www.haomeiwen.com/subject/pbrohftx.html