今天来总结下Android中的ContentProvider(以下简称CP),具体代码请见https://github.com/Mangosir/ContentProviderReview/tree/master

概述

其实相对于Acivity,Service,BroadcastReciver这三个组件,CP的使用率还是相对比较少的,除非你开发的app是要采用手机本地资源或跟其它app共享资源的。从这个使用目的来看好像ContentProvider是用来进行进程间通信的,确实,android中IPC的六种方式之一就是使用CP,好吧,扯远了,那就走上正轨,先来看看官网介绍:

CP管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。 CP是连接一个进程中的数据与另一个进程中运行的代码的标准界面。

如果您想要访问CP中的数据,可以将应用的 Context 中的 ContentResolver 对象用作客户端来与CP通信。ContentResolver 对象会与CP对象(即实现 ContentProvider 的类实例)通信。CP对象从客户端接收数据请求,执行请求的操作并返回结果。

如果您不打算与其他应用共享数据,则无需开发自己的CP。 不过,您需要通过自己的CP在您自己的应用中提供自定义搜索建议。 如果您想将复杂的数据或文件从您的应用复制并粘贴到其他应用中,也需要创建您自己的提供程序。

Android 本身包括的CP可管理音频、视频、图像和个人联系信息等数据。 android.provider 软件包参考文档中列出了部分提供程序。 任何Android 应用都可以访问这些提供程序,但会受到某些限制。

简介

CP是Android四大组件之一,为存储数据和获取数据提供统一的接口,可以在不同的应用程序间进行数据共享(IPC方式之一)。CP是以类似于数据库中的表的形式来组织数据的,无论数据来源是什么,CP都可以被认为是一张表,它把数据组装成一张表格来被外界使用;它对外提供了一下几个方法

onCreate() 实现这个方法在启动时初始化你的CP

insert(Uri uri,ContentValues values) 实现这个方法处理一个请求插入新的一行

query(Uri uri, String[]projection, String selection, String[] selectionArgs, String sortOrder)

实现这个方法去处理来自客户端的查询请求,支持取消操作

update(Uri uri,ContentValues values, String selection, String[] selectionArgs)

实现这个方法,处理更新一行或者多行的请求

delete(Uri uri, Stringselection, String[] selectionArgs)

实现这个方法,处理删除一行或者更多行的请求

getType(Uri uri) 实现这个方法处理指定uri的数据的MIME类型

从这些方法也可以看出对CP的操作与对数据库的操作类似。

使用原因

1.其中一个原因上面也说了,就是进程间通信的一种方式;

2.CP提供了对底层数据存储方式的抽象,不管底层使用Sqlite数据库还是MongoDB,被CP封装后给应用层开发者提供的数据接口不变

3.CP为应用程序间的数据共享提供了一个安全的环境,它允许你把自己想要开放的数据提供给其它应用进行增删改差,而不需要担心开放数据库权限所带来的安全问题

使用CP

在讲解之前先看下谷歌官方文档怎么介绍的(地址是在线API)

大致翻译下,可能不准确,英文有点渣

CP是Android应用程序的主要构建模块之一,为应用程序提供内容。CP封装数据并通过单个ContentResolver接口提供给应用程序。

当你需要在多个应用程序之间分享数据的时候就可以用到CP。比如,手机通讯录数据被多个应用程序使用并且只能存储在CP中。如果您不需要在多个应用程序间分享数据,你可以通过SqliteDatabase使用数据库。

当通过ContentResolver发出请求时,系统会检查给定的URI的权限,并将请求传递给注册该权限的CP。CP也可以解析其它的URI,UriMatcher类有助于解析URI。

数据访问方法(比如insert和update)可能被多个线程同时调用,所以必须是线程安全的。其它方法(比如onCreate)只能由应用程序主线程调用,同时避免执行冗长操作。参考方法描述以了解它们的线程特性。

对ContentResolver的请求会自动转发到合适的CP实例,所以子类不需要担心跨进程调用的细节。

在这里我们了解到有一个ContentResolver是作为客户端发请求到ContentProvider来访问其中的数据。那通过啥媒介来访问请求呢,没错就是URI:

Content URI是用于在CP中标识数据的URI,它包括整个CP的符号名称(授权)和一个指向表或者文件的名称(路径),后面追加可选ID表示表中具体哪一行。模式如下 content://<authority>/<path>/<id>。CP每一个数据访问方法都将URI作为传参,通过它来确定要访问的表,行或文件。

设计授权:CP通常具有单一授权,该授权是它在应用程序内的名称,为避免与其它应用程序的CP冲突,通常我们将CP所在应用程序的包名的扩展名来作为授权,比如content://com.mango.test.provider

设计路径结构:我们通常通过追加指向单个表的路径来创建URI,比如我有一个表table1,这URI是content://com.mango.test.provider/table1

处理URI可选ID:ContentProvider将ID值与表的_ID列进行匹配,并对匹配的行执行请求的访问,比如content://com.mango.test.provider/table1/6

讲到URI,Android提供了一个类UriMatcher,主要是在CP用来匹配Uri的,当我们通过CP插入一条数据的时候,先用UriMatcher进行匹配,假如是系统的ContentProvider,比如联系人,这些系统提供了相应的Uri,我们就可以根据系统提供的Uri来操作数据。

这个类提供了三个开放方法,还有两个匹配模式

在路径后添加# 比如content://com.mango.test.provider/table1/#    这是匹配由任意长度的数字字符组成的字符串(content://com.mango.test.provider/table1/11 就可以匹配成功)

在路径后面添加* 比如content://com.mango.test.provider/table1/*  这是匹配由任意长度的任何有效字符组成的字符串

(content://com.mango.test.provider/table1/k2 就可以匹配成功)

接下来我们就写个在一个APP里去对另一个APP的ContentProvider进行增删改查人员的操作的Demo。

新建一个类去继承ContentProvider

public class PeopleContentProvider extends ContentProvider {//AUTHORITY_APP要与xml注册文件里写的保持一致private static final String AUTHORITY_APP = "com.mangoer.review";//匹配成功返回的匹配码private static final int MATCH_ALL_CODE = 100;private static final int MATCH_ONE_CODE = 101;//Uri匹配检查的类private static UriMatcher uriMatcher;//数据库操作实例private SQLiteDatabase db;//创建数据库的辅助类private DBHelper openHelper;private Cursor cursor = null;//数据改变后指定通知的Uriprivate static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY_APP + "/student");//在静态代码块中添加要匹配的 Uristatic {//入参是匹配不成功时返回NO_MATCH(-1)uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);/*** uriMatcher.addURI(authority, path, code); 其中* authority:主机名(用于唯一标示一个ContentProvider,这个需要和清单文件中的authorities属性相同)* path:路径路径(可以用来表示我们要操作的数据,路径的构建应根据业务而定)* code:返回值(用于匹配uri的时候,作为匹配成功的返回值)*/uriMatcher.addURI(AUTHORITY_APP, "student", MATCH_ALL_CODE);// 匹配记录集合uriMatcher.addURI(AUTHORITY_APP, "student/#", MATCH_ONE_CODE);// 匹配单条记录}@Overridepublic boolean onCreate() {//创建数据库openHelper = new DBHelper(getContext());db = openHelper.getWritableDatabase();return false;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {switch (uriMatcher.match(uri)) {/*** 这里如果匹配是uriMatcher.addURI(AUTHORITY_APP, "student",* MATCH_SUCCESS_CODE)中的Uri,则我们可以在这里对这个ContentProvider中的数据库* 进行删除等操作。这里如果匹配成功,我们将删除所有的数据*/case MATCH_ALL_CODE:int count=db.delete("personData", null, null);if(count>0){notifyDataChanged();return count;}break;/*** 这里如果匹配是uriMatcher.addURI(AUTHORITY_APP,* "student/#",MATCH_ONE_CODE);中的Uri,则说明我们要操作单条记录*/case MATCH_ONE_CODE:// 这里可以做删除单条数据的操作。break;default:throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());}return 0;}@Overridepublic String getType(Uri uri) {return null;}/*** 插入 使用UriMatch的实例中的match方法对传过来的 Uri进行匹配。 这里通过ContentResolver传过来一个Uri,* 用这个传过来的Uri跟在ContentProvider中静态代码块中uriMatcher.addURI加入的Uri进行匹配* 根据匹配的是否成功会返回相应的值,在上述静态代码块中调用uriMatcher.addURI(AUTHORITY_APP,* "student",MATCH_CODE)这里的MATCH_CODE* 就是匹配成功的返回值,也就是说假如返回了MATCH_CODE就表示这个Uri匹配成功了* ,我们就可以按照我们的需求就行操作了,这里uriMatcher.addURI(AUTHORITY_APP,* "person/data",MATCH_CODE)加入的Uri为:* content://com.example.studentProvider/student* ,如果传过来的Uri跟这个Uri能够匹配成功,就会按照我们设定的步骤去执行相应的操作*/@Overridepublic Uri insert(Uri uri, ContentValues values) {int match=uriMatcher.match(uri);if(match!=MATCH_ALL_CODE){throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());}long rawId = db.insert("personData", null, values);Uri insertUri = ContentUris.withAppendedId(uri, rawId);if(rawId>0){notifyDataChanged();return insertUri;}return null;}/*** 查询 如果uri为* content://com.example.studentProvider/student则能匹配成功,然后我们可以按照需求执行匹配成功的操作*/@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {switch (uriMatcher.match(uri)) {/*** 如果匹配成功,就根据条件查询数据并将查询出的cursor返回*/case MATCH_ALL_CODE:cursor = db.query("personData", null, null, null, null, null, null);break;case MATCH_ONE_CODE:// 根据条件查询一条数据。。。。break;default:throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());}return cursor;}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {switch (uriMatcher.match(uri)) {case MATCH_ONE_CODE:long age = ContentUris.parseId(uri);selection = "age = ?";selectionArgs = new String[] { String.valueOf(age) };int count = db.update("personData", values, selection,selectionArgs);if(count>0){notifyDataChanged();}break;case MATCH_ALL_CODE:// 如果有需求的话,可以对整个表进行操作break;default:throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());}return 0;}//通知指定URI数据已改变private void notifyDataChanged() {getContext().getContentResolver().notifyChange(NOTIFY_URI, null);}
}

然后在AndroidManifest.xml中注册ContentProvider

<providerandroid:name=".provider.PeopleContentProvider"android:authorities="com.mangoer.review"android:exported="true"></provider>

这里的authorities是这个ContentProvider唯一标识,这样别的应用就可以找到这个CP了

exported为true表示允许别的应用访问

内容提供者已经写好了,现在在另外一个APP里写一个内容访问者

public class MainActivity extends Activity implements OnClickListener {private ContentResolver contentResolver;private ListView lvShowInfo;private MyAdapter adapter;private Button btnInit;private Button btnInsert;private Button btnDelete;private Button btnUpdate;private Button btnQuery;private Cursor cursor;private static final String AUTHORITY = "com.mangoer.review";private static final Uri STUDENT_ALL_URI = Uri.parse("content://" + AUTHORITY + "/student");private Handler handler=new Handler(){public void handleMessage(android.os.Message msg) {//当ContentProvider有数据更新推送过来的时候,我们可以在此做一些操作// 比如Adapter.notifyDataSetChanged()cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);adapter.changeCursor(cursor);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);lvShowInfo=findViewById(R.id.lv_show_info);initData();}private void initData() {btnInit=findViewById(R.id.btn_init);btnInsert=findViewById(R.id.btn_insert);btnDelete=findViewById(R.id.btn_delete);btnUpdate=findViewById(R.id.btn_update);btnQuery=findViewById(R.id.btn_query);btnInit.setOnClickListener(this);btnInsert.setOnClickListener(this);btnDelete.setOnClickListener(this);btnUpdate.setOnClickListener(this);btnQuery.setOnClickListener(this);contentResolver = getContentResolver();//注册内容观察者contentResolver.registerContentObserver(STUDENT_ALL_URI,true,new PersonOberserver(handler));adapter=new MyAdapter(MainActivity.this,cursor);lvShowInfo.setAdapter(adapter);}@Overridepublic void onClick(View v) {switch (v.getId()) {//初始化case R.id.btn_init:ArrayList<Student> students = new ArrayList<>();Student student1 = new Student("苍老师",25,"好老师");Student student2 = new Student("柳岩",26,"好球");Student student3 = new Student("杨幂",27,"大幂幂");Student student4 = new Student("陈冠希",28,"拍的一手好片");students.add(student1);students.add(student2);students.add(student3);students.add(student4);for (Student Student : students) {ContentValues values = new ContentValues();values.put("name", Student.getName());values.put("age", Student.getAge());values.put("msg", Student.getMsg());contentResolver.insert(STUDENT_ALL_URI, values);}break;//增case R.id.btn_insert:Student student = new Student("小明", 26, "帅气男人");//实例化一个ContentValues对象ContentValues insertContentValues = new ContentValues();insertContentValues.put("name",student.getName());insertContentValues.put("age",student.getAge());insertContentValues.put("msg",student.getMsg());//这里的uri和ContentValues对象经过一系列处理之后会传到ContentProvider中的insert方法中,//在我们自定义的ContentProvider中进行匹配操作contentResolver.insert(STUDENT_ALL_URI,insertContentValues);break;//删case R.id.btn_delete://删除所有条目contentResolver.delete(STUDENT_ALL_URI, null, null);//删除_id为1的记录Uri delUri = ContentUris.withAppendedId(STUDENT_ALL_URI,1);contentResolver.delete(delUri, null, null);break;//改case R.id.btn_update:ContentValues contentValues = new ContentValues();contentValues.put("msg","性感");//更新数据,将age=26的条目的msg更新为"性感",原来age=26的introduce为"大方".//生成的Uri为:content://com.mangoer.review/student/26Uri updateUri = ContentUris.withAppendedId(STUDENT_ALL_URI,26);contentResolver.update(updateUri,contentValues, null, null);break;//查case R.id.btn_query://通过ContentResolver获得一个调用ContentProvider对象Cursor cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);//CursorAdapter的用法,参考此博客:http://blog.csdn.net/dmk877/article/details/44983491adapter=new MyAdapter(MainActivity.this,cursor);lvShowInfo.setAdapter(adapter);cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);adapter.changeCursor(cursor);break;}}
}

在初始化的时候我们用ContentResolver注册了一个ContentOberServer:

直译也就是内容观察者,观察指定的Uri引起的数据库的变化,然后通知主线程,根据需求做我们想要做的处理。这样说大家可能理解的不是特别透彻,这样跟大家说它可以实现类似于Adapter的notifyDataSetChanged()这个方法的作用,比方说当观察到ContentProvider的数据变化时会自动调用谷歌工程师给我们提供的好的方法,可以在此方法中通知主线程数据改变等等。那么问题来了,应该怎样实现这样的功能呢?首先要做的就是注册这个观察者,这里的注册是在需要监测ContentProvider的应用中进行注册而不是在ContentProvider中,而在ContentProvider中要做的就是当数据变化时进行通知,这里的通知的方法谷歌已经帮我们写好,直接调用就行了,查看谷歌文档你会发现在ContentResolver中有这样的介绍:

public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)

注册一个观察者实例,当指定的Uri发生改变时,这个实例会回调实例对象做相应处理。

参数:uri:需要观察的Uri

notifyForDescendents:如果为true表示以这个Uri为开头的所有Uri都会被匹配到,

如果为false表示精确匹配,即只会匹配这个给定的Uri。

举个例子,假如有这么几个Uri:

①content://com.mangoer.review/student

②content://com.mangoer.review/student/#

③content://com.mangoer.review/student/10

④content://com.mangoer.review/student/teacher

假如观察的Uri为content://com.example.studentProvider/student,当notifyForDescendents为true时则以这个Uri开头的Uri的数据变化时都会被捕捉到,在这里也就是①②③④的Uri的数据的变化都能被捕捉到,当notifyForDescendents为false时则只有①中Uri变化时才能被捕捉到。

看到registerContentObserver这个方法,根据语言基础我想大家能够想到ContentResolver中的另一个方法

public finalvoidunregisterContentObserver(ContentObserverobserver)

它的作用就是取消对注册的那个Uri的观察,这里传进去的就是在registerContentObserver中传递进去的ContentObserver对象。到这关于注册和解除注册的ContentObserver可能大家都比较清楚了,那么问题来了,怎么去写一个ContentObserver呢?其实它的实现很简单,直接创建一个类继承ContentObserver需要注意的是这里必须要实现它的构造方法

public ContentObserver(Handlerhandler)

这里传进去的是一个Handler对象,这个Handler对象的作用一般要依赖于ContentObserver的另一个方法即

public void onChange(boolean selfChange)

这个方法的作用就是当指定的Uri的数据发生变化时会回调该方法,此时可以借助构造方法中的Handler对象将这个变化的消息发送给主线程,当主线程接收到这个消息之后就可以按照我们的需求来完成相应的操作

调用过程源码解析

源码分析由于篇幅太长,就不在这里继续了,放到下一篇分析ContentResolver与ContentProvider的联系之源码解析

Android四大组件之ContentProvider 全面解析,ContentResolver源码解析如何调用其它APP的ContentProvider相关推荐

  1. 安卓Android与H5双向交互MathJax展示数学公式(源码+解析)

    安卓Android与H5双向交互MathJax展示数学公式(源码+解析) 博主就今天周五又做了个需求(安卓Android与H5交互),原来上线的功能是服务器配置过来的学习报告(一个H5页面)并提供原始 ...

  2. Kafka核心源码解析 - KafkaController源码解析

    在进入源码解析之前,我先来介绍一些KafkaController在Kafka集群中的作用. (1)负责监听zookeeper上所有的元数据变更请求: (2)负责topic的partition迁移任务分 ...

  3. spring 源码深度解析_spring源码解析之SpringIOC源码解析(下)

    前言:本篇文章接SpringIOC源码解析(上),上一篇文章介绍了使用XML的方式启动Spring,介绍了refresh 方法中的一些方法基本作用,但是并没有展开具体分析.今天就和大家一起撸一下ref ...

  4. 源码 解析_List源码解析

    点击上方「10分钟编程」关注我呦 让我们在一起每天「博学」一点点,成为更好的自己! List源码解析 本篇文章有点长,所以先列个目录 List源码解析 1.ArrayList 2.LinkedList ...

  5. linux WiFi源码解析,WIFIDOG 源码解析

    WIFIDOG 源码解析 openwrt wifidog是我linux c语言编程的启蒙项目,一年前折腾此项目大半年,从此爱上了linux 系统编程.现在看来,这是一个再简单不过的linux c语言项 ...

  6. Android基础四大组件之Activity的启动过程源码解析

    前言 Activity是Android中一个很重要的概念,堪称四大组件之首,关于Activity有很多内容,比如生命周期和启动Flags,这二者想要说清楚,恐怕又要写两篇长文,更何况分析它们的源码呢. ...

  7. android glide流程解析,Glide 源码解析(一):简单流程分析

    这篇文章上次修改于 839 天前,可能其部分内容已经发生变化,如有疑问可询问作者. 这篇文章是这个系列的第一篇文章,我第一次写这样连续系列的文章,我先一层一层的剥开 Glide ,如果谁有更好的想法欢 ...

  8. Android特别的数据结构(二)ArrayMap源码解析

    1. 数据结构 public final class ArrayMap<K,V> implements Map<K,V> 由两个数组组成,一个int[] mHashes用来存放 ...

  9. Android特别的数据结构(一) SparseArray源码解析

    1.数据结构 class SparseArray<E> implements Cloneable 由两个数组构成,一个数组mKeys类型为int[],存放Key,一个数组mValues类型 ...

最新文章

  1. 反欺诈中所用到的机器学习模型有哪些?
  2. SpringSide 3 中的 Struts 2
  3. python编程入门详解_python编程入门知识练习
  4. Jython 安装使用
  5. Zookeeper面试题
  6. 如何通过命令终端访问本地/局域网/远程的MySQL数据库_访问数据库_连接数据库_登录数据库
  7. 防火墙配置十大任务之十,构建虚拟防火墙
  8. SpringMVC注解HelloWorld
  9. python使用协程实现udp_python-socket和进程线程协程(代码展示)
  10. testng查看覆盖率_使用Cobertura统计单元测试覆盖率
  11. mysql查询全年星期_数据库查询显示一年中所有的周一到周五的数据
  12. MPLS ××× Carrier Supporting Carrier Option AB(二)
  13. 如何评价NVIDIA RTX 2080 Ti显卡?
  14. 麒麟810相当于骁龙多少?
  15. 系统集成项目管理工程师教程重点、笔记和试题大全
  16. 微信小程序中使用 web-view 内嵌 H5 时,登录问题的处理方法
  17. 亲测比较好用的各类软件
  18. 中文核心期刊投稿指南
  19. 谷歌语法和FOFA常用语法总结
  20. Python计算贝塔系数和夏普比率

热门文章

  1. 计算机毕业设计Java电子商城系统(源码+系统+mysql数据库+lw文档)
  2. 融合计费账务系统架构与核心功能的研究与实现
  3. 软件测试的缺陷等级划分
  4. Python 核心编程(三)
  5. java参数错误,参数不匹配错误java.lang.IllegalArgumentException: argument type mismatch
  6. java边界布局东南西北_JAVA swing布局管理器实例解析
  7. 都说vivo营销强,但你知道vivo营销究竟强在哪里吗?
  8. 国家大数据政策文件汇编(2022年)(附下载)
  9. mac M1芯片安装vmware虚拟机及centos8详细教程
  10. 项目篇--win10卸载C:\Windows\assembly下的程序集