在Android中,应用间的数据共享是一件很常见的事情,典型的场景就是读取手机通讯录。为了实现安全高效地数据共享,Android提供了ContentProvider
这一组件,即内容提供器。本文就简单讲解一下ContentProvider
的使用。
实际上,手机中本就存在一些内容提供器,学会如何通过这些内容提供器访问其他应用中的数据也十分重要。其实只需要借助ContentResolver
,我们就可以很方便地访问其他应用提供的共享数据。要想获得一个ContentResolver
实例,只要调用Context
的getContentResolver
方法即可,该方法的原型如下:
public ContentResolver getContentResolver()
需要注意的是,内容提供器通过Uri
对象来区分想要访问的数据,而不是数据库中的表名。原因也很明显,在多个应用中可能具有相同的表名,因此表名无法充当唯一标识。Uri
字符串由两部分组成,即authority
和path
。authority
一般由应用包名+.provider
构成,path
则是我们想要访问的数据表名。当然,为了表明这是一条Uri
字符串,往往还要在头部添加content://
。一个典型的例子如下:
content://com.example.providerlibrary.provider/Book
上面这条Uri
字符串表示想要访问providerlibrary
中的Book
表。当然,仅有一条Uri
字符串是不够的,我们还需要通过Uri
的parse
方法将字符串解析为Uri
对象。通过Uri
对象,就可以访问其他应用中的共享数据了。parse
方法原型如下:
public static Uri parse(String uriString)
添加数据的操作由ContentResolver
的insert
方法实现,其原型如下:
public final Uri insert(Uri url,ContentValues values)
第一个参数是Uri
,第二个参数则是需要插入的数据内容。ContentValues
的用法与HashMap相似,通过键值对的方式存储数据。insert
方法将返回插入数据在目标数据表中的Uri
。以下提供一个添加数据的例子:
ContentValues values=new ContentValues();
values.put("name","Java");
values.put("author","Bill");
values.put("price","60");
Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
getContentResolver().insert(uri,values);
删除数据的操作由ContentResolver
的delete
方法实现,其原型如下:
public final int delete(Uri url,String where,String[] selectionArgs)
第一个参数是Uri
,第二个、第三个参数是删除的限制条件,如果都传入null
就代表删除数据表中的所有行。delete
方法将返回被删除的行数。以下提供一个删除数据的例子:
Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
getContentResolver().delete(uri,"name=?",new String[]{"Java"});
以上操作就代表删除Book
表中所有name
字段为”Java“的数据。
更新数据的操作由ContentResolver
的update
方法实现,其原型如下:
public final int update(Uri uri,ContentValues values,String where,String[] selectionArgs)
第一个参数是Uri
,第二个参数是用于更新的数据,第三个、第四个参数是更新的限制条件,如果都传入null
就代表更新数据表中的所有行。update
方法将返回被更新的行数。以下提供一个更新数据的例子:
ContentValues values=new ContentValues();
values.put("price","70");
Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
getContentResolver().update(uri,values,"name=?",new String[]{"Java"});
以上操作就代表找到Book
表中所有name
字段为“Java”的数据,并将它们的price
字段更新为”70“。
查询数据的操作由ContentResolver
的query
方法实现,其原型如下:
public final Cursor query(Uri uri,String[] projection,String selection,
String[] selectionArgs,String sortOrder)
第一个参数是Uri
。第二个参数是想要查询的列,传入null
就代表查询所有列。第三个、第四个参数是查询的限制条件,如果都传入null
就代表查询数据表中的所有行。第五个参数是排序规则,传入null
就代表进行默认排序。query
方法将返回Cursor
对象,通过这个Cursor
对象我们就可以访问查询到的数据了。以下提供一个查询数据的例子:
Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
Cursor cursor=getContentResolver().query(uri,new String[]{"author","price"},
"name=?",new String[]{"Java"},null);
if(cursor.moveToFirst()){
do{
Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("name")));
Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("price")));
}while(cursor.moveToNext());
}
cursor.close();
以上操作就代表查询Book
表中所有name
字段为“Java”的数据,并且只返回它们author
和price
字段的内容,最后还对返回结果进行了默认排序。通过do-while
循环,我们借助Cursor
对象获取了所有查询到的数据。
正如本文开篇所言,内容提供器的一个典型应用场景就是读取手机通讯录的数据。对此,可以参考这篇博客:
读取手机联系人
学会了如何访问其他应用中的数据,现在我们再来学习一下如何创建自己的内容提供器。这样,其他的应用也可以通过ContentResolver
访问我们应用的共享数据了。
创建内容提供器的方法很简单,只要定义一个类继承ContentProvider
即可,示例代码如下:
public class BookContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
}
ContentProvider
是一个抽象类,我们需要重写以上这6个方法。它们的作用如下:
ContentResolver
在试图访问应用,内容提供器就会进行初始化。Uri
对应数据的MIME
类型字符串,具体细节将在后文详述。insert、delete、update、query
这四个方法的参数和返回值已经在ContentResolver
部分进行了讲解,这里就不再赘述了。
可以看到,ContentProvider
的后5个方法都需要用到Uri
参数。因此,我们需要一种手段去对外界传入的Uri
进行匹配,并解析出Uri
的意图。实际上,只需要借助UriMatcher
就可以轻松完成这些任务。
在讲解UriMatcher
之前,我们需要知道Uri
具有两种类型。除了前文讲述的Uri之外,还可以在Uri
字符串末尾添加一个id
,用于表示数据表中具体某一行的数据,示例代码如下:
content://com.example.providerlibrary.provider/Book
content://com.example.providerlibrary.provider/Book/1
第一条Uri
字符串代表Book
表中的所有数据,第二条Uri
字符串则代表Book
表中id
为1的数据。
UriMatcher
中的主要方法是addURI
和match
,它们的原型如下:
public void addURI(String authority, String path, int code)
public int match(Uri uri)
addURI
方法用于向UriMatcher
中添加能够匹配的Uri
。第一、二个参数分别是authority
和path
,第三个参数是自定义码。path
中可以使用通配符#
和*
,它们的区别如下:
使用这两个通配符可以表达相对丰富的意图,示例代码如下:
content://com.example.providerlibrary.provider/*
content://com.example.providerlibrary.provider/Book/#
第一条Uri
能够匹配providerlibrary
中的所有数据表,第二条Uri
能够匹配Book
表中的所有行。
match
方法用于匹配外界传入的Uri
,并返回匹配成功的Uri
对应的自定义码。下面演示这两个方法的使用方式:
public class BookContentProvider extends ContentProvider {
private static final int BOOK=0;
private static final int BOOK_ITEM=1;
private static final int AUTHOR=2;
private static final int AUTHOR_ITEM=3;
private static final String AUTHORITY="com.example.providerlibrary.provider";
private static UriMatcher uriMatcher;
static{//初始化
uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"Book",BOOK);
uriMatcher.addURI(AUTHORITY,"Book/#",BOOK_ITEM);
uriMatcher.addURI(AUTHORITY,"Author",AUTHOR);
uriMatcher.addURI(AUTHORITY,"Author/#",AUTHOR_ITEM);
}
......
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
switch(uriMatcher.match(uri)){
case BOOK:
//访问Book表
break;
case BOOK_ITEM:
//访问Book表中的某一条数据
break;
case AUTHOR:
//访问Author表
break;
case AUTHOR_ITEM:
//访问Author表中的某一条数据
break;
default:
break;
}
return 0;
}
......
}
可以看到,我们在一个静态块中对UriMatcher
进行了初始化,并通过addURI
方法添加了四条Uri
数据。在下面的delete
方法中,通过UriMatcher
的match
方法,可以返回匹配Uri
的自定义码。通过这些自定义码,我们就成功解析出Uri
的意图了。在其他方法中,也是使用这种switch-case
的方式对Uri
进行匹配,这里就不再给出示例代码了。
getType
方法用于返回Uri
对应的MIME
类型,Android对于getType
方法返回的数据格式做了相应规定,主要是以下两种格式:
vnd.android.cursor.dir/vnd..
vnd.android.cursor.item/vnd..
如果外界传入的Uri
访问的是某一个表,则采用第一种格式。如果外界传入的Uri
访问的是表中某一个id
对应的数据,则采用第二种格式。示例代码如下:
Uri:content://com.example.providerlibrary.provider/Book
getType:vnd.android.cursor.dir/vnd.com.example.providerlibrary.provider.Book
Uri:content://com.example.providerlibrary.provider/Book/1
getType:vnd.android.cursor.item/vnd.com.example.providerlibrary.provider.Book
既然外界可以通过ContentProvider
访问我们应用中的数据,那么如何防止隐私数据泄露就成了一个重要的问题。不过得益于ContentProvider
良好的设计,这一点我们已经不需要担心了。可以看到,ContentProvider
中访问数据的所有方法都需要先进行Uri
的匹配,只要我们不把隐私数据对应的Uri
加入UriMatcher
中,外界是无论如何都无法访问到这些数据的。
上文大致讲解了一下ContentProvider
的使用,接下来给出一个具体的例子。需要注意的是,在这个例子中需要使用SQLite
数据库,关于SQLite
的使用可以参考这篇博客:
详解Android中的SQLite数据库存储
public class BookContentProvider extends ContentProvider {
private static final int BOOK=0;
private static final int BOOK_ITEM=1;
private static final int AUTHOR=2;
private static final int AUTHOR_ITEM=3;
private static final String AUTHORITY="com.example.providerlibrary.provider";
private BookOpenHelper bookOpenHelper;
private static UriMatcher uriMatcher;
static{//初始化
uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"Book",BOOK);
uriMatcher.addURI(AUTHORITY,"Book/#",BOOK_ITEM);
uriMatcher.addURI(AUTHORITY,"Author",AUTHOR);
uriMatcher.addURI(AUTHORITY,"Author/#",AUTHOR_ITEM);
}
@Override
public boolean onCreate() {
bookOpenHelper=new BookOpenHelper(getContext(),"ProviderDemo.db",null,1);
return true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase database=bookOpenHelper.getWritableDatabase();
long dataId=0;//将数据插入表中后的数据id
Uri insertUri=null;
switch(uriMatcher.match(uri)){
case BOOK:
case BOOK_ITEM:
dataId=database.insert("Book",null,values);
insertUri=Uri.parse("content://com.example.providerlibrary.provider/Book/"+dataId);
break;
case AUTHOR:
case AUTHOR_ITEM:
dataId=database.insert("Author",null,values);
insertUri=Uri.parse("content://com.example.providerlibrary.provider/Author/"+dataId);
break;
default:
break;
}
return insertUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase database=bookOpenHelper.getWritableDatabase();
int deleteNum=0;//被删除的数据条数
switch(uriMatcher.match(uri)){
case BOOK:
//访问Book表
deleteNum=database.delete("Book",selection,selectionArgs);
break;
case BOOK_ITEM:
//访问Book表中的某一条数据
String bookDeleteId=uri.getPathSegments().get(1);//被删除数据的id
deleteNum=database.delete("Book","id=?",new String[]{bookDeleteId});
break;
case AUTHOR:
//访问Author表
deleteNum=database.delete("Author",selection,selectionArgs);
break;
case AUTHOR_ITEM:
//访问Author表中的某一条数据
String authorDeleteId=uri.getPathSegments().get(1);//被删除数据的id
deleteNum=database.delete("Author","id=?",new String[]{authorDeleteId});
break;
default:
break;
}
return deleteNum;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase database=bookOpenHelper.getWritableDatabase();
int updateNum=0;//被更新的数据条数
switch (uriMatcher.match(uri)){
case BOOK:
updateNum=database.update("Book",values,selection,selectionArgs);
break;
case BOOK_ITEM:
String bookUpdateId=uri.getPathSegments().get(1);//被更新的数据id
updateNum=database.update("Book",values,"id=?",new String[]{bookUpdateId});
break;
case AUTHOR:
updateNum=database.update("Author",values,selection,selectionArgs);
break;
case AUTHOR_ITEM:
String authorUpdateId=uri.getPathSegments().get(1);
updateNum=database.update("Author",values,"id=?",new String[]{authorUpdateId});
break;
default:
break;
}
return updateNum;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase database=bookOpenHelper.getReadableDatabase();
Cursor cursor=null;//用于返回的Cursor对象
switch (uriMatcher.match(uri)){
case BOOK:
cursor=database.query("Book",projection,selection,selectionArgs,
null,null,sortOrder);
break;
case BOOK_ITEM:
String bookQueryId=uri.getPathSegments().get(1);//用于查询的id
cursor=database.query("Book",projection,"id=?",new String[]{bookQueryId},
null,null,sortOrder);
break;
case AUTHOR:
cursor=database.query("Author",projection,selection,selectionArgs,
null,null,sortOrder);
break;
case AUTHOR_ITEM:
String authorQueryId=uri.getPathSegments().get(1);//用于查询的id
cursor=database.query("Author",projection,"id=?",new String[]{authorQueryId},
null,null,sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public String getType(Uri uri) {
String type="";
switch (uriMatcher.match(uri)){
case BOOK:
type="vnd.android.cursor.dir/vnd."+AUTHORITY+".Book";
break;
case BOOK_ITEM:
type="vnd.android.cursor.item/vnd."+AUTHORITY+".Book";
break;
case AUTHOR:
type="vnd.android.cursor.dir/vnd."+AUTHORITY+".Author";
break;
case AUTHOR_ITEM:
type="vnd.android.cursor.item/vnd."+AUTHORITY+"Author";
break;
default:
break;
}
return type;
}
}
上述例子中的大部分知识点已经在前文讲过了,这里不再赘述。只有一点需要说明,即如何从带有id
的Uri
中解析出id
字符串。可以看到,我们在代码中使用了Uri
的getPathSegments
方法,方法原型如下:
public abstract List getPathSegments();
这个方法可以将Uri
的path
部分按照/
进行分割,并返回一个List
对象。借助这个List
,我们就可以获得id
字符串了。在上面的例子中,返回的List
中的第二个元素就是id
字符串。
下面给出上述例子的demo
下载地址:ProviderDemo