前言
在Android中,应用间的数据共享是一件很常见的事情,典型的场景就是读取手机通讯录。为了实现安全高效地数据共享,Android提供了ContentProvider这一组件,即内容提供器。本文就简单讲解一下ContentProvider的使用。
访问其他应用中的数据
实际上,手机中本就存在一些内容提供器,学会如何通过这些内容提供器访问其他应用中的数据也十分重要。其实只需要借助ContentResolver,我们就可以很方便地访问其他应用提供的共享数据。要想获得一个ContentResolver实例,只要调用Context的getContentResolver方法即可,该方法的原型如下:
public ContentResolver getContentResolver()
内容Uri
需要注意的是,内容提供器通过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个方法。它们的作用如下:
- onCreate:在内容提供器初始化的时候会被调用,返回true代表初始化成功,返回fasle则代表初始化时失败。只要外界有
ContentResolver在试图访问应用,内容提供器就会进行初始化。 - insert、delete、update、query:增删改查。
- getType:返回
Uri对应数据的MIME类型字符串,具体细节将在后文详述。
insert、delete、update、query这四个方法的参数和返回值已经在ContentResolver部分进行了讲解,这里就不再赘述了。
如何匹配Uri
可以看到,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方法
getType方法用于返回Uri对应的MIME类型,Android对于getType方法返回的数据格式做了相应规定,主要是以下两种格式:
vnd.android.cursor.dir/vnd.<authority>.<path>
vnd.android.cursor.item/vnd.<authority>.<path>
如果外界传入的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的使用可以参考这篇博客:
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<String> getPathSegments();
这个方法可以将Uri的path部分按照/进行分割,并返回一个List<String>对象。借助这个List,我们就可以获得id字符串了。在上面的例子中,返回的List中的第二个元素就是id字符串。
项目demo下载地址
下面给出上述例子的demo下载地址:ProviderDemo
本文详细介绍了Android中的ContentProvider组件,包括如何使用ContentResolver访问其他应用中的数据、自定义内容提供器的方法,以及如何防止隐私数据泄露等内容。
8983

被折叠的 条评论
为什么被折叠?



