- 一、创建 ContentProvider
- Uri 参数:
- getType() 方法中返回的 MIME 类型
- 解析请求内容的 Uri
- UriMatcher 类
- 二、代码示例
- 安全性
- 三、实现跨程序数据共享
- 1、创建 DatabaseProvider
- 2、创建 ProviderTest
- 布局文件
- 逻辑代码
- 3、运行 ProviderTest
- 解决运行报错
- 运行结果:
一、创建 ContentProvider
-
创建类继承
ContentProvider
-
重写继承的 6 个抽象方法
onCreate()
// 初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等 *** 作,
// 返回true表示内容提供器初始化成功,返回false则表示失败。
@Override
public boolean onCreate() {
return false;
}
query()
/**
* 从内容提供器中查询数据
*
* @param uri uri参数来确定查询哪张表
* @param projection 用于确定查询哪些列
* @param seletion 用于约束查询哪些行
* @param selectionArgs
* @param sortOrder 用于对结果进行排序
* @return 查询的结果存放在Cursor对象中返回
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {}
insert()
/**
* 向内容提供器中添加一条数据
*
* @param uri uri参数来确定要添加到的表
* @param contentValues 待添加的数据保存在values参数中
* @return 返回一个用于表示这条新记录的URI
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
delete()
/**
* 从内容提供器中删除数据
*
* @param uri uri参数来确定删除哪一张表中的数据
* @param selection 用于约束删除哪些行
* @param selectionArgs
* @return 被删除的行数将作为返回值返回
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
update()
/**
* 更新内容提供器已有的数据
*
* @param uri 使用 uri 参数来确定更新哪一张表中的数据
* @param contentValues 新数据保存在values参数中
* @param selection 用于约束更新哪些行
* @param selectionArgs 用于约束更新哪些行
* @return 受影响的行数将作为返回值返回
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
getType()
/**
* 根据传入的内容 URI 来返回相应的 MIME 类型。
* MIME 类型:
* 例如:
* 1、Uri 为 cotent://com.example.app.provider/table1
* 则对应的 MIME 类型为:vdn.android.cursor.dir/vdn.com.example.app.provider.table1
*
* 2、Uri 为 cotent://com.example.app.provider/table1/1
* 则对应的 MIME 类型为:vdn.android.cursor.item/vdn.com.example.app.provider.table1
*
* @param uri
* @return 返回 MIME 类型
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
Uri 参数:
相当于数据库 CRUD *** 作中传入的表名,指明要访问哪个程序的哪个数据库文件
这个参数由两个部分组成 authority
和 path
authority
用于对不同应用程序进行区分,为避免冲突,一般用应用程序包名path
对同一应用程序中不同的表进行区分,添加到authority
的后面- 字符串头部添加协议声明,以区分普通字符串和内容 Uri 字符串
示例:
1、路径结尾:表示调用方期望访问该表中的全部数据
"content://com.example.app.provider/table1"
"content://com.example.app.provider/table2"
2、id 结尾:表示调用方期望访问该表中拥有相应 Id 的数据
"content://com.example.app.provider/table1/1"
可见,内容URI可以非常清楚地表达出我们想要访问哪个程序中哪张表里的数据。也正是因此,ContentResolver中的增删改查方法才都接收Uri对象作为参数,因为如果使用表名的话,系统将无法得知我们期望访问的是哪个应用程序里的表。
示例中的字符串目前也还只是单纯的字符串而已,要经过解析才能成为真正的 Uri 对象进行参数传递。
解析字符串:
Uri uri = Uri.prase("content://com.example.app.provider/table1");
getType() 方法中返回的 MIME 类型
- 该方法根据传入的内容
URI
来返回相应的MIME
类型。 - 不同于
URI
authority / path,MIME
类型以 authority . path 结尾
1、路径结尾的内容 URI
例如:
content://com.example.app.provider/table1
对应的 MIME 类型为:
"vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
1、Id 结尾的内容 URI
例如:
content://com.example.app.provider/table1/1
对应的 MIME 类型为:
"vnd.android.cursor.item/vnd.com.example.app.provider.table1"
解析请求内容的 Uri
- 创建我们自己的
ContentProvider
需要对调用方传过来的uri
参数进行解析,从中分析出调用方希望访问的表。
1、解析 路径 结尾格式的内容 Uri,使用通配符 *,能够匹配任意表的内容 URI 格式
"content://com.example.app.provider/*"
或 "content://com.example.app.provider"
2、解析 Id 结尾格式的内容 Uri,使用通配符 #,匹配 table1 表中任意一行数据的内容 URI 格式
"content://com.example.app.provider/table1/#"
UriMatcher 类
实现匹配内容 Uri 的功能
addURI()
方法
- 保存可以匹配的 Uri 类型
- 3 个参数:
authority
、path
和自定义代码
(自定义代码用于和传入的 URI 进行匹配,匹配成功则返回对应类型的自定义代码)
// 访问 table1 表中所有的数据
public static final int TABLE1_DIR = 0;
// 访问 table1 表中的单条数据
public static final int TABLE1_ITEM = 1;
// 访问 table2 表中所有的数据
public static final int TABLE2_DIR = 2;
// 访问 table2 表中的单条数据
public static final int TABLE2_ITEM = 3;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
}
match()
方法
- 用于和已保存的可解析的
URI
格式进行匹配,即addURI()
方法中添加的那些URI
,匹配成功则返回addURI()
中对应条目的自定义代码,然后我们就知道调用方具体想访问什么数据。 - 常在增删改查中使用
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
// 对传入的 uri 进行匹配,update(), insert(), delete(),相似
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询 table1 表中所有的数据
break;
case TABLE1_ITEM:
// 查询 table1 表的单条数据
break;
case TABLE2_DIR:
// 查询 table1 表中所有的数据
break;
case TABLE2_ITEM:
// 查询 table1 表中的单条数据
break;
default:
break;
}
return null;
}
二、代码示例
public class MyProvider extends ContentProvider {
// 访问 table1 表中所有的数据
public static final int TABLE1_DIR = 0;
// 访问 table1 表中的单条数据
public static final int TABLE1_ITEM = 1;
// 访问 table2 表中所有的数据
public static final int TABLE2_DIR = 2;
// 访问 table2 表中的单条数据
public static final int TABLE2_ITEM = 3;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
}
// 初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等 *** 作,
// 返回true表示内容提供器初始化成功,返回false则表示失败。
@Override
public boolean onCreate() {
return false;
}
/**
* 从内容提供器中查询数据
*
* @param uri uri参数来确定查询哪张表
* @param projection 用于确定查询哪些列
* @param seletion 用于约束查询哪些行
* @param selectionArgs
* @param sortOrder 用于对结果进行排序
* @return 查询的结果存放在Cursor对象中返回
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
// 对传入的 uri 进行匹配,update(), insert(), delete(),相似
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询 table1 表中所有的数据
break;
case TABLE1_ITEM:
// 查询 table1 表的单条数据
break;
case TABLE2_DIR:
// 查询 table1 表中所有的数据
break;
case TABLE2_ITEM:
// 查询 table1 表中的单条数据
break;
default:
break;
}
return null;
}
/**
* 根据传入的内容 URI 来返回相应的 MIME 类型。
* MIME 类型:
* 例如:
* 1、Uri 为 cotent://com.example.app.provider/table1
* 则对应的 MIME 类型为:vdn.android.cursor.dir/vdn.com.example.app.provider.table1
*
* 2、Uri 为 cotent://com.example.app.provider/table1/1
* 则对应的 MIME 类型为:vdn.android.cursor.item/vdn.com.example.app.provider.table1
*
* @param uri
* @return 返回 MIME 类型
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 返回 MIME 字符串
return "vnd.android.cursor.dir/vdn.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vdn.android.cursor.item/vdn.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vdn.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vdn.android.cursor.item/vdn.com.example.app.provider.table2";
default:
break;
}
return null;
}
/**
* 向内容提供器中添加一条数据
*
* @param uri uri参数来确定要添加到的表
* @param contentValues 待添加的数据保存在values参数中
* @return 返回一个用于表示这条新记录的URI
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
/**
* 从内容提供器中删除数据
*
* @param uri uri参数来确定删除哪一张表中的数据
* @param selection 用于约束删除哪些行
* @param selectionArgs
* @return 被删除的行数将作为返回值返回
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
/**
* 更新内容提供器已有的数据
*
* @param uri 使用 uri 参数来确定更新哪一张表中的数据
* @param contentValues 新数据保存在values参数中
* @param selection 用于约束更新哪些行
* @param selectionArgs 用于约束更新哪些行
* @return 受影响的行数将作为返回值返回
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
安全性
- 所有的 CRUD *** 作都必须要匹配到想要获取内容的
应用
所提供的内容 URI 格式才能进行。 - 自己创建的内容提供器不要添加隐私数据的 URI,即存放了隐私数据的数据库,这样外部程序就访问不到这些隐私数据。
三、实现跨程序数据共享 1、创建 DatabaseProvider
我们在之前的 DatabaseTest 项目中创建 Provider。
右键 -> new -> Others -> ContentProvider,输入类名,指定该内容提供器的 authority,标识该程序。
点击 finish 后会生成 DatabaseProvider.java
文件,IDE 已经自动帮我们继承了 ContentProvider
类以及导入了该类的 6 个抽象方法。同时AndroidManifest.xml
文件中自动声明了该 ContentProvider
。authorities
对应创建时填入的值。enabled
及exported
属性对应了创建时勾选的两个选项。
<application>
...
<provider
android:name="com.example.contentprovider.DatabaseProvider"
android:authorities="com.example.databasetest.provider"
android:enabled="true"
android:exported="true">
provider>
application>
DatabaseProvider
的代码:
package com.example.contentprovider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import com.example.databasetest.MyDatabaseHelper;
public class DatabaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
public static final String AUTHORITY = "com.example.databasetest.provider";
private static UriMatcher sUriMatcher;
private MyDatabaseHelper mDbHelper;
// 初始化 ContentProvider 可供访问的数据,以 URI 形式封装进 UriMatcher 中,
// 以便其他程序访问数据时利用 UriMatcher 进行 match ,得到返回的自定义代码,我们就知道其他程序想要访问什么数据了,
// 并执行相应的 *** 作
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
sUriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
sUriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
public DatabaseProvider() {
}
/**
* 在此创建或升级数据库
*
* @return 返回 true 表示内容提供器初始化成功,这时数据库完成了创建和升级 *** 作
*/
@Override
public boolean onCreate() {
// Implement this to initialize your content provider on startup.
mDbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
/**
* 1、先获取到 SQLiteDatabase 的实例
* 2、根据传入的 uri 参数,利用 UriMatcher.match() 判断出用户想要访问哪张表
* uri.getPathSegments().get(1); 方法
* 此方法会对 uri 参数 authority 之后的部分,”/“符号进行分割,分割结果会放到一个字符串列表中,
* 所以这个列表的第 0 个位置存储的是路径(数据库),第 1 个位置存放的是 id
* 3、如果传入的是 id 则根据 id 查询单条数据
*
* @param uri
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return 返回 Cursor 对象
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Implement this to handle query requests from clients.
// 查询数据
SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor cursor = null;
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("Book", projection, "id = ?", new String[]{bookId}, null, null, sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("Category", projection, "id = ?", new String[]{categoryId}, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
// 返回删除的行数
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
// 删除数据
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int deleteRaws = 0;
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
deleteRaws = db.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRaws = db.delete("Book", "id = ?", new String[]{bookId});
break;
case CATEGORY_DIR:
deleteRaws = db.delete("Category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deleteRaws = db.delete("Category", "id = ?", new String[]{categoryId});
break;
default:
break;
}
return deleteRaws;
}
// 返回一个能表示这条新增数据的 URI,插入的 id 结尾
@Override
public Uri insert(Uri uri, ContentValues values) {
// Implement this to handle requests to insert a new row.
// 添加数据
SQLiteDatabase db = mDbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
break;
default:
break;
}
return uriReturn;
}
// 返回受影响的行数
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// Implement this to handle requests to update one or more rows.
// 更新数据
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int updateRows = 0;
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
updateRows = db.update("Book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updateRows = db.update("Book", values, "id = ?", new String[]{bookId});
break;
case CATEGORY_DIR:
updateRows = db.update("Category", values, selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updateRows = db.update("Category", values, "id = ?", new String[]{categoryId});
break;
default:
break;
}
return updateRows;
}
// 返回 uri 对应的 MIME 类型字符串
@Override
public String getType(Uri uri) {
// Implement this to handle requests for the MIME type of the data
// at the given URI.
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
}
return null;
}
}
运行该程序,接着新建一个新项目 ProviderTest
,测试访问我们新建的 ContentProvider
的数据。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/add_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add To Book"
android:textAllCaps="false" />
<Button
android:id="@+id/query_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Query From Book"
android:textAllCaps="false" />
<Button
android:id="@+id/update_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Update Book"
android:textAllCaps="false" />
<Button
android:id="@+id/delete_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Delete From Book"
android:textAllCaps="false" />
</LinearLayout>
逻辑代码
package com.example.providertest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
public static final String DATABASE_URI = "content://com.example.databasetest.provider";
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
providerTest();
}
private void providerTest() {
// 添加数据
Button addData = findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MainActivity", "click add");
Uri uri = Uri.parse(DATABASE_URI + "/book");
ContentValues values = new ContentValues();
values.put("name", "A Clash of Kings");
values.put("author", "George Martin");
values.put("pages", 1040);
values.put("price", 55.55);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
});
// 查询数据
Button queryData = findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MainActivity", "click query");
Uri uri = Uri.parse(DATABASE_URI + "/book");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
String author = cursor.getString(cursor.getColumnIndexOrThrow("author"));
int pages = cursor.getInt(cursor.getColumnIndexOrThrow("pages"));
double price = cursor.getDouble(cursor.getColumnIndexOrThrow("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
}
cursor.close();
}
}
});
// 更新数据
Button updateData = findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MainActivity", "click update");
Uri uri = Uri.parse(DATABASE_URI + "/book/" + newId);
ContentValues values = new ContentValues();
values.put("name", "A Storm of Swords");
values.put("pages", 1216);
values.put("price", 24.05);
getContentResolver().update(uri, values, null, null);
}
});
// 删除数据
Button deleteData = findViewById(R.id.delete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Uri uri = Uri.parse(DATABASE_URI + "/book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}
3、运行 ProviderTest
点击 Add To Book 向我们创建的 Provider 添加数据。
应用竟然闪退报错了。查看 Logcat 的错误信息。
看到这条错误信息,
- 第一反应是检查
ProviderTest
中 增删改查四个方法传入的Uri
有没有错误。 DatabaseTest
中UriMatcher
静态代码块中addURI
存储的匹配类型是不是有误。DatabaseTest
中AndroidManifest.xml
文件中声明Provider
中的authority
属性是否一致。
以上三个地方均没有问题,运行还是出现了上述错误。所以可能是运行的安卓版本过高,用低版本 API 写的教程有些内容过时了。
解决运行报错解决 java.lang.IllegalArgumentException: Unknown URL content://authority 的问题
由于我使用的手机是 Android 10,所以运行 ProviderTest
之前先打开 DatabaseTest
应用,再点击 ProviderTest
项目中的增删改查方法,就不会闪退了。
直接在手机上查看数据库内容用的是郭霖大神开发的开源框架 Glance
使用教程:郭霖 Glance
使用 Glance
的方法也很简单,直接添加该依赖即可
repositories {
google()
mavenCentral()
}
dependencies {
debugImplementation 'com.guolindev.glance:glance:1.1.0'
}
需要注意的是 glance
只能在使用 androidx
库的项目中使用。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)