内容提供器(Content Provider)
内容提供器简介:
Content Provider主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。
内容提供器的用法一般有两种:
第一种是使用现有的内容提供器来读取和操作相应程序中的数据,例如系统自带应用联系人就提供了现有的内容提供器所以我们就能创建一个程序来读取联系人里面的数据。
第二种是创建自己的内容提供器给我们程序的数据提供外部访问接口,那么任何其他程序知道这个接口就可以对这部分数据进行访问了.
下面我们直接看一个第二种用法的一个例子。
在例子之前我们要知道这些知识:
- 对于每一个应用程序来说,如果想要访问内容提供器共享的数据,就一定要借助ContentResolver类,我们可以通过Context中的getContentResolver方法来获取ContentResolver类的实例,ContentResolver类中提供了一系列的的方法对数据进行CRUD操作
- 不同于SQLiteDatabase,ContentResolver中的增删改查方法都不是接收表名参数,而是用一个Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一的标识符。
- 内容URI有两部分组成:authority和path。authority是用于对不同的应用程序作区分,为了避免冲突,都会采用程序包名的方式来进行命名,比如某个程序的包名为com.example.app,那么该程序对应的authority就可以命名为com.example.app.provider。path则是对同一应用程序中不同的表作区分的,通常会添加到authority后面,比如某个程序的数据库里存在的两张表table1和table2,这是就可以将path命名为/table1和/table2,然后将path和authority组合再加上协议头就是下面的标准写法了:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
以上是表示访问table1或table2表中的所有数据,我们还可以在内容URI后面加上id:
content://con.example.app.provider/table1/1
这样就表示我们期望访问的是table1表中id为1的数据
内容URI就以上两种写法了,以路径结尾的就是期望访问表中的所有数据;以id结尾的就是期望访问表中拥有相应id的数据。
我们还可以使用通配符的方式来匹配这两种格式的URI,下面会用到,规则如下:
- *:表示匹配任意长度的任意字符
- #:表示匹配任意长度的数字
所以一个能匹配任意一张表的内容URI可以写为:content://com.example.app.provider/*
一个能够匹配table1表中任意一行中数据的内容URI为content://com.example.app.provider/table1/#
下面是核心步骤
- 创建自己的内容提供器
我们打开上一章中的DatabaseTest项目,通过内容提供器来给它加入外部访问接口,将Toast去掉,因为跨程序不能直接使用Toast。右击com.example.databasetestb包>New->Other->Content Provider,我们将内容提供器命名为DatabaseProvider,将authority指定为com.houchongmu.databasetest.provider勾选Exported和Enabled。这昂个属性前者表示是否允许外部程序访问我们的内容提供器,Enabled属性表示是否启用这个内容提供器,点击Finish。 - 接着修改DatabaseProvider
package com.example.houchongmu.databasetest;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
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.houchongmu.databasetest.provider";
public static UriMatcher uriMatcher;
private static MyDatabaseHelper dbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
public DatabaseProvider() {
}
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore", null, 3);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = null;
switch (uriMatcher.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 Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);//returns :long the row ID of the newly inserted row, or -1 if an error occurred
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) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updateRows = 0;
switch (uriMatcher.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;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deleteRows = db.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRows = db.delete("Book", "id = ?", new String[]{bookId});
break;
case CATEGORY_DIR:
deleteRows = db.delete("Category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String category = uri.getPathSegments().get(1);
deleteRows = db.delete("Category", "id = ?", new String[]{category});
break;
}
return deleteRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.houchongmu.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.houchongmu.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.houchongmu.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.houchongmu.databasetest.provider.category";
default:
break;
}
return null;
}
}
里面重写了六个方法,我们一个一个看
- onCreate方法:初始化内容提供器的时候调用,通常会在这里完成对数据库的创建和升级等操作,返回true表示内容提供器初始化成功,返回false表示失败。注意的是,只有当ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化。这里利用SQLiteOpenHelper初始化了一个名为BookStore的数据库
- query方法:从内容提供器中查询数据。首先获取到SQLiteDatabase实例,接着使用uri参数来确定具体查询哪种表,projection参数用于确定查询哪些列,selection和selectionArgs参数用于约束查询哪几行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。这里有个细节额,调用了Uri对象的getPathSegments方法,它会将内容URI权限之后的部分以“/“符号进行分割,并将分割后的结果放入到一个字符串列表中,那这个列表的第0个位置就是路径,第一个位置就是存放的id了。 得到ud之后,再通过selection和selectionArgs参数进行约束就实现了查询单条数据 。
- UriMatcher类:我们注意到该方法里面有个switch语句,关于这个我们得从这个类里面的静态代码块讲起,静态代码块里面初始化了一个UriMatcher类,这个类可以轻松实现匹配内容URI的功能,构造函数里面传的是一个code值(the code to match for the root URI)。
addURI方法接收三个参数,可以分别将authority、path和一个自定义code值传进去,当调用UriMatcher类的match方法时就可以将一个Uri对象传进去,返回值是某个能够匹配这个Uri对象对应的code值,利用这个值就可以判断出调用方期望访问的是哪张表中的数据了。然后执行相应的逻辑
- UriMatcher类:我们注意到该方法里面有个switch语句,关于这个我们得从这个类里面的静态代码块讲起,静态代码块里面初始化了一个UriMatcher类,这个类可以轻松实现匹配内容URI的功能,构造函数里面传的是一个code值(the code to match for the root URI)。
- insert方法:
刚方法同样先获取到SQLiteDatabase的实例,然后根据传入的Uri判断用户想往哪张表里面添加数据,然后再调用SQLiteDatabase的实例进行insert操作就可以了,这个返回的是新插入行的id。注意本类insert方法要求返回的是一个能够表示这条新增数据的URI,所以我们还得调用Uri.parse方法来讲一个内容URI解析成Uri对象,当然这个内容URI是以新增数据的id结尾的。其他的方法类似就不过多解释了。
- insert方法:
- getType方法:
这是内容提供器必须提供的一个方法,用于获取Uri对象所对应的MIME类型,一个URI所对应的MIME字符串主要由三个部分组成,Android对这三个部分做了一下格式规定:- 必须以vnd开头。
- 如果内容URI以路径结尾,则后面接android.cursor.dir/。如果内容URI以id结尾,则后面接android.cursor.item/
- 最后接上vnd.<authority>.<path>
如:对于content://com.example.app.provider/table1的MIME类型可以写为vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于content://com.example.app.provider/table1/1的MIME类型可以写为vnd.android.cursor.item/vnd.com.example.app.provider.table1
知道了上面三点这个方法就不用过多解释了。
- 最后接上vnd.<authority>.<path>
- getType方法:
- 接下来新建ProviderTest来访问DatabaseTest中的数据
- 在布局里面创建四个Button
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:onClick="add_data"
android:text="添加Book数据"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:onClick="query_data"
android:text="查询Book数据"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:onClick="updata_data"
android:text="更新Book数据"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button2"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:onClick="delete_data"
android:text="删除Book数据"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button3"
app:layout_constraintVertical_bias="0.0" />
</android.support.constraint.ConstraintLayout>
- MainActivity
package com.example.houchongmu.providertest;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void add_data(View view) {
Uri uri = Uri.parse("content://com.houchongmu.databasetest.provider/book");
ContentValues values = new ContentValues();
values.put("name", "假如给我三天光明");
values.put("author", "海伦凯勒");
values.put("price", 30);
values.put("pages", 500);
Uri newUri = getContentResolver().insert(uri, values);//返回的是一个新的Uri对象,包含的新增数据的id
newId = newUri.getPathSegments().get(1);
}
public void query_data(View view) {
Uri uri = Uri.parse("content://com.houchongmu.databasetest.provider/book");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
Log.d(TAG, "query_data: " + name);
Log.d(TAG, "query_data: " + author);
Log.d(TAG, "query_data: " + price);
Log.d(TAG, "query_data: " + pages);
} while (cursor.moveToNext());
cursor.close();
}
}
public void updata_data(View view) {
Uri uri = Uri.parse("content://com.houchongmu.databasetest.provider/book");
ContentValues values = new ContentValues();
// values.put("name", "少有人走的路");
// values.put("author", "不知道");
// values.put("pages", 400);
// values.put("price", 12.34);
// getContentResolver().update(uri, values, null, null);
// values.clear();
values.put("price", 19);
getContentResolver().update(uri, values, "name = ?", new String[]{"假如给我三天光明"});
}
public void delete_data(View view) {
Uri uri = Uri.parse("content://com.houchongmu.databasetest.provider/book/"+newId);
int rows = getContentResolver().delete(uri, null, null);
Log.d(TAG, "delete_data: 删除了" + rows + "行");
}
}
以上内容结合上面自定义的DatabaseProvider类很容易理解,就不过多解释了。
newId是获取新插入数据的id,后面删除数据的Button就能删除这条插入的数据。
点击添加BOOK数据,然后再点击查询BOOK数据:
两个程序都能访问这条数据