相信大家对于 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如图所示:
可以看出内容 URI 可以非常清楚地表达出我们想要访问哪个程序中哪张表里的数据,但还没完,还需要将它解析成 Uri 对象才可以作为参数传入。通过调用 Uri.parse()方法,就可以将内容 URI 字符串解析成 Uri 对象了,代码如下:
Uri 这个类就是专门做这种链接转换的。其实这种链接方式和 http 很像。content:是不能变化的,后面就是包名.provider ,也是一定的,最后就是表名。
(2)ContentResolve类
现在有了酷似“表名”的Uri,类似的,在ContentResolver类中提供的一系列用于对数据进行增删改查操作的方法也酷似SQLiteDatabase的那些辅助性方法:insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。它们不仅方法名一样,连提供的参数都非常相似,见下图,红色部分是区别:
所以其他程序若想要访问ContentProvider中共享的数据的方法是:
第一:通过 Context 中的 getContentResolver() 方法实例化一个 ContentResolver 对象。
第二:调用该对象的增删改查方法去操作 ContentProvider 中的数据。
下面我们来看看查询联系人数据的基本代码:
别忘记在注册文件中声明权限
不但如此,Android 6.0 之后有了运行时权限,所以我们还要加上判断才可以。
这一部分是通用代码,大家可以在使用的过程中把这部分代码封装到 BaseActivity 中去。这样可以增加代码复用。
这是需要动态申请权限的图标,不过没关系,大家记不得可以去查找一下即可。
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格式添加了进去。
接下来就是六个抽象方法的具体实现了,先看onCreate()方法,这里创建了一个MyHelper的实例,然后返回true表示内容提供器初始化成功,现在数据库就已经完成了创建或升级操作。
接下来是 getType()方法,需要返回一个MIME字符串。一个内容URI所对应的MIME字符串主要由三部分组分,Android对这三个部分做了以下格式规定:必须以vnd开头;如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/;最后接上vnd.< authority>.< path>。所以四个内容URI对应的MIME字符串分别是:
在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,因此建议将这些耗时操作放入异步线程中处理。
今天状态不够好,前途漫漫不知路在何方。只能一步一步向前走。
愿我们成为真实的自己。