android Application Component研究之ContentProvider

    本文为原创文章,欢迎转载!转载时请注明出处:http://blog.csdn.net/windskier

    android在实现进程间数据访问时,对不同的进程不同Application的数据访问提供了一套解决方案,这套解决方案便是ContentProvider。在开发应用程序时,想要访问其他进程的数据,ContentProvider便是不二的选择。

    Content Provider其实很简单,其实质就是IPC通信,通过提供一个IInterface给Client来访问当前进程的数据。下面来分析一下Content Provider的管理过程。

1. ContentResolver

    ContentResolver为application访问ContentProvider提供一套接入机制,有了ContentResolver,使application访问ContentProvider变得相当轻松。
    ContentResolver是一个抽象类,对于Application访问Provider的接口,Android提供了ContentResolver的一个子类ApplicationContentResolver,通过ApplicationContentResolver来获取provider。
    ApplicationContentResolver定义在ContextImpl.java中。

1.1 acquire provider

    ApplicationContentResolver是在Application请求对指定Uri进程数据操作时,根据Uri中包含的authority来获取相应的Provider。下面的序列图给出了acquire provider的过程。



    由上面的时序图中可以看出,Client需要向AMS请求获取相应的provider,这个Provider在AMS中以一个ContentProviderRecord类型的对象来管理的。ContentProviderRecord 是一个Parcelable类型,可以作为AMS返回值返回给Client进程。通过返回ContentProviderHolder对象,AMS可以提供给Client所请求的Provider。
    下面的类图给出了ContentProviderRecord与Provider的关系,可以从中看出Client通过ContentProviderRecord中提供的IContentProvider接口来访问Provider,不过有些情况下,Client可以本地创建Provier访问数据,此时Client就可以直接通过下图中的Transport类型去操作数据,这个规程下面会介绍到。

    Provider是提供保护数据的接入访问的,我们可以在AndroidManifest.xml中设定它的运行进程,一般情况下,不同进程间,不同Application间的访问只能通过IPC来进行,但那是有些情况是可以允许Client在自己的进程中创建本地Provider来进行访问的。下面介绍一下那些情况下允许本地创建Provider的几种情况:
    1. 当Client和Provider处在同一个进程中时,并且Client的UID和Provider的UID相同(UID是系统为每个AndroidManifest.xml中的Application分配的,或者根据设定的android:sharedUserId)时;
    2. 当Client和Provider处在同一个进程中,并且Client的UID和Provider的UID不相同,但是Client的UID为System UID;
    3. 当Client和Provider处在不同进程中,Provider设置了 android:multiprocess属性, 并且Client的UID和Provider的UID相同时;
    4. 当Client和Provider处在不同进程中,Provider设置了 android:multiprocess属性, 并且Client的UID和Provider的UID不相同时,但是Client的UID是System UID;
    这里需要说明一下, android的官方文档上说明Provider设置了 android:multiprocess属性,那么则会在Client访问时在本地创建一个Provider,但是事实上并不是这样的,除了 android:multiprocess属性的设置,还需要UID的匹配。相关的代码在
ContentProviderRecord.java
    public boolean canRunHere(ProcessRecord app) {
        return (info.multiprocess || info.processName.equals(app.processName))
                && (uid == Process.SYSTEM_UID || uid == app.info.uid);
    }
   
    下面给出了Provier和Client不再同一个进程时,在进程启动时Publish provide的时序图,publishContentProviders()方法将publish Provider所处application中所有的provider,publish过程即是为application中的provider创建对应的ContentProviderRecord对象,并存储在AMS中,待这些provider被请求访问时,直接提供给client其ContentProviderRecord对象,此时的ContentProviderRecord对象中存储着Provider的访问接口IContentProvider,通过这个接口,client可以IPC访问Provide。

 

    对于满足本地创建Provider的Client和Provider,AMS并不向Client提供IContentProvider,而是只提供给Client 表示Provider信息的ProviderInfo对象,Client再通过获得ProviderInfo来创建本地Provider。
    总结:
    1. Client远程访问Provider,从AMS获取 IContentProvider接口,通过IPC访问Provider;
    2. Client本地访问Provider,通过本地Provider的 Transport内部类对象,本地直接访问Provider。
    3. 如果Client本地访问Provider,那么AMS将会暂不对该Provider进行管理。

1.2 ContentProviderClient

     从ContentResolver中访问Provider的各个操作方法中可以看出,由于Component可能会访问不同的Provider的数据,因此在ContentResolver设计各个操作方法时,均会首先根据操作指定的Uri去acquire provider,但是acquire provider的过程会比较耗费资源,比如Client和AMS之间的IPC通信等。如果想要避免每次对Provider进行访问时都会acquire provider,Android提供了一个ContentProviderClient类型,它的成员变量包含了Provider的IContentProvider接口,Client可以请求ContentResolver为某一个请求的Provide创建一个ContentProviderClient对象,那么下次Client再次对该Provider进行操作时,可以不用再执行acquire provider操作,ContentProviderClient适用于频繁访问某一个Provide的情况。Client通过调用acquireContentProviderClient()方法获取ContentProviderClient。
    使用ContentProviderClient需要注意一点就是一定在不需要访问Provider时主动释放Provider。

1.3 ContentProviderOperation

    有时Client可能会进行批量的Provider操作,android针这种需求提供了一个便捷的方式,那就是使用ContentProviderOperation类,Client通过创建一个ContentProviderOperation的ArrayList对象,在这个ArrayList中记录下所有的Provider操作,每一个Provider操作均记录在一个ContentProviderOperation中,并交由ContentProvider来统一处理。ContentProviderOperation涵盖了最基本的Provider最基本的insert, update, delete, query操作。
   根据上一节所说的ContentProviderClient的作用,对于频繁访问同一个Provider应该使用ContentProviderClient访问Provider以便减少资源消耗,可以看出在使用ContentProviderOperation进行批量处理时,我们应该使用ContentProviderClient来进行来请求Provide操作。
@ContentResolver.java
    public ContentProviderResult[] applyBatch(String authority,
            ArrayList operations)
            throws RemoteException, OperationApplicationException {
        ContentProviderClient provider = acquireContentProviderClient(authority);
        if (provider == null) {
            throw new IllegalArgumentException("Unknown authority " + authority);
        }
        try {
            return provider.applyBatch(operations);
        } finally {
            provider.release();
        }
    }
    下图为这个过程的时序图:
    

1.4 Account sync

    我们知道Android设备在启动时会要求用户创建并登录一个Google account,并且提供了向该account同步设备信息的功能,如联系人,日程,email等内容。对于应用程序来说,ContentResolver提供了一套Sync同步的接口,在这篇文章中就不再详细的分析数据的Sync过程了,我打算在以后的文章中再分析Sync。如果在做应用程序开发时,的确需要向Account上同步数据,最好的方法是将数据设计为ContentProvider,利用ContentResolver的Sync接口去进行同步工作。

2. ContentProvider


2.1 Permission

2.1.1 Read/Write permission

     既然android通过ContentProvdier来实现进程间的数据访问,那么它就需要对保护的数据在被访问时进行权限检测,不但要检测读权限,同时要检测写权限。在AndroidManifest.xml中设置Provider的属性时,有3种permission可以设置,分别为android:permission, android:readPermission, android:writePermission.后2个Permission即为读写权限。而android:permission则代表着读和写2种权限, 但是android:permission只有在readPermission和writePermission未被设置时才有效。


2.1.2 grantUriPermissions

    android文档有关于grantUriPermissions的详细说明,这里就简单的提两句,阐述一下grantUriPermissions的使用情况。比如当application A需要使用Component B去访问Provider C,但是Component B并未添加Provider C的Read/Write permission,如果这个Provider设置了属性android:grantUriPermissions,那么就有办法使Component B访问C。通过设置A 启动 B时的Inent属性FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION,即可授权B在启动后去访问C。

    

    对于授权permission的具体实现在方法grantUriPermissionFromIntentLocked()中。

    有两种通过Inent属性来授权Permission,一种是Client启动新的Activity去访问Provider;

startActivityUncheckedLocked()@ActivityManagerService.java

        mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
                intent, r.getUriPermissionsLocked());

    另一种,client启动了一个activity去获取了Uri,这个activity在返回并通过send Result的方式向Client传递Uri的同时(返回的resultCode即是Intent),授权client对Provider的操作权限。下面是两种不同情况下Send result中授权的代码

sendActivityResultLocked()ActivityManagerService.java

        if (callingUid > 0) {
            mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
                    data, r.getUriPermissionsLocked());
        }
finishActivityLocked()@ActivityManagerService.java
            if (r.info.applicationInfo.uid > 0) {
                mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid,
                        resultTo.packageName, resultData, 
                        resultTo.getUriPermissionsLocked());
            }
    

    AndroidManifest.xml文件中的element grant-uri-permission和设置android:grantUriPermissions属性起到相同的作用,均需要Client的启动Intent设置FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION属性才能访问Provider。不同的是,grant-uri-permission只允许Client访问特定的Provider子集,grant-uri-permission内的参数设定了这个被允许访问的Provider子集。两者冲突时优先判决android:grantUriPermissions。

2.1.3 Path Permission

    AndroidManifest.xml文件中的element path-permission类似于grant-uri-permission,同样是指定某个Provider子集的权限设定;不同的是使用path-permission不需要设置Client Intent属性,并且path-permission单独设置了Read/Write Permission,这样可以使Provider更灵活的给不同的Provider子集设置不同的权限。

    

    

    我们知道ContentProvider就是一个提供了数据接入的实现,开发人员在设计一个ContentProvider时,需要确定采用什么样的方式来存储数据,对于不同的数据操作方式,ContentProvide需要提供不同的接入操作,因此在这篇文章中不再介绍具体的ContentProvider操作。ContentProvider最常用的数据存储方式是通过sqlite数据库,在以后有机会分析android的数据库实现机制时在进行详细的说明与分析。

你可能感兴趣的:(android Application Component研究之ContentProvider)