中的内容提供者
为什么需要内容提供者
为了跨程序访问数据。试想如果在App-1中创建了一个私有数据库,App-2是不能直接访问的。因为权限不够,虽然可以使用chmod 777
来修改权限,然后使用SQLiteDatabase.openDatabase
的静态方法,填上具体的路径和模式来访问。但这并不推荐,有没有更好的办法?官方推荐使用ContentProvider--内容提供者。
创建内容提供者
简单起见,使用以前的数据库的项目DatabaseTest,同时建立两个表book和category, onUpgrade
方法实现了数据库的升级功能。onUpgrade
里面强制onCreate
,注意必须先删除原来的表,否则我们创建时候发现原来的表还存在就会报错。
package com.example.administrator.databasetest;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public static final String CREATE_CATEGORY = "create table category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
private Context mContext;
publicMyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
this.mContext = context;
}
@Override
publicvoidonCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
publicvoidonUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists book");
db.execSQL("drop table if exists category");
onCreate(db);
}
}
MainActivity里面就显示下界面,省略了。
如果想让这个数据库共享,其他应用也能访问?只需新增一个内容提供者即可。
New -> Other -> ContentProvider,AS会帮我们在AndroidManifest.xml里注册好。有一个属性authorities
比较重要,一般命名方式是<包名>.provider
,比如com.example.cptest.provider
。
可以看到,内容提供者的方法和操作数据库差不多。最大的不同是操作数据库需要填上表名,而内容提供者中的方法需要填上Uri。为什么呢?因为是跨程序访问数据,多个应用的表名可能一样,这样就不知道到底访问哪个应用的数据了。Uri的格式一般如下
content://<package_name>.provider/<path>/<id>
,举个例子content://com.example.databasetest.provider/book/2
表示访问book表的id为2的那行数据。
甚至可以使用通配符
*
表示匹配任意长度的任意字符#
表示匹配任意长度的数字
于是可以匹配任意表的URI可以写成content://com.example.databasetest.provider/*
可以匹配一个表中任意一行的URI可以写成content://com.example.databasetest.provider/book/#
package com.example.administrator.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;
import android.support.annotation.NonNull;
public class DatabaseProvider extends ContentProvider {
// 0123是自定义代码,用于清楚表达我们想要访问访问数据库的哪个表或者哪一行数据
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;
// 和清单文件里provider的authority属性一致
public static final String AUTHORITY = "com.example.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static {
// NO_MATCH就是-1
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 第三个参数填上自定义的代码,对应于uriMatcher.match(uri)
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
@Override
publicintdelete(@NonNull Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deletedRows = db.delete("book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deletedRows = db.delete("book", "id = ?", new String[]{bookId});
break;
case CATEGORY_DIR:
deletedRows = db.delete("category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deletedRows = db.delete("category", "id = ?", new String[]{categoryId});
break;
default:
}
return deletedRows;
}
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.administrator.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.administrator.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.administrator.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.administrator.databasetest.provider.category";
default:
return null;
}
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.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:
}
return uriReturn;
}
// 一旦使用到内容提供者就调用此方法,并得到数据库连接的实例,返回true表示内容提供者初始化成功
@Override
publicbooleanonCreate() {
// TODO: Implement this to initialize your content provider on startup.
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 19);
return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO: Implement this to handle query requests from clients.
SQLiteDatabase db = dbHelper.getReadableDatabase();
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); // 这里path是/book/bookId,get(1)就是bookId
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:
}
return cursor;
}
@Override
publicintupdate(@NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
updatedRows = db.update("book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("book", values, "id = ?", new String[]{bookId});
break;
case CATEGORY_DIR:
updatedRows = db.update("category", values, selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("category", values, "id = ?", new String[]{categoryId});
break;
default:
}
return updatedRows;
}
}
流程是这样的,一旦需要内容提供者时就会调用其onCreate方法并且实例化了数据库连接帮助类。提供了UriMatcher,在静态代码块里初始化,添加上我们期望匹配的URI
static {
// NO_MATCH就是-1
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 第三个参数填上自定义的代码,对应于uriMatcher.match(uri)
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
接收三个参数,分别是authority、path和自定义唯一码。
增删改查的方法就不说了,注意两点。
- 有个新方法
uri.getPathSegments().get(1);
这是什么意思呢?简单来说比如一个URI是这样的content://com.example.databasetest.provider/book/2
,那么以provider/
处分割,后面的部分是<path>.<id>
,那么get(0)
就获取到了路径,get(1)
就获取到了id。 insert
方法返回的是一个新的URI,比如新增的一行db.insert返回一个新的id为3,那么内容提供者的insert方法返回的新URI为content://com.example.databasetest.provider/book/3
最后介绍getType()
这个方法 -- 根据传入的内同URI来返回相应的MIME类型。
- 必须以vnd开头
- 如果URI以path结尾,则后接
android.cursor.dir/
;如果URI以id结尾,则后接android.cursor.item/
- 最后接上
vnd.<authority>.<path>
对于content://com.example.databasetest.provider/book
这个URI,对应的MIME是vnd.android.cursor.dir/vnd.com.example.databasetest.book
;
对于content://com.example.databasetest.provider/book/2
这个URI,对应的MIME是vnd.android.cursor.item/vnd.com.example.databasetest.book
。
好了内容提供者写好了,赶紧在另外一个应用里尝试一下!
通过内容提供者访问数据
假设此应用时App-B,上面的应用是App-A。
布局实现对上述应用数据库中的book表的CURD
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:id="@+id/add_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Add to Book"/>
<Button android:id="@+id/query_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Query From Book"/>
<Button android:id="@+id/update_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Update Data"/>
<Button android:id="@+id/del_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Delete Data"/>
</LinearLayout>
MainActivity,所有的方法都是基于getContentResolver()
。得到内容提供者后,尝试访问App-A的数据。此时App-A里内容提供者的onCreate
方法得到执行,由此创建了数据库。
我们只需正确匹配Uri就能访问到App-A中的数据库了,代码很简单,不需要讲解了。
package com.sunhaiyu.contentprovidertest;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
// newId是每插入一条数据就会被赋值的,所以进行更新和删除操作时只能操作最后插入的数据,其他数据不会受到影响
private String newId;
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
publicvoidonClick(View v) {
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
ContentValues values = new ContentValues();
values.put("name", "A Clash of Kings");
values.put("author", "George Martin");
values.put("pages", 1040);
values.put("price", 19.99);
Uri newUri = getContentResolver().insert(uri, values);
if (newUri != null) {
newId = newUri.getPathSegments().get(1);
}
}
});
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
publicvoidonClick(View v) {
Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("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 updataData = (Button) findViewById(R.id.update_data);
updataData.setOnClickListener(new View.OnClickListener() {
@Override
publicvoidonClick(View v) {
Uri uri = Uri.parse("content://com.example.databasetest.provider/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 delData = (Button) findViewById(R.id.del_data);
delData.setOnClickListener(new View.OnClickListener() {
@Override
publicvoidonClick(View v) {
Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}
是不是很方便?使用ContentProvider就能式样App-A轻松访问到App-B中的内容。还有一些常见的例子,比如访问联系人和短信数据等。