简单起见,我们还是在上一章中 DatabaseTest 项目的基础上继续开发,通过内容提供器 来给它加入外部访问接口。
打开 DatabaseTest 项目,首先将 MyDatabaseHelper 中使用 Toast 弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用 Toast。然后添加 一个 DatabaseProvider 类,代码如下所示:
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 uriMatcher;
private 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);
}
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db",
null, 2);
return true;
}
@Override
public Cursor query(Uri
uri, String[] projection, String selection, String[] selectionArgs, String
sortOrder) {
// 查询数据
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);
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);
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 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:
break;
}
return updatedRows;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 删除数据
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:
break;
}
return deletedRows;
}
@Override
public String getType(Uri
uri) {
switch (uriMatcher.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;
}
}
代码虽然很长,不过不用担心,这些内容都非常容易理解,因为使用到的全部都是上一
小节中我们学到的知识。首先在类的一开始,同样是定义了四个常量,分别用于表示访问
Book 表中的所有数据、访问 Book 表中的单条数据、访问 Category 表中的所有数据和访问 Category 表中的单条数据。然后在静态代码块里对 UriMatcher 进行了初始化操作,将期望匹
配的几种
URI 格式添加了进去。
接下来就是每个抽象方法的具体实现了,先来看下 onCreate()方法,这个方法的代码很 短,就是创建了一个 MyDatabaseHelper 的实例,然后返回 true 表示内容提供器初始化成功, 这时数据库就已经完成了创建或升级操作。
接着看一下 query()方法,在这个方法中先获取到了 SQLiteDatabase 的实例,然后根据 传入的 Uri
参数判断出用户想要访问哪张表,再调用 SQLiteDatabase 的 query()进行查询,并 将 Cursor 对象返回就好了。注意当访问单条数据的时候有一个细节,这里调用了 Uri 对象的 getPathSegments()方法,它会将内容 URI 权限之后的部分以“/”符号进行分割,并把分割后 的结果放入到一个字符串列表中,那这个列表的第 0 个位置存放的就是路径,第 1 个位置存 放的就是 id 了。得到了 id 之后,再通过 selection 和 selectionArgs 参数进行约束,就实现了 查询单条数据的功能。
再往后就是 insert()方法,同样它也是先获取到了 SQLiteDatabase 的实例,然后根据传入
的 Uri 参数判断出用户想要往哪张表里添加数据,再调用 SQLiteDatabase 的 insert()方法进行添加就可以了。注意 insert()方法要求返回一个能够表示这条新增数据的 URI,所以我们还需要调用 Uri.parse()方法来将一个内容 URI 解析成 Uri 对象,当然这个内容 URI 是以新增数据 的 id 结尾的。
接下来就是 update()方法了,相信这个方法中的代码已经完全难不倒你了。也是先获取 SQLiteDatabase 的实例,然后根据传入的 Uri 参数判断出用户想要更新哪张表里的数据,再 调用 SQLiteDatabase 的 update()方法进行更新就好了,受影响的行数将作为返回值返回。
下面是 delete()方法,是不是感觉越到后面越轻松了?因为你已经渐入佳境,真正地找 到窍门了。这里仍然是先获取到 SQLiteDatabase 的实例,然后根据传入的 Uri 参数判断出用
户想要删除哪张表里的数据,再调用 SQLiteDatabase 的 delete()方法进行删除就好了,被删 除的行数将作为返回值返回。
最后是 getType()方法,这个方法中的代码完全是按照上一节中介绍的格式规则编写的,
相信已经没有什么解释的必要了。
这样我们就将内容提供器中的代码全部编写完了,不过离实现跨程序数据共享的功能还 差了一小步,因为还需要将内容提供器在 AndroidManifest.xml 文件中注册才可以,如下所示:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.databasetest"
android:versionCode="1"
android:versionName="1.0" >
……
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:label="@string/app_name"
android:theme="@style/AppTheme" >
……
<provider
android:name="com.example.databasetest.DatabaseProvider"
android:authorities="com.example.databasetest.provider" >
</provider>
</application>
</manifest>
可以看到,这里我们使用了<provider>标签来对 DatabaseProvider 这个内容提供器进行注 册,在 android:name 属性中指定了该类的全名,又在 android:authorities 属性中指定了该内容
提供器的权限。
现在 DatabaseTest 这个项目就已经拥有了跨程序共享数据的功能了,我们赶快来尝试一下。首先需要将 DatabaseTest 程序从模拟器中删除掉,以防止上一章中产生的遗留数据对我
们造成干扰。然后运行一下项目,将 DatabaseTest 程序重新安装在模拟器上了。接着关闭掉
DatabaseTest 这个项目,并创建一个新项目 ProviderTest ,我们就将通过这个程序去访问 DatabaseTest 中的数据。
还是先来编写一下布局文件吧,修改 activity_main.xml 中的代码,如下所示:
<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" />
<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
Book" />
<Button
android:id="@+id/delete_data"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:text="Delete From
Book" />
</LinearLayout>
布局文件很简单,里面放置了四个按钮,分别用于添加、查询、修改和删除数据的。然
后修改
MainActivity 中的代码,如下所示:
public class MainActivity extends
Activity {
private String newId;
@Override
protected
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 添加数据
Uri uri =
Uri.parse("content://com.example.databasetest. provider/book");
ContentValues values = new
ContentValues();
values.put("name", "A * of Kings");
values.put("author", "George Martin");
values.put("pages", 1040);
values.put("price", 22.85);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
});
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(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 updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(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 deleteData = (Button) findViewById(R.id.delete_data);
deleteData.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//
删除数据
Uri uri =
Uri.parse("content://com.example.databasetest. provider/book/" +
newId);
getContentResolver().delete(uri,
null, null);
}
});
}
}
可以看到,我们分别在这四个按钮的点击事件里面处理了增删改查的逻辑。添加数据的时候,首先调用了 Uri.parse()方法将一个内容 URI 解析成 Uri 对象,然后把要添加的数据都
存放到
ContentValues 对象中,接着调用 ContentResolver 的 insert()方法执行添加操作就可以 了。注意 insert()方法会返回一个 Uri 对象,这个对象中包含了新增数据的 id,我们通过 getPathSegments()方法将这个 id 取出,稍后会用到它。
查询数据的时候,同样是调用了 Uri.parse()方法将一个内容 URI
解析成 Uri
对象,然后 调用 ContentResolver 的 query()方法去查询数据,查询的结果当然还是存放在 Cursor 对象中 的。之后对 Cursor 进行遍历,从中取出查询结果,并一一打印出来。
更新数据的时候,也是先将内容 URI
解析成 Uri 对象,然后把想要更新的数据存放到 ContentValues 对象中,再调用 ContentResolver 的 update()方法执行更新操作就可以了。注意 这里我们为了不想让 Book 表中其他的行受到影响,在调用 Uri.parse()方法时,给内容 URI 的尾部增加了一个 id,而这个 id 正是添加数据时所返回的。这就表示我们只希望更新刚刚 添加的那条数据,Book 表中的其他行都不会受影响。
删除数据的时候,也是使用同样的方法解析了一个以 id 结尾的内容
URI,然后调用 ContentResolver 的 delete()方法执行删除操作就可以了。由于我们在内容 URI 里指定了一个 id,因此只会删掉拥有相应 id
的那行数据,Book 表中的其他数据都不会受影响。
现在运行一下 ProviderTest 项目,会显示如图 7.4 所示的界面。
图 7.4
点击一下 Add To Book 按钮,此时数据就应该已经添加到 DatabaseTest 程序的数据库中了,我们可以通过点击 Query From Book 按钮来检查一下,打印日志如图 7.5 所示。
图 7.5
然后点击一下 Update Book 按钮来更新数据,再点击一下 Query From Book 按钮进行检 查,结果如图 7.6
所示。
图 7.6
最后点击 Delete From Book 按钮删除数据,此时再点击 Query From Book 按钮就查询不 到数据了。
由此可以看出,我们的跨程序共享数据功能已经成功实现了!现在不仅是 ProviderTest 程序,任何一个程序都可以轻松访问 DatabaseTest 中的数据,而且我们还丝毫不用担心隐私
数据泄漏的问题。