Android组件之ContentProvider

简介: android在实现进程间数据访问时,对不同的进程不同Application的数据访问提供了一套解决方案,这套解决方案便是ContentProvider。
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
  1. public boolean canRunHere(ProcessRecord app) {  
  2.     return (info.multiprocess || info.processName.equals(app.processName))  
  3.             && (uid == Process.SYSTEM_UID || uid == app.info.uid);  
  4. }  
   
    下面给出了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
  1. public ContentProviderResult[] applyBatch(String authority,  
  2.         ArrayList<ContentProviderOperation> operations)  
  3.         throws RemoteException, OperationApplicationException {  
  4.     ContentProviderClient provider = acquireContentProviderClient(authority);  
  5.     if (provider == null) {  
  6.         throw new IllegalArgumentException("Unknown authority " + authority);  
  7.     }  
  8.     try {  
  9.         return provider.applyBatch(operations);  
  10.     } finally {  
  11.         provider.release();  
  12.     }  
  13. }  
    下图为这个过程的时序图:
    

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

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

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

sendActivityResultLocked()ActivityManagerService.java

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

    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。
  1. <grant-uri-permission android:path="string"  
  2.                       android:pathPattern="string"  
  3.                       android:pathPrefix="string" />  

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子集设置不同的权限。

    

  1. <path-permission android:path="string"  
  2.                  android:pathPrefix="string"  
  3.                  android:pathPattern="string"  
  4.                  android:permission="string"  
  5.                  android:readPermission="string"  
  6.                  android:writePermission="string" />  

    

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


目录
相关文章
|
23天前
|
存储 设计模式 数据库
构建高效的安卓应用:探究Android Jetpack架构组件
【4月更文挑战第20天】 在移动开发的世界中,构建一个既高效又可维护的安卓应用是每个开发者追求的目标。随着Android Jetpack的推出,Google为开发者提供了一套高质量的库、工具和指南,以简化应用程序开发流程。本文将深入探讨Jetpack的核心组件之一——架构组件,并展示如何将其应用于实际项目中,以提升应用的响应性和稳定性。我们将通过分析这些组件的设计原则,以及它们如何协同工作,来揭示它们对于构建现代化安卓应用的重要性。
|
1月前
|
Android开发
Android四大组件详解2
Android四大组件详解
26 1
|
1月前
|
存储 监控 数据可视化
Android四大组件详解1
Android四大组件详解
42 0
|
3月前
|
设计模式 Android开发
[Android 四大组件] --- BroadcastReceiver
[Android 四大组件] --- BroadcastReceiver
35 0
|
5月前
|
数据库 Android开发 开发者
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
49 0
|
5月前
|
数据库 Android开发
Android Studio开发之应用组件Application的讲解及实战(附源码,通过图书管理信息系统实战)
Android Studio开发之应用组件Application的讲解及实战(附源码,通过图书管理信息系统实战)
68 0
|
5月前
|
XML Java Android开发
Android Studio开发之使用内容组件Content获取通讯信息讲解及实战(附源码 包括添加手机联系人和发短信)
Android Studio开发之使用内容组件Content获取通讯信息讲解及实战(附源码 包括添加手机联系人和发短信)
107 0
|
4天前
|
Java 开发工具 Android开发
如何在Eclipse中查看Android源码或者第三方组件包源码(转)
如何在Eclipse中查看Android源码或者第三方组件包源码(转)
13 4
|
23天前
|
设计模式 前端开发 数据库
构建高效Android应用:使用Jetpack架构组件实现MVVM模式
【4月更文挑战第21天】 在移动开发领域,构建一个既健壮又易于维护的Android应用是每个开发者的目标。随着项目复杂度的增加,传统的MVP或MVC架构往往难以应对快速变化的市场需求和复杂的业务逻辑。本文将探讨如何利用Android Jetpack中的架构组件来实施MVVM(Model-View-ViewModel)设计模式,旨在提供一个更加模块化、可测试且易于管理的代码结构。通过具体案例分析,我们将展示如何使用LiveData, ViewModel, 和Repository来实现界面与业务逻辑的分离,以及如何利用Room数据库进行持久化存储。最终,你将获得一个响应迅速、可扩展且符合现代软件工
25 0
|
27天前
|
Android开发 开发者
什么是Android Jetpack,它包括哪些组件?
【4月更文挑战第17天】Android Jetpack是Google提供的一套工具集,助力开发者高效、稳定地开发Android应用。它包含架构、UI、行为和基础组件,简化了后台任务、导航和生命周期管理,使开发者能专注于创新。随着不断更新,如CameraX的推出,掌握Jetpack对开发者面试和工作至关重要。
22 0