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的数据库实现机制时在进行详细的说明与分析。


目录
相关文章
|
2月前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
3月前
|
存储 Android开发 开发者
深入理解安卓应用开发的核心组件
【10月更文挑战第8天】探索Android应用开发的精髓,本文带你了解安卓核心组件的奥秘,包括Activity、Service、BroadcastReceiver和ContentProvider。我们将通过代码示例,揭示这些组件如何协同工作,构建出功能强大且响应迅速的应用程序。无论你是初学者还是资深开发者,这篇文章都将为你提供新的视角和深度知识。
|
3月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
113 0
|
1月前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
3月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
3月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
55 6
|
4月前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
319 5
|
4月前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
5月前
|
存储 搜索推荐 Java
探索安卓开发中的自定义视图:打造个性化UI组件Java中的异常处理:从基础到高级
【8月更文挑战第29天】在安卓应用的海洋中,一个独特的用户界面(UI)能让应用脱颖而出。自定义视图是实现这一目标的强大工具。本文将通过一个简单的自定义计数器视图示例,展示如何从零开始创建一个具有独特风格和功能的安卓UI组件,并讨论在此过程中涉及的设计原则、性能优化和兼容性问题。准备好让你的应用与众不同了吗?让我们开始吧!
|
5月前
|
XML 搜索推荐 Android开发
安卓开发中的自定义View组件实践
【8月更文挑战第30天】探索Android世界,自定义View是提升应用界面的关键。本文以简洁的语言带你了解如何创建自定义View,从基础到高级技巧,一步步打造个性化的UI组件。