我们需要了解ContentProvider最好的方式就是查看Android官方文档:
ContentProvider基础知识.
通常ContentProvider有两种使用场景
内容提供程序以一个或多个表的形式将数据呈现给外部应用,这些表与关系型数据库中的表类似。
行表示提供程序收集的某种类型数据的实例,行中的每一列表示为一个实例所收集的单个数据。
内容提供程序有助于应用管理其自身和其他应用所存储数据的访问,并提供与其他应用共享数据的方法。
内容提供程序是一种标准接口,可将一个进程中的数据与另一个进程中运行的代码进行连。通过配置内容提供程序,您可以使其他应用安全地访问和修改您的应用数据
访问内容提供程序中的数据,客户端可以使用应用的 Context 中的 ContentResolver 对象与提供程序进行通信。
ContentResolver 对象会与提供程序对象(即实现 ContentProvider 的类的实例)通信。
ContentResolver 方法可提供持久性存储空间的基本“CRUD”(创建、检索、更新和删除)功能。
1、为数据设计原始存储
内容提供程序以两种方式提供数据:
2、定义 ContentProvider 类及其所需方法的具体实现。
3、定义提供程序的授权字符串、该字符串的内容 URI 以及列名称。
一、内容URI
内容 URI 用来在提供程序中标识数据。内容 URI 包括整个提供程序的符号名称(其授权)和指向表的名称(路径)。
ContentResolver 对象会解析出 URI 的授权,并将该授权与已知提供程序的系统表进行比较,从而“解析”提供程序。
ContentProvider 使用内容 URI 的路径部分选择需访问的表。 通常,提供程序会为其公开的每个表显示一条路径。
content://user_dictionary/words
user_dictionary
字符串是提供程序的授权,words
字符串是表的路径。
字符串 content://(架构)始终显示,并且会将其标识为内容 URI。
二、设计URI
1、设计授权
提供程序通常拥有单一授权,该授权充当其 Android 内部名称。为避免与其他提供程序发生冲突,您应该使用互联网网域所有权(反向)作为提供程序授权的基础。
例如:如果您的 Android 软件包名称为 com.example.appname,则应为提供程序提供 com.example.appname.provider 授权。
2、设计路径结构
通常,开发者会追加指向单个表格的路径,从而根据权限创建内容 URI。
例如,如果您有 table1 和 table2 两个表格,则可以通过合并上一示例中的授权来生成内容 URI
com.example.appname.provider/table1 和 com.example.appname.provider/table2。
3、处理内容URI ID
按照约定,提供程序会接受末尾拥有行 ID 值的内容 URI,进而提供对表内单个行的访问。
同样按照约定,提供程序会将该 ID 值与表的 _ID 列进行匹配,并对匹配的行执行请求访问。
4、内容 URI 模式
为帮助您选择对传入的内容 URI 执行的操作,提供程序 API 加入了便利类 UriMatcher,它会将内容 URI“模式”映射为整型值。您可以在 switch 语句中使用这些整型值,为匹配特定模式的一个或多个内容 URI 选择所需操作。
4.1、内容 URI 模式使用以下通配符匹配内容 URI
4.2、以设计和编码内容 URI 处理为例,假设某个拥有授权 com.example.app.provider 的提供程序能识别以下指向表的内容 URI
提供程序也可识别追加了行 ID 的内容 URI:
content://com.example.app.provider/table3/1:对应 table3 中的 1 所标识行的内容 URI。
4.3、可以使用以下内容 URI 模式
以下代码段展示了 UriMatcher 中方法的工作方式。
代码采用不同方式来处理整个表的 URI 与单个行的 URI,它为整张表使用的内容 URI 模式是 content://
,为单个行使用的内容 URI 模式则是 content://
。
addURI() 方法会将授权和路径映射为整型值。match() 方法会返回 URI 的整型值。switch 语句会根据match方法返回的整型值选择查询整个表或者查询表中单个记录
举例:跟后续的代码无关
// 1. Creates a UriMatcher object.
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 2. 添加URI到UriMatcher
static {
// 访问table3整个表的内容,将1跟跟整个表的URI进行映射
uriMatcher.addURI("com.example.app.provider", "table3", 1);
// 访问table3中单个记录,#匹配由任意长度的数字字符组成的字符串
// 将数字2跟单个记录访问的URI进行映射
uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
}
// 3. 对比Uri,然后进行各自正确的操作
switch (uriMatcher.match(uri)) {
case 1:
if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
break;
case 2:
selection = selection + "_ID = " + uri.getLastPathSegment();
break;
default:
// 如果条件不满足可以再次抛出异常提醒调用者
}
ContentProvider 实例会处理其他应用发送的请求,从而管理对结构化数据集的访问。
所有形式的访问最终都会调用 ContentResolver,后者接着通过调用 ContentProvider 的具体方法来获取访问权限。
抽象类 ContentProvider 定义了六个抽象方法,您必须将其作为具体子类的一部分加以实现。
query()
从提供程序中检索数据。使用参数选择要查询的表、要返回的行和列以及结果的排序顺序。将数据作为 Cursor 对象返回。
query() 参数 | SELECT 关键字/参数 | 备注 |
---|---|---|
Uri | FROM table_name | Uri 映射至提供程序中名为 table_name 的表。 |
projection | col,col,col,… | projection 是检索到的每个行所应包含的列的数组。 |
selection | WHERE col = value | selection 指定选择行的条件。 |
selectionArgs | (没有完全等效项,选择参数会替换选择子句中的 ? 占位符。) | |
sortOrder | ORDER BY col,col,… | sortOrder 指定在返回的 Cursor 中各行的显示顺序。 |
insert()
在提供程序中插入新行。使用参数选择目标表并获取要使用的列值。返回新插入行的内容 URI。
update()
更新提供程序中的现有行。使用参数选择要更新的表和行,并获取更新后的列值。返回已更新的行数。
delete()
从提供程序中删除行。使用参数选择要删除的表和行。返回已删除的行数。
getType()
返回内容 URI 对应的 MIME 类型。
onCreate()
初始化提供程序。创建提供程序后,Android 系统会立即调用此方法。请注意,只有在 ContentResolver 对象尝试访问您的提供程序时,系统才会创建它。
注意事项:
Android 系统会在启动提供程序时调用 onCreate()。
此方法中你应该只执行快速运行的初始化任务,并将数据库创建和数据加载推迟到提供程序实际收到数据请求时进行。
如果您在 onCreate() 中执行冗长的任务,则会减慢提供程序的启动速度。反之,这将减慢提供程序对其他应用的响应速度。
ContentProvider.query() 方法必须返回 Cursor 对象,如果失败,系统会抛出 Exception。
果您使用 SQLite 数据库作为数据存储,则只需返回由 SQLiteDatabase 类的某个 query() 方法返回的 Cursor。
如果查询不匹配任何行,则您应该返回一个 Cursor 实例(其 getCount() 方法返回 0)。只有当查询过程中出现内部错误时,您才应该返回 null。
Android 系统必须能够跨进程边界传达 Exception。
insert() 方法会使用 ContentValues 参数中的值,向相应表中添加新行。如果 ContentValues 参数中未包含列名称,可能希望在提供程序代码或数据库模式中提供其默认值。
此方法应返回新行的内容 URI。如要构造此方法,请使用 withAppendedId() 向表的内容 URI 追加新行的 _ID(或其他主键)值。
update() 方法与 insert() 采用相同的 ContentValues 参数,并且该方法与 delete() 及 ContentProvider.query() 采用相同的 selection 和 selectionArgs 参数。
delete() 方法无需从您的数据存储中实际删除行。 如果您将同步适配器与提供程序一起使用,则应考虑为已删除的行添加“删除”标志,而不是完全移除行。同步适配器可以检查是否存在已删除的行,并将这些行从服务器中移除,然后再将其从提供程序中删除。
ContentProvider 类拥有两个返回 MIME 类型的方法:
getType():任何ContentProvider程序都必须实现。
getStreamTypes():当提供程序提供文件时,系统要求您实现的方法。
对于指向一行或多行表数据的内容 URI,getType() 应该以 Android 供应商特有的 MIME 格式返回 MIME 类型:
类型部分:vnd
子类型部分:
提供程序特有部分:vnd..
和
。
值应具有全局唯一性,
值应在对应的 URI 模式中具有唯一性。适合选择贵公司的名称或应用 Android 软件包名称的某个部分作为
。适合选择 URI 关联表的标识字符串作为
。如果提供程序的授权是 com.example.app.provider(
),并且它公开了名为 table1(
) 的表。
类型.子类型部分/内容提供者特有部分
// 类型.子类型部分/内容提供者特有部分
vnd.android.cursor.dir/vnd.com.example.provider.table1
vnd.android.cursor.item/vnd.com.example.provider.table1
代码如下(示例)
@Override
public String getType(@NonNull Uri uri) {
int match = sUriMatcher.match(uri);
switch (match) {
// 1. 必须以vnd开头
// 2. 如果内容URI以路径结尾,则后接android.cursor.dir/,
// 如果内容URI以id结尾,则后接android.cursor.item/
// 3. 最后接上vnd..
case GESTURE_DIR:
return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".gesture";
case GESTURE_ITEM:
return "vnd.android.cursor.item/vnd." + AUTHORITY + ".gesture";
default:
throw new IllegalArgumentException(String.format("Unknown URI: %s", uri));
}
}
image/*(任何“图像”内容)
,则 ContentProvider.getStreamTypes() 方法应返回数组:{ "image/jpeg", "image/png", "image/gif"}
*\/jpeg
,并且 ContentProvider.getStreamTypes() 应返回:{"image/jpeg"}
android官网提供了ContentProvider:实现内容提供程序权限
简单的讲一下常用的两个(更多的内容请查看上方提供的官网链接):
统一的读写提供程序级权限
元素的 android:permission 属性指定)。例如:
com.example.app.provider.permission.READ_PROVIDER
单独的读写提供程序级权限
针对整个提供程序的读取权限和写入权限。您可以通过
元素的 android:readPermission
属性和 android:writePermission
属性指定这些权限。这些权限优先于 android:permission 所需的权限。
还有路径级权限和临时权限请查看:实现内容提供程序权限
授权 (android:authorities)
用于在系统内标识整个ContentProvider的符号名称。
提供程序类名 ( android:name )
实现的ContentProvider的完整路径。
例如:com.example.app.provider.MyContentProvider
权限: 指定其他应用访问提供程序数据时所须的权限
启动和控制属性
信息属性: 提供程序的可选图标和标签:
例如在AndroidManifest.xml中配置
<provider
android:name="com.example.app.MyContentProvider"
android:authorities="com.example.app.provider"
android:enabled="true"
android:exported="true"
android:permission="com.example.app.provider.permission.READ_PROVIDER" />
package com.google.mediapipe.examples.hands.service;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.annotation.Nullable;
public class GestureSqliteHelper extends SQLiteOpenHelper {
private static final String CREATE_GESTURE = "create table if not exists Gesture ("
+ "id integer primary key autoincrement,"
+ "gesture_switch integer,"
+ "gesture_result integer)";
public GestureSqliteHelper(@Nullable Context context, @Nullable String name,
@Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_GESTURE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
package com.google.mediapipe.examples.hands.service;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
public class GestureContentProvider extends ContentProvider {
private static final String TAG = "GestureContentProvider";
private static final String DB_NAME = "Hands.db";
private static final String TABLE_GESTURE = "Gesture";
private static final int GESTURE_DIR = 0;
private static final int GESTURE_ITEM = 1;
private static final String AUTHORITY = "com.google.mediapipe.examples.hands.provider";
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "gesture", GESTURE_DIR);
sUriMatcher.addURI(AUTHORITY, "gesture/#", GESTURE_ITEM);
}
private GestureSqliteHelper mDbHelper;
@Override
public boolean onCreate() {
Context context = getContext();
if (context == null) {
return false;
}
mDbHelper = new GestureSqliteHelper(context, DB_NAME, null, 1);
return true;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
int match = sUriMatcher.match(uri);
switch (match) {
// 1. 必须以vnd开头
// 2. 如果内容URI以路径结尾,则后接android.cursor.dir/,
// 如果内容URI以id结尾,则后接android.cursor.item/
// 3. 最后接上vnd..
case GESTURE_DIR:
return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".gesture";
case GESTURE_ITEM:
return "vnd.android.cursor.item/vnd." + AUTHORITY + ".gesture";
default:
throw new IllegalArgumentException(String.format("Unknown URI: %s", uri));
}
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
// 查询数据
SQLiteDatabase database = mDbHelper.getReadableDatabase();
Cursor cursor = null;
switch (sUriMatcher.match(uri)) {
case GESTURE_DIR:
cursor = database.query(TABLE_GESTURE, projection, selection, selectionArgs,
null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
break;
case GESTURE_ITEM:
List<String> segments = uri.getPathSegments();
if (segments.size() < 2) {
return null;
}
String gestureId = segments.get(1);
cursor = database.query(TABLE_GESTURE, projection, "id=?", new String[]{gestureId},
null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
break;
}
return cursor;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
SQLiteDatabase database = mDbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (sUriMatcher.match(uri)) {
case GESTURE_DIR:
case GESTURE_ITEM:
long gestureId = database.insert(TABLE_GESTURE, null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/gesture/" + gestureId);
getContext().getContentResolver().notifyChange(uriReturn, null);
break;
}
return uriReturn;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
SQLiteDatabase database = mDbHelper.getWritableDatabase();
int deleteRows = 0;
switch (sUriMatcher.match(uri)) {
case GESTURE_DIR:
deleteRows = database.delete(TABLE_GESTURE, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
break;
case GESTURE_ITEM:
List<String> segments = uri.getPathSegments();
if (segments.size() < 2) {
return deleteRows;
}
String gestureId = segments.get(1);
deleteRows = database.delete(TABLE_GESTURE, "id=?", new String[]{gestureId});
getContext().getContentResolver().notifyChange(uri, null);
break;
}
return deleteRows;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
SQLiteDatabase database = mDbHelper.getWritableDatabase();
int updateRows = 0;
switch (sUriMatcher.match(uri)) {
case GESTURE_DIR:
updateRows = database.update(TABLE_GESTURE, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
break;
case GESTURE_ITEM:
List<String> segments = uri.getPathSegments();
if (segments.size() < 2) {
return updateRows;
}
String gestureId = segments.get(1);
updateRows = database.update(TABLE_GESTURE, values, "id=?", new String[]{gestureId});
getContext().getContentResolver().notifyChange(uri, null);
break;
}
return updateRows;
}
}
常量URI和字段名
private static final String TAB_NAME = "gesture";
private static final String AUTHORITY = "com.google.mediapipe.examples.hands.provider";
public static final Uri URI_GESTURE_SWITCH_DIR = Uri.parse("content://" + AUTHORITY + "/" + TAB_NAME);
public static final Uri URI_GESTURE_SWITCH_ITEM = Uri.parse("content://" + AUTHORITY + "/" + TAB_NAME + "/1");
// 数据库字段名
public static final String COLUMN_ID = "id";
public static final String COLUMN_GESTURE_SWITCH = "gesture_switch";
通过ContentProvider插入数据
// 通过ContentProvider插入数据
private void providerInsert() {
runDatabase(() -> {
ContentValues values = new ContentValues();
// 数据库字段名+当前字段值
values.put(COLUMN_GESTURE_SWITCH, 0);
Uri insertUri = getContentResolver().insert(URI_GESTURE_SWITCH_DIR, values);
Log.d(TAG, "providerInsert:: " + insertUri);
});
}
通过ContentProvider更新数据
private void providerUpdate() {
runDatabase(() -> {
ContentValues values = new ContentValues();
values.put(COLUMN_GESTURE_SWITCH, 0);
int updateId = getContentResolver().update(URI_GESTURE_SWITCH_ITEM, values, null,
null);
Log.d(TAG, "providerUpdate:: " + updateId);
});
}
通过ContentProvider查询数据
private void providerQuery() {
runDatabase(() -> {
Cursor cursor = getContentResolver().query(URI_GESTURE_SWITCH_DIR, null, null,
null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID));
int switchStatus = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_GESTURE_SWITCH));
Log.d(TAG, "providerQuery id:: " + id + " ,switchStatus:: " + switchStatus);
}
cursor.close();
}
});
}
通过ContentProvider删除数据
private void providerDelete() {
runDatabase(() -> {
int deleteId = getContentResolver().delete(URI_GESTURE_SWITCH_ITEM, null, null);
Log.d(TAG, "providerDelete:: " + deleteId);
});
}
以上就是本文要讲的内容,本文仅仅是简单的介绍了ContentProvider的概念和使用,而需要了解ContentProvider更多的用法可以查看Android官方文档。
ContentProvider创建已经用法.
参考:
ContentProvider讲解
Android - 内容提供者(Content Provider)