我要做 Android 之 ContentProvider

  1. 云栖社区>
  2. 博客>
  3. 正文

我要做 Android 之 ContentProvider

迎着风 2018-09-09 11:13:00 浏览836
展开阅读全文

相信大家对于 Android 顶顶大名的四大组件也不陌生了。今天我们来谈一谈 ContentProvider 的用法。什么东西都是基本最重要啊,可以人是一个健忘的动物,所以需要做下来笔记,常看常新。愿我们成为真实的自己。

ContentProvider概要

从系统提供的Provider访问数据

  • 内容URI的组成
  • ContentResolve类

创建自己的Provider

  • UriMater类
  • 自定义一个Provider的步骤

ContentProvider 也有存储数据的功能,但是与安卓自带的数据库和 SharedPreferences 以及文件存储方法不同的是,后者保存下的数据只能被该应用程序使用,而前者可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。所以组件ContentProvider主要负责存储和共享数据。

ContentProvider有两种形式:可以使用现有的内容提供者来读取和操作相应程序中的数据,也可以创建自己的内容提供者给这个程序的数据提供外部访问接口。

2.从系统提供的Provider访问数据

既然ContentProvider有对外共享数据的功能,换句话说,其他应用程序可以通过ContentProvider对应用中的数据进行增删改查,之前学习SQLite数据存储的时候就提到过可以实现增删改查的各种辅助性方法,实际上ContentProvider是对SQLiteOpenHelper的进一步封装,不过不再用单纯的表名指明被操作的表,毕竟现在是其他程序访问它,而是用有一定格式规范的内容URI来代替。下面先来学习URI的组成。

(1)URI的组成

以之前学习数据库的demo为例,它的包名是com.example.myapplication,如果其他程序想访问该程序student.db中的student表,那么需要的内容URI如图所示:

img_f657cc44bd416c90026e369c61c96a93.png
01.png

可以看出内容 URI 可以非常清楚地表达出我们想要访问哪个程序中哪张表里的数据,但还没完,还需要将它解析成 Uri 对象才可以作为参数传入。通过调用 Uri.parse()方法,就可以将内容 URI 字符串解析成 Uri 对象了,代码如下:

img_a8eeaa24cac2032bb96d0b616a03e4c1.png
02.png

Uri 这个类就是专门做这种链接转换的。其实这种链接方式和 http 很像。content:是不能变化的,后面就是包名.provider ,也是一定的,最后就是表名。

(2)ContentResolve类

现在有了酷似“表名”的Uri,类似的,在ContentResolver类中提供的一系列用于对数据进行增删改查操作的方法也酷似SQLiteDatabase的那些辅助性方法:insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。它们不仅方法名一样,连提供的参数都非常相似,见下图,红色部分是区别:


img_4d4cf861aa33725f87aa25f5d2e62666.png
03.png

所以其他程序若想要访问ContentProvider中共享的数据的方法是:

第一:通过 Context 中的 getContentResolver() 方法实例化一个 ContentResolver 对象。

第二:调用该对象的增删改查方法去操作 ContentProvider 中的数据。

下面我们来看看查询联系人数据的基本代码:

img_287e784cd53aa01c47dc56b91f344a74.png
04.png

别忘记在注册文件中声明权限

不但如此,Android 6.0 之后有了运行时权限,所以我们还要加上判断才可以。

img_1e93d8c211b436130456e7fcdb4c2d1b.png
05.png

这一部分是通用代码,大家可以在使用的过程中把这部分代码封装到 BaseActivity 中去。这样可以增加代码复用。


img_67bff96f5f76e85bf02d67996cd7948c.png
06.png

这是需要动态申请权限的图标,不过没关系,大家记不得可以去查找一下即可。

3.创建自己的Provider

(1)UriMater类

UriMater 类有匹配内容 URI 的功能,在这里常用它的两个方法:一个是 addURI() 方法来传入 URI,它接收三个参数(权限,路径,一个自定义代码);另一个是 match() 方法用来匹配 URI,接收一个 Uri 对象,返回值是某个能够匹配这个 Uri 对象所对应的自定义代码,利用这个自定义代码,就可以判断出调用方期望访问的是哪张表中的数据了。

(2)自定义一个Provider的步骤

步骤一:新建一个类去继承ContentProvider。
步骤二:重写ContentProvider的六个抽象方法,方法及含义如图:

public class MyProvider extends ContentProvider {
    // 在ContentProvider 创建后调用
    @Override
    public boolean onCreate() {
        return false;
    }
    //该方法用于提供外部应用从 ContentProvider 中获取数据
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }
    @Nullable
    @Override   //用于返回当前 Uri 代表的 MIME 类型
    public String getType(@NonNull Uri uri) {
        return null;
    }
    //该方法用于提供外部应用从 ContentProvider 中插入数据
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }
    //该方法用于提供外部应用从 ContentProvider 中删除数据
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
    //该方法用于提供外部应用从 ContentProvider 中修改数据
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

步骤三:在配置文件中进行注册,并注明属性:

android:authorities即Provider的权限,形式是包名.provider
android:name即Provider的全名,形式是包名.类名
android:exported="true"指明该Provider可被其它程序访问。

(3)例子:为student.db创建MyProvider

接下里还是给上篇的数据库demo创建一个自定义提供器MyProvider,然后在别的应用程序中通过MyProvider去操作student.db中的数据。

开始自定义提供器!一开始定义了四个常量,分别表示访问student表中的所有数据、访问student表中的单条数据(student/#用于表示student表中任意一行记录)、访问course表中的所有数据和访问course表中的单条数据。然后在静态代码块里对UriMatcher进行了初始化操作,将期望匹配的几种URI格式添加了进去。

img_9b25088574881b183d812601a3182e70.png
07.png

接下来就是六个抽象方法的具体实现了,先看onCreate()方法,这里创建了一个MyHelper的实例,然后返回true表示内容提供器初始化成功,现在数据库就已经完成了创建或升级操作。


img_db9ead0a81a0fe089d158406b1915b24.png
08.png

接下来是 getType()方法,需要返回一个MIME字符串。一个内容URI所对应的MIME字符串主要由三部分组分,Android对这三个部分做了以下格式规定:必须以vnd开头;如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/;最后接上vnd.< authority>.< path>。所以四个内容URI对应的MIME字符串分别是:

img_a48b3937e5a589c4f8e9f7bae84a664f.png
09.png

在query()方法里先获取到SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要访问哪张表,再调用SQLiteDatabase的query()进行查询并将Cursor对象返回就好了。注意当访问的是单条数据时调用了Uri对象的getPathSegments()方法,它会将内容URI权限之后的部分以“/”符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id了。得到了id之后,再通过selection和selectionArgs参数进行约束,就实现了查询单条数据的功能。

补充部分:事务有关:
事务是恢复和并发控制的基本单位。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性

原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(durability)。持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。


/**
     * SQLite的事务管理
     */
    public void transation() {
        SQLiteDatabase db = helper.getWritableDatabase();
        // 事务开始
        db.beginTransaction();
        //事务的处理
        try {
            db.execSQL("update person set age = age-1 where name = 'xiaohong'");
            db.execSQL("update person set age = age+1 where name = 'xiaolan'");
            //事务处理成功
            db.setTransactionSuccessful();
        } finally {
            //结束事务
            db.endTransaction();
        }
 
    }

当使用 sqlite 数据库批量插入数据的时候使用事务就可以提升效率。

在使用 sqlite 数据库的时候有那些可以优化的地方。

一:显示使用事务
Android中,无论是使用SQLiteDatabase的insert,delete等方法还是execSQL都开启了事务,来确保每一次操作都具有原子性,使得结果要么是操作之后的正确结果,要么是操作之前的结果。所以我们可以显示的支持事务,这样事务的操作打开次数会明显变少。

二:建立索引
创建索引的基本语法:

CREATE INDEX index_name ON table_name;

三:及时关闭Cursor

四:耗时异步化
数据库的操作,属于本地IO,通常比较耗时,如果处理不好,很容易导致ANR,因此建议将这些耗时操作放入异步线程中处理。

今天状态不够好,前途漫漫不知路在何方。只能一步一步向前走。
愿我们成为真实的自己。

网友评论

登录后评论
0/500
评论
迎着风
+ 关注