详解ContentProvider的使用

前言

在Android中,应用间的数据共享是一件很常见的事情,典型的场景就是读取手机通讯录。为了实现安全高效地数据共享,Android提供了ContentProvider这一组件,即内容提供器。本文就简单讲解一下ContentProvider的使用。

访问其他应用中的数据

实际上,手机中本就存在一些内容提供器,学会如何通过这些内容提供器访问其他应用中的数据也十分重要。其实只需要借助ContentResolver,我们就可以很方便地访问其他应用提供的共享数据。要想获得一个ContentResolver实例,只要调用ContextgetContentResolver方法即可,该方法的原型如下:

public ContentResolver getContentResolver()

内容Uri

需要注意的是,内容提供器通过Uri对象来区分想要访问的数据,而不是数据库中的表名。原因也很明显,在多个应用中可能具有相同的表名,因此表名无法充当唯一标识。Uri字符串由两部分组成,即authoritypathauthority一般由应用包名+.provider构成,path则是我们想要访问的数据表名。当然,为了表明这是一条Uri字符串,往往还要在头部添加content://。一个典型的例子如下:

content://com.example.providerlibrary.provider/Book

上面这条Uri字符串表示想要访问providerlibrary中的Book表。当然,仅有一条Uri字符串是不够的,我们还需要通过Uriparse方法将字符串解析为Uri对象。通过Uri对象,就可以访问其他应用中的共享数据了。parse方法原型如下:

public static Uri parse(String uriString)

添加数据

添加数据的操作由ContentResolverinsert方法实现,其原型如下:

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);

删除数据

删除数据的操作由ContentResolverdelete方法实现,其原型如下:

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“的数据。

更新数据.

更新数据的操作由ContentResolverupdate方法实现,其原型如下:

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“。

查询数据

查询数据的操作由ContentResolverquery方法实现,其原型如下:

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”的数据,并且只返回它们authorprice字段的内容,最后还对返回结果进行了默认排序。通过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中的主要方法是addURImatch,它们的原型如下:

public void addURI(String authority, String path, int code)
public int match(Uri uri)

addURI方法用于向UriMatcher中添加能够匹配的Uri。第一、二个参数分别是authoritypath,第三个参数是自定义码。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方法中,通过UriMatchermatch方法,可以返回匹配Uri的自定义码。通过这些自定义码,我们就成功解析出Uri的意图了。在其他方法中,也是使用这种switch-case的方式对Uri进行匹配,这里就不再给出示例代码了。

如何实现getType方法

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;
    }
}

上述例子中的大部分知识点已经在前文讲过了,这里不再赘述。只有一点需要说明,即如何从带有idUri中解析出id字符串。可以看到,我们在代码中使用了UrigetPathSegments方法,方法原型如下:

public abstract List getPathSegments();

这个方法可以将Uripath部分按照/进行分割,并返回一个List对象。借助这个List,我们就可以获得id字符串了。在上面的例子中,返回的List中的第二个元素就是id字符串。

项目demo下载地址

下面给出上述例子的demo下载地址:ProviderDemo

你可能感兴趣的:(Android基础)