四大组件-内容提供者ContentProvider

定义

内容提供者,是Android四大组件之一

作用

进程之间进行数据 交互、共享 ,即跨进程通信

1.内容提供者只是搬运工,真正的存储、操作数据的数据源还是原来存储数据的方式(数据库、文件、xml或网络)
2.数据源可以为:数据库、文件、XML或网络等

原理

ContentProvider的底层原理 即为 Binder的机制

具体使用

ContentProvider的使用主要有以下内容:

1.统一资源标识符(URI)
  • 定义:URI(Uniform Resource Identifier),即统一资源标识符
  • 作用:唯一标识ContentProvider及其中的数据

外界进程通过URI找到对应的ContentProvider及其中的数据,再进行数据操作

  • 具体使用
    URI分为系统预置和自定义,分别对应系统内置数据(通讯录、日程表等)和自定义数据库

自定义URI=content://com.wz.provider/User/1 , 其中

  • 主题Schema(content):ContentProvider的URI前缀(Android规定)
  • 授权信息Authority(com.wz.provider) :ContentProvider的唯一标识符
  • 表名Path(User):ContentProvider指向数据库的某个表名
  • 记录ID(1):表中的某个记录(若无指定则返回全部记录)
//设置URI
Uri uri=Uri.parse("content://com.wz.provider/User/1")
// 上述URI指向的资源是:名为 `com.wz.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据
// 特别注意:URI模式存在匹配通配符* & #

// *:匹配任意长度的任何有效字符的字符串
// 以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/* 

// #:匹配任意长度的数字字符的字符串
// 以下的URI 表示 匹配provider中的table表的所有行
content://com.example.app.provider/table/#
2.MIME数据类型
  • 作用:指定某个扩展名的文件用某种应用程序来打开,例如指定.html文件采用text应用程序打开

  • 具体使用:

    ContentProvider根据URI返回MIME类型
    ContentProvider.getType(uri);
    
    MIME类型组成

    每种MIME类型由两部分组成:类型+子类型(MIME是一个字符串)

    text/html  
    text=类型,html=子类型
    
    MIME类型形式

    MIME类型有2种形式:单条记录、多条记录

    //形式1:单条记录
    vnd.android.cursor.item/自定义
    //形式2:多条记录
    vnd.android.cursor.dir/自定义
    
    //1. vnd:表示父类型和子类型具有非标准、特定的形式。
    //2. vnd:父类型已经固定好(即不能更改),只能区别是单条还是多条
    //3. 子类型可自定义
    

    实例

    < -- 单条记录 -- >
      //单个记录的MIME类型
      vnd.android.cursor.item/vnd.yourcompanyname.contenttype 
      
      //若一个uri如下
      content://com.example.transportationprovider/trains/122   
      // 则ContentProvider会通过ContentProvider.geType(url)返回以下MIME类型
      vnd.android.cursor.item/vnd.example.rail
    
    
    < -- 多条记录 -- >
      //多条记录的MIME类型
      vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
    
       // 若一个Uri如下
      content://com.example.transportationprovider/trains
       // 则ContentProvider会通过ContentProvider.geType(url)返回以下MIME类型
      vnd.android.cursor.dir/vnd.example.rail
    
3.ContentProvider类
组织数据的方式
  • ContentProvider主要以 表格的形式 组织数据(同时也支持文件数据,表格用得比较多)
  • 每个表格中包含多张表,每张表包含行、列、分别对应记录、字段(行==记录,列==字段)
主要方法
  • 进程间共享数据的本质就是:添加、删除、获取、更新。所以ContentProvider的核心方法也主要是上述4个作用

    < -- 4个核心方法 -- >
      //外部进程向ContentProvider  中 添加 数据
      public Uri insert(Uri uri,ContentValues values)
    
      //外部进程 删除 ContentProvider中的数据
      public int delete(Uri uri,String selection,String[] selectionArgs)
    
      //外部进程 更新 ContentProvider中的数据
      public int update(Uri uri,ContentValues values,String selection,String[] selectionArgs)
    
      //外部进程 获取 ContentProvider 中的数据
      public Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs)
    //注
      1.上述四个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)
      2.存在多线程并发访问,需要实现线程同步(单个SQLite不需要,SQLite内部已经实现了线程同步)
    < -- 2个其他方法 -- >
      //ContentProvider创建后 或 打开系统后其他进程第一次访问该ContentProvider时,由系统进行调用
      //注:运行在ContentProvider进程的主线程,故不能做耗时操作
      public boolean onCreate()
    
      //得到数据类型,即返回当前Uri所代表数据的MIME类型
      public Stirng getType(Uri uri)
    
  • Android为常见的数据(通讯录、日程表)提供了内置默认的ContentProvider,但也可以根据需求自定义ContentProvider(重写上述6个方法)

  • ContentProvider类不会直接与外部进程进行交互,而是通过ContentResolver类

ContentResolver类
作用:
  • 统一管理不同ContentProvider间的操作

    1.通过URI即可操作不同的ContentProvider中的数据
    2.外部进程通过ContentResolver类从而与ContentProvider类进行交互

ContentResolver存在的原因:
  • 一般来说,一款应用要使用多个ContentProvider,若需要了解每个ContentProvider的不同实现从而完成数据的交互,操作成本高、难度大。所以在ContentProvider的基础上多加了一个ContentResolver类对所有ContentProvider进行统一管理。
使用:
  • ContentResolver类提供了与ContentProvider相同名字和作用的4个方法

    //外部进程向 ContentProvider 中添加数据
    public Uri insert(Uri uri,ContentValues values)
    
    // 外部进程 删除 ContentProvider 中的数据
    public int delete(Uri uri, String selection, String[] selectionArgs)
    
    // 外部进程更新 ContentProvider 中的数据
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)  
    
    // 外部应用 获取 ContentProvider 中的数据
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
    
  • 实例说明

    // 使用ContentResolver前,需要先获取ContentResolver
    // 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
    ContentResolver resolver =  getContentResolver();
    // 设置ContentProvider的URI
    Uri uri = Uri.parse("content://cn.scu.myprovider/user"); 
    // 根据URI 操作 ContentProvider中的数据
    // 此处是获取ContentProvider中 user表的所有记录 
    Cursor cursor = resolver.query(uri, null, null, null, "userid desc"); 
    
4.辅助工具类
ContentUris类
  • 作用:操作URI
  • 具体使用:
    两个核心方法:
    • withAppendedId() == 向URI追加一个id

    • parseId() == 从URI中获取id

      // withAppendedId()作用:向URI追加一个id
      Uri uri = Uri.parse("content://cn.scu.myprovider/user") 
      Uri resultUri = ContentUris.withAppendedId(uri, 7);  
      // 最终生成后的Uri为:content://cn.scu.myprovider/user/7
      
      // parseId()作用:从URL中获取ID
      Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") 
      long personid = ContentUris.parseId(uri); 
      //获取的结果为:7
      
UriMatcher类
  • 作用:
    1.在ContentProvider中注册URI
    2.根据URI匹配ContentProvider中对应的数据表

  • 使用
    步骤1:初始化UriMatcher对象
    步骤2:在ContentProvider 中注册URI( addURI() )
    步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源( match() )

    //步骤1:初始化UriMatcher对象
      UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); 
       //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码
      // 即初始化时不匹配任何东西
    
    // 步骤2:在ContentProvider 中注册URI(addURI())
      int URI_CODE_a = 1;
      int URI_CODE_b = 2;
      matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a); 
      matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b); 
      // 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
      // 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b
    
    // 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())
      @Override   
       public String getType(Uri uri) {   
        Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");   
    
        switch(matcher.match(uri)){   
         // 根据URI匹配的返回码是URI_CODE_a
         // 即matcher.match(uri) == URI_CODE_a
          case URI_CODE_a:   
          return tableNameUser1;   
        // 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
          case URI_CODE_b:   
          return tableNameUser2;
      // 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
      }   
    }
    
ContentObserver类
  • 定义:内容观察者

  • 作用:观察uri引起ContentProvider中的数据变化(当ContentProvider中的数据发生增、删、改时就会触发ContentObserver类)并通知外界(即 访问 该数据的访问者)

  • 使用:

    // 步骤1:注册内容观察者ContentObserver
         // 通过ContentResolver类进行注册,并指定需要观察的URI
        getContentResolver().registerContentObserver(uri);
      
    
    // 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
        public class UserContentProvider extends ContentProvider { 
          public Uri insert(Uri uri, ContentValues values) {
          db.insert("user", "userid", values);
          // 通知访问者
          getContext().getContentResolver().notifyChange(uri, null);
            }
          }
    
    // 步骤3:解除观察者
        // 同样需要通过ContentResolver类进行解除
        getContentResolver().unregisterContentObserver(uri);
    
5.实例使用

ContentProvider不仅可以用于进程之间的通信,也可以用于进程内的通信

  • 进程内通信
    步骤:

    1.创建数据库类
    2.自定义ContentProvider类(创建提供者)
    3.注册创建的ContentProvider类(注册提供者,在清单配置文件注册)
    4.访问ContentProvider类的数据

6.感谢

文章出处:简书大神 Carson_Ho
博客地址:https://www.jianshu.com/p/ea8bc4aaf057

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容