- 一、创建 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()
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String seletion, @Nullable String[] selectionArgs, @Nullable String sortOrder) {}
- insert()
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
- delete()
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
- update()
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
- getType()
@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
@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;
}
@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;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@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属性对应了创建时勾选的两个选项。
...
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() {
}
@Override
public boolean onCreate() {
// Implement this to initialize your content provider on startup.
mDbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@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 的数据。
2、创建 ProviderTest 布局文件逻辑代码
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库的项目中使用。



