Content Provider
MoMo Lv5
  1. 什么是Content Provider
  2. ContentProvider 是如何实现数据共享的(原理)
  3. 说ContentProvider、ContentResolver、ContentObserver 之间的关系
  4. 如何创建自己应用的内容提供者的使用场景。
  5. ContentProvider的权限管理。
  6. 为什么要使用通过ContentResolver类从而与ContentProvider类进行交互,而不直接访问ContentProvider类
  7. ContentProvider的底层是采用Android中的Binder机制,既然已经有了binder实现了进程间通信了为什么还会需要contentProvider

什么是内容提供者

之前有说过可以用Intent在组件中传递数据,那么其数据的大小是否有限制呢?很明显是有限制的,Intent传递数据大小的限制大概在1M左右,超过这个限制就会静默崩溃。因此我们就可以通过ContentProvider进行进程间的数据传递,也就是ContentProvider是一种进程间的数据传递的方式。 一般来说,Android数据存储的方式有:文件,数据库,网络,SharePreferences,ContentProvider。

imageContentProvider更准确来说只是一个中间者的身份,真正存储数据的是数据库和文件等形式

ContentProvider 使用方法

ContentResolver和url

介绍ContentProvider的使用,就需要先了解ContentResolver和url。 url是统一资源标识符。ContentProvider使用表的形式来组织数据,无论数据的来源是什么,ConentProvider 都会认为是一种表,然后把数据组织成表格。因此就需要一个url来定位需要操作的是哪个数据

url

自定义url的组成图:

image

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

1
2
3
4
5
6
7
8
9
10
11
12
// 设置URI
Uri uri = Uri.parse("content://com.carson.provider/User/1")
// 上述URI指向的资源是:名为 `com.carson.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据

// 特别注意:URI模式存在匹配通配符* & #

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

ContentProvider

ContentProvider主要以表格的形式组织数据,同时也支持文件数据,只是表格形式用得比较多

每个表格中包含多张表,每张表包含行 & 列,分别对应记录 & 字段,同数据库

进程间共享数据的本质是:添加、删除、获取 & 修改(更新)数据
所以ContentProvider的核心方法也主要是上述4个作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/** 4个核心方法**/
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) 
// 外部应用 获取 ContentProvider 中的数据

// 注:
// 1. 上述4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)
// 2. 存在多线程并发访问,需要实现线程同步
// a. 若ContentProvider的数据存储方式是使用SQLite & 一个,则不需要,因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步
// b. 若ContentProvider的数据存储方式是内存,则需要自己实现线程同步

/** 2个其他方法**/
public boolean onCreate()
// ContentProvider创建后 或 打开系统后其它进程第一次访问该ContentProvider时 由系统进行调用
// 注:运行在ContentProvider进程的主线程,故不能做耗时操作

public String getType(Uri uri)
// 得到数据类型,即返回当前 Url 所代表数据的MIME类型

ContentResolver

用于统一管理不同 ContentProvider间的操作

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

一般来说,一款应用要使用多个ContentProvider,若需要了解每个ContentProvider的不同实现从而再完成数据交互,操作成本高 & 难度大
所以再ContentProvider类上加多了一个 ContentResolver类对所有的ContentProvider进行统一管理。

使用ContentProvider在两个进程进行数据传递

  1. 创建数据库类
  2. 自定义 ContentProvider 类
  3. 注册 创建的ContentProvider类
  4. 进程内访问ContentProvider的数据

进程A:

  1. 创建数据库类:MyDBHelper,该类主要完成数据库创建和对应表格的创建;

    1
    2
    3
    4
    5
    6
    7
    8
    public class DBHelper extends SQLiteOpenHelper {
    ...
    @Override
    public void onCreate(SQLiteDatabase db) {
    // 创建两个表格:用户表和兴趣表
    }
    ...
    }
  2. 实现自定义MyProvider类,继承ContentProvider,并重写增删改查接口;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class MyProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
    // 在onCreate对数据库进行初始化
    return true;
    }

    /**
    * 添加数据
    */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
    // 根据url找到需要操作的表格
    String table = getTableName(uri);

    // 向该表添加数据
    db.insert(table, null, values);

    // 当该URI对应的ContentProvider类里面的数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
    mContext.getContentResolver().notifyChange(uri, null);
    return uri;
    }
    ...
    }
  3. 注册自定义MyProvider类:在Manifest中声明它的Uri和权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <provider 
    android:name="MyProvider"
    android:authorities="com.xurui.myprovider"

    // 声明外界进程可访问该Provider的权限 &
    android:permission="com.xurui.PROVIDER"

    // 设置此provider是否可以被其他进程使用
    android:exported="true"
    android:enabled="true"
    />

进程B

  1. 在Manifest声明可访问的权限

    1
    2
    // 声明本应用可允许通信的权限(全权限),需要和进程A保持一致
    <uses-permission android:name="com.xurui.PROVIDER"/>
  2. 访问MyProvider,进行增加数据操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 设置URI
    Uri uri_user = Uri.parse("content:/com.xurui.myprovider/user");

    // 插入表中数据
    ContentValues values = new ContentValues();
    values.put("_id", 1);
    values.put("name", "CVTE_Jordan");


    // 获取ContentResolver
    ContentResolver resolver = getContentResolver();
    // 通过ContentResolver 根据URI 向ContentProvider中插入数据
    resolver.insert(uri_user,values);
    }
    }

ContentProvider的权限管理(读写分离,权限控制-精确到表级,URL控制)

对于由ContentProvider公开出来的数据,它应该是存储在应用内存中的数据。而对于一些存储在外存上的数据,对于ContentProvider,需要在AndroidManifest.xml文件中配置节点的属性,来实现权限控制。通常使用一些属性设置:

  • android:grantUriPermssions:临时许可标志。
  • android:permission:Provider读写权限。
  • android:readPermission:Provider的读权限。
  • android:writePermission:Provider的写权限。
  • android:enabled:标记允许系统启动Provider。
  • android:exported:标记允许其他应用程序使用这个Provider。
  • android:multiProcess:标记允许系统启动Provider相同的进程中调用客户端。

说说ContentProvider、ContentResolver、ContentObserver 之间的关系?

在我个人开发中,后面两个类反而比ContentProvider用的更多,来看看其联系:

  • ContentProvider:内容提供者,主要作用就是管理数据,比如最常见的增删改查操作,同时为这些数据的访问提供了统一的接口,实现进程间的数据传递和共享;
  • ContentResolver:内容解析者,ContentResolver可以为不同URI操作不同的ContentProvider中的数据,外部进程可以通过ContentResolver与ContentProvider进行交互。
  • ContentObserver:内容观察者,观察ContentProvider中的数据变化,有变化的时候执行特定操作。本人用的最多的是监听Settings数据库的变化。由于ContentObserver的生命周期不同步于Activity和Service等,因此,在不需要时,需要手动的调用unregisterContentObserver()去取消注册。
1
2
3
4
5
6
//注册smsContentObserver用于监听短信数据库变化
if (smsContentObserver != null) {
getContentResolver().registerContentObserver(Uri.parse("content://sms"),
true, smsContentObserver);// 注册监听短信数据库的变化
Log.i(TAG, "注册监听短信数据库的变化");
}

ContentProvider 实现原理

ContentProvider的底层采用了Binder机制,后续文章会普及Binder机制,这里可以理解为一种Android跨进程通讯的机制,简单的用法是进程A可以通过Binder获得进程B的本地代理,通过本地代理,就可以在进程A里面的调用进程B的方法。在ContentProvider的实现原理中,通过ContentResolver可以查找对应给定Uri的ContentProvider,返回对应的本地代理 BinderProxy,通过这个BinderProxy就可以调用insert、delete接口。

ContentProvider的底层是采用Android中的Binder机制,既然已经有了binder实现了进程间通信了为什么还会需要contentProvider?

原因1:从架构层次看

一个软件平台至少由业务层、数据访问层、数据层构成。其中业务层就是一系列的App,数据层就是数据库,文件,网络等存储方式。那么为了降低业务层和数据层的耦合程度,我们希望数据访问层也可以有一个个独立的组件,对业务层提供统一调用接口,对数据层可以针对不同数据存储类型有不同的实现方式。这样业务层不需要关心下层的具体实现,只需要按约定好的标准接口去增删改查即可。这个独立的组件就是ContentProvider。

原因2:从传输效率看

不同的进程可以通过Binder、Intent去传输数据,但如果数据量大的时候,就都不适用了。而ContentProvider进行数据传输的方式是采用匿名共享内存机制,众所周知,共享内存可以高效地传递大量数据。因此,结合了Binder机制和匿名共享内存机制的ContentProvider就更加适合不同进程间传递数据。

总结

image

Powered by Hexo & Theme Keep
Unique Visitor Page View