Android四大组件之Content Provider简析

内容内容提供器(Content Provider)和内容接收器(ContentResolver),有程序将其数据提供了对外的访问接口,继而就有对应的其他程序调用这些接口。Android系统提供了内容内容提供器(Content Provider)和内容接收器(ContentResolver)就是为了这两个方面提供的对象。

一,内容提供器(Content Provider)简介

1,Android 数据持久化的技术,包括文件存储、SharedPreferences 存储、以及数据库存储。不知道你有没有发现,使用这些持久化技术所保存的数据都只能在当前应用程序中访,问。虽然文件和SharedPreferences 存储中提供了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE 这两种操作模式,用于供给其他的应用程序访问当前应用的数据,但这两种模式在Android 4.2 版本中都已被废弃了。因为Android官方已经不再推荐使用这种方式来实现跨程序数据共享的功能,而是应该使用更加安全可靠的内容提供器技术。

2,可能你会有些疑惑,为什么要将我们程序中的数据共享给其他程序呢?当然,这个要视情况而定的,比如说账号和密码这样的隐私数据显然是不能共享给其他程序的,不过一些,可以让其他程序进行二次开发的基础性数据,我们还是可以选择将其共享的。例如系统的电话簿程序,它的数据库中保存了很多的联系人信息,如果这些数据都不允许第三方的程序进行访问的话,恐怕很多应用的功能都要大打折扣了。

3,内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访,数据的安全性。目前,使用内容提供器是Android 实现跨程序共享数据的标准方式。

4,不同于文件存储和SharedPreferences 存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险,。内容提供器的用法一般有两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。

二,内容接收器(ContentResolver)简介

1,当一个应用程序通过内容提供器对其数据提供了外部访问接口,任何其他的应用程序就都可以对这部分数据进行访问。Android 系统中自带的电话簿、短信、媒体库等程序都提,供了类似的访问接口。

2,对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolve 类,可以通过Context 中的getContentResolver()方法获取到该类的实例。ContentResolver 中提供了一系列的方法用于对数据进行CRUD 操作,其中insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。有没有似曾相识的感觉?没错,SQLiteDatabase 中也是使用的这几个方法来进行CRUD操作的,只不过它们在方法参数上稍微有一些区别。不同于SQLiteDatabase,ContentResolver 中的增删改查方法都是不接收表名参数的,而是使用一个Uri 参数代替,这个参数被称为内容URI。内容URI 给内容提供器中的数据建立了唯一标识符,它主要由两部分组成,权限(authority)和路径(path)。权限是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的权限就可以命名为com.example.app.provider。路径则是用于对同一应用程序中不同的表做区分的,通常都会添加到权限的后面。比如某个程序的数据库里存在两张表,table1 和table2,这时就可以将路径分别命名为/table1和/table2,然后把权限和路径进行组合,内容URI 就变成com.example.app.provider/table1和com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此,内容URI 最标准的格式写法如下:

content://com.example.app.provider/table1

content://com.example.app.provider/table2

3,有没有发现,内容URI 可以非常清楚地表达出我们想要访问哪个程序中哪张表里的数据。也正是因此,ContentResolver 中的增删改查方法才都接收Uri 对象作为参数,因为使用表名的话系统将无法得知我们期望访问的是哪个应用程序里的表。,在得到了内容URI 字符串之后,我们还需要将它解析成Uri 对象才可以作为参数传入。

解析的方法也相当简单,代码如下所示:

Uri uri = Uri.parse("content://com.example.app.provider/table1")

只需要调用Uri.parse()方法,就可以将内容URI 字符串解析成Uri 对象了。

三,内容接收器(ContentResolver)的增删改查

Uri uri = Uri.parse("content://com.example.app.provider/table1")

只需要调用Uri.parse()方法,就可以将内容URI 字符串解析成Uri 对象了。

现在我们就可以使用这个Uri 对象来查询table1 表中的数据了,代码如下所示:

1,查询

Cursor cursor = getContentResolver().query(

uri,

projection,

selection,

selectionArgs,

sortOrder);

这些参数和SQLiteDatabase 中query()方法里的参数很像,但总体来说要简单一些,毕竟这是在访问其他程序中的数据,没必要构建过于复杂的查询语句。

query()方法 参数对应SQL 部分描述

uri from table_name 指定查询某个应用程序下的某一张表

projection select column1, column2 指定查询的列名

selection where column = value 指定where 的约束条件

selectionArgs - 为where 中的占位符提供具体的值

orderBy order by column1, column2 指定查询结果的排序方式

查询完成后返回的仍然是一个Cursor 对象,这时我们就可以将数据从Cursor 对象中逐个读取出来了。读取的思路仍然是通过移动游标的位置来遍历Cursor 的所有行,然后再取出每一行中相应列的数据,代码如下所示:

if (cursor != null) {

while (cursor.moveToNext()) {

String column1 = cursor.getString(cursor.getColumnIndex("column1"));

int column2 = cursor.getInt(cursor.getColumnIndex("column2"));

}

cursor.close();

}

2,增加

我们先来看看如何向table1 表中添加一条数据,代码如下所示:

ContentValues values = new ContentValues();

values.put("column1", "text");

values.put("column2", 1);

getContentResolver().insert(uri, values);

可以看到,仍然是将待添加的数据组装到ContentValues 中,然后调用ContentResolver的insert()方法,将Uri 和ContentValues 作为参数传入即可。

3,更新

现在如果我们想要更新这条新添加的数据,把column1的值清空,可以借助ContentResolver 的update()方法实现,代码如下所示:

ContentValues values = new ContentValues();

values.put("column1", "");

getContentResolver().update(uri, values, "column1 = ? and column2 = ?", newString[] {"text", "1"});

注意上述代码使用了selection 和selectionArgs 参数来对想要更新的数据进行约束,以防止所有的行都会受影响。

4,删除

可以调用ContentResolver 的delete()方法将这条数据删除掉,代码如下所示:

getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });

四,内容接收器(ContentResolver)的具体应用(读取系统联系人)

private void init() {

readContacts();

adapter = new ArrayAdapter(MainActivity1.this, android.R.layout.simple_list_item_1, contactsList);

contactsView = (ListView) findViewById(R.id.contacts_view);

contactsView.setAdapter(adapter);

}

private void readContacts() {

Cursor cursor = null;

try{

cursor = getContentResolver().query(

ContactsContract.CommonDataKinds.Phone.CONTENT_URI,

null, null, null, null);

while(cursor.moveToNext()){

// 获取联系人姓名

String displayName = cursor.getString(cursor.getColumnIndex(

ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));

// 获取联系人手机号

String number = cursor.getString(cursor.getColumnIndex(

ContactsContract.CommonDataKinds.Phone.NUMBER));

contactsList.add(displayName + "\n" + number);

}

} catch(Exception e) {

e.printStackTrace();

} finally {

if(cursor!=null){

cursor.close();

}

}

}

读取系统联系人也是需要声明权限的,因此修改AndroidManifest.xml 中的代码,如下所示:

五,创建自己的内容提供器(将自己的程序的部分数据共享,是其他程序也可以使用)

如果想要实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以通过新建一个类去继承ContentProvider 的方式来创建一个自己的内容提供器。ContentProvider 类中有六个抽象方法,我们在使用子类继承它的时候,需要将这六个方法全部重写。

1,示例

public class MyProvider extends ContentProvider {

@Override

public boolean onCreate() {

return false;

}

@Override

public Cursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

return null;

}

@Override

public Uri insert(Uri uri, ContentValues values) {

return null;

}

@Override

public int update(Uri uri, ContentValues values, String selection,

String[] selectionArgs) {

return 0;

}

@Override

public int delete(Uri uri, String selection, String[] selectionArgs) {

return 0;

}

@Override

public String getType(Uri uri) {

return null;

}

}

2,方法介绍

在这六个方法中,相信大多数你都已经非常熟悉了,我再来简单介绍一下吧。

1). onCreate()

初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回true 表示内容提供器初始化成功,返回false 则表示失败。注意,只有当存在ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。

2). query()

从内容提供器中查询数据。使用uri 参数来确定查询哪张表,projection 参数用于确定查询哪些列,selection 和selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在Cursor 对象中返回。

3). insert()

向内容提供器中添加一条数据。使用uri 参数来确定要添加到的表,待添加的数据保存在values 参数中。添加完成后,返回一个用于表示这条新记录的URI。

4). update()

更新内容提供器中已有的数据。使用uri 参数来确定更新哪一张表中的数据,新数据保存在values 参数中,selection 和selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。

5). delete()

从内容提供器中删除数据。使用uri 参数来确定删除哪一张表中的数据,selection和selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。

6). getType()

根据传入的内容URI 来返回相应的MIME 类型。

3,路径(Uri)详解

可以看到,几乎每一个方法都会带有Uri 这个参数,这个参数也正是调用ContentResolver的增删改查方法时传递过来的。而现在,我们需要对传入的Uri 参数进行解析,从中分析出调用方期望访问的表和数据。

回顾一下,一个标准的内容URI 写法是这样的:

content://com.example.app.provider/table1

这就表示调用方期望访问的是com.example.app 这个应用的table1 表中的数据。除此之外,我们还可以在这个内容URI 的后面加上一个id,如下所示:

content://com.example.app.provider/table1/1

这就表示调用方期望访问的是com.example.app 这个应用的table1 表中id 为1 的数据。

内容URI 的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以id 结尾就表示期望访问该表中拥有相应id 的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下。

1). *:表示匹配任意长度的任意字符

2). #:表示匹配任意长度的数字

所以,一个能够匹配任意表的内容URI 格式就可以写成:

content://com.example.app.provider/*

而一个能够匹配table1 表中任意一行数据的内容URI 格式就可以写成:

content://com.example.app.provider/table1/#

接着,我们再借助UriMatcher 这个类就可以轻松地实现匹配内容URI 的功能。UriMatcher中提供了一个addURI()方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义代码传进去。这样,当调用UriMatcher 的match()方法时,就可以将一个Uri 对象传入,返回值是某个能够匹配这个Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。

4,Uri 对象所对应的MIME 类型格式

getType()方法。它是所有的内容提供器都必须提供的一个方法,用于获取Uri 对象所对应的MIME 类型。一个内容URI 所对应的MIME字符串主要由三部分组分,Android 对这三个部分做了如下格式规定。

1). 必须以vnd 开头。

2). 如果内容URI 以路径结尾,则后接android.cursor.dir/,如果内容URI 以id 结尾,则后接android.cursor.item/。

3). 最后接上vnd..

所以,对于content://com.example.app.provider/table1 这个内容URI,它所对应的MIME类型就可以写成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

对于content://com.example.app.provider/table1/1 这个内容URI,它所对应的MIME 类型就可以写成:

vnd.android.cursor.item/vnd.com.example.app.provider.table1

六,具体案例

内容提供者是为不同的程序之间提供数据共享的,所以要验证自定义的内容提供者,则需建立两个项目,其中一个自定义内容提供者,另一个调用该内容提供者完成数据共享。

1,项目一,自定义内容提供者

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;

}

}

接下来就是每个抽象方法的具体实现了,先来看下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 文件中注册才可以。

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

……

android:name="com.example.databasetest.DatabaseProvider"

android:authorities="com.example.databasetest.provider" >

2,项目二,调用自定义的内容提供者

还是先来编写一下布局文件吧,修改activity_main.xml 中的代码,如下所示:

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical" >

android:id="@+id/add_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Add To Book" />

android:id="@+id/query_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Query From Book" />

android:id="@+id/update_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Update Book" />

android:id="@+id/delete_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Delete From Book" />

布局文件很简单,里面放置了四个按钮,分别用于添加、查询、修改和删除数据的。然后修改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 Clash 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 表中的其他数据都不会受影响。

七,总结

bt2和bt5两种删除方式都可成功

通过bt2和bt5的对比,可以证明路径uri和约束条件是相辅相成的。

既可以在路径中带上参数,约束条件为空,再由接口提供者做逻辑处理;

也可以设置约束条件,在调用方做具体的逻辑处理。

类似于客户端调用服务端,即可以在客户端做数据处理,也可以在服务端做逻辑处理。事先约定好即可。

你可能感兴趣的:(Android四大组件之Content Provider简析)