android ContentProvider使用详解

标签: android contentprovider | 发表时间:2013-02-27 22:53 | 作者:zhoujianghai
出处:http://blog.csdn.net
由于之前主要做手机游戏相关的开发,所以ContentProvider了解的不多,今天就来学习一下。
1. 首先来了解一下ContentProvider是什么?它的作用是什么?
ContentProvider是Android的四大组件之一,可见它在Android中的作用非同小可。它主要的作用是:实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者SQLite等。
2. 那ContentProvider是怎么实现数据共享的呢?
下面先来了解一下这几个东东:
(1) URI

URI:统一资源标识符,代表要操作的数据,可以用来标识每个ContentProvider,这样你就可以通过指定的URI找到想要的ContentProvider,从中获取或修改数据。在Android中URI的格式如下图所示(图片来自于网络):


主要分三个部分:scheme, authority and path。scheme表示上图中的content://,authority表示B部分,path表示C和D部分。
A部分:表示是一个Android内容URI,说明由ContentProvider控制数据,该部分是固定形式,不可更改的。
B部分:是URI的授权部分,是唯一标识符,用来定位ContentProvider。格式一般是自定义ContentProvider类的完全限定名称,注册时需要用到,如:com.alexzhou.provider.NoteProvider
C部分和D部分:是每个ContentProvider内部的路径部分,C和D部分称为路径片段,C部分指向一个对象集合,一般用表的名字,如:/notes表示一个笔记集合;D部分指向特定的记录,如:/notes/1表示id为1的笔记,如果没有指定D部分,则返回全部记录。
在开发中通常使用常量来定义URI,如下面的CONTENT_URI:

public static final String AUTHORITY = "com.alexzhou.provider.NoteProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/notes");

(2) MIME
MIME是指定某个扩展名的文件用一种应用程序来打开,就像你用浏览器查看PDF格式的文件,浏览器会选择合适的应用来打开一样。Android中的工作方式跟HTTP类似,ContentProvider会根据URI来返回MIME类型,ContentProvider会返回一个包含两部分的字符串。MIME类型一般包含两部分,如:
text/html
text/css
text/xml
application/pdf
等,分为类型和子类型,Android遵循类似的约定来定义MIME类型,每个内容类型的Android MIME类型有两种形式:多条记录(集合)和单条记录。
多条记录
vnd.android.cursor.dir/vnd.alexzhou.note
单条记录
vnd.android.cursor.item/vnd.alexzhou.note
vnd表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型vnd.之后的内容可以按照格式随便填写。在使用Intent时,会用到MIME这玩意,根据Mimetype打开符合条件的活动。

3. 接下来通过一个简单的demo,来学习怎么创建自定义的ContentProvider,这里数据源选用SQLite,最常用的也是这个。
(1) 创建一个类NoteContentProvider,继承ContentProvider,需要实现下面5个方法:
query
insert
update
delete
getType

这些方法是eclipse自动生成的,暂时先不动他们。

/**
author:alexzhou 
email :[email protected]
date  :2013-2-25
 **/
 
public class NoteContentProvider extends ContentProvider {
 
    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        return false;
    }
 
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        // TODO Auto-generated method stub
        return null;
    }
 
    @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        return null;
    }
 
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub
        return null;
    }
 
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }
 
    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }
 
}
(2)先来设计一个数据库,用来存储笔记信息,主要包含_ID,title,content,create_date四个字段。创建NoteProviderMetaData类,封装URI和数据库、表、字段相关信息,源码如下:
/**
author:alexzhou 
email :[email protected]
date  :2013-2-25
 **/
 
public class NoteProviderMetaData {
 
    public static final String AUTHORITY = "com.alexzhou.provider.NoteProvider";
 
    public static final String DATABASE_NAME = "note.db";
    public static final int DATABASE_VERSION = 1;
 
    /**
     * 
     *数据库中表相关的元数据
     */
    public static final class NoteTableMetaData implements BaseColumns {
 
        public static final String TABLE_NAME = "notes";
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/notes");
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.alexzhou.note";
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.alexzhou.note";
 
        public static final String NOTE_TITLE = "title";
        public static final String NOTE_CONTENT = "content";
        public static final String CREATE_DATE = "create_date";
 
        public static final String DEFAULT_ORDERBY = "create_date DESC";
 
        public static final String SQL_CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
                                        + _ID + " INTEGER PRIMARY KEY,"
                                        + NOTE_TITLE + " VARCHAR(50),"
                                        + NOTE_CONTENT + " TEXT,"
                                        + CREATE_DATE + " INTEGER"
                                        + ");" ;
    }
}

AUTHORITY代表授权,该字符串和在Android描述文件AndroidManifest.xml中注册该ContentProvider时的android:authorities值一样,NoteTableMetaData继承BaseColumns,后者提供了标准的_id字段,表示行ID。
(3) ContentProvider是根据URI来获取数据的,那它怎么区分不同的URI呢,因为无论是获取笔记列表还是获取一条笔记都是调用query方法,现在来实现这个功能。需要用到类UriMatcher,该类可以帮助我们识别URI类型,下面看实现源码:
private static final UriMatcher sUriMatcher;
private static final int COLLECTION_INDICATOR = 1;
private static final int SINGLE_INDICATOR = 2;
 
static {
    sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    sUriMatcher.addURI(NoteProviderMetaData.AUTHORITY, "notes", COLLECTION_INDICATOR);
    sUriMatcher.addURI(NoteProviderMetaData.AUTHORITY, "notes/#", SINGLE_INDICATOR);
}

这段代码是NoteContentProvider类中的,UriMatcher的工作原理:首先需要在UriMatcher中注册URI模式,每一个模式跟一个唯一的编号关联,注册之后,在使用中就可以根据URI得到对应的编号,当模式不匹配时,UriMatcher将返回一个NO_MATCH常量,这样就可以区分了。
(4) 还需为查询设置一个投影映射,主要是将抽象字段映射到数据库中真实的字段名称,因为这些字段有时是不同的名称,既抽象字段的值可以不跟数据库中的字段名称一样。这里使用HashMap来完成,key是抽象字段名称,value对应数据库中的字段名称,不过这里我把两者的值设置是一样的,在NoteContentProvider.java中添加如下代码。
private static HashMap<String, String> sNotesProjectionMap;
static {
    sNotesProjectionMap = new HashMap<String, String>();
    sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData._ID, NoteProviderMetaData.NoteTableMetaData._ID);
    sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData.NOTE_CONTENT, NoteProviderMetaData.NoteTableMetaData.NOTE_CONTENT);
    sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData.NOTE_TITLE, NoteProviderMetaData.NoteTableMetaData.NOTE_TITLE);
    sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData.CREATE_DATE, NoteProviderMetaData.NoteTableMetaData.CREATE_DATE);
}

(5) 在NoteContentProvider.java中创建一个内部类DatabaseHelper,继承自SQLiteOpenHelper,完成数据库表的创建、更新,这样可以通过它获得数据库对象,相关代码如下。
private DatabaseHelper mDbHelper;
 
private static class DatabaseHelper extends SQLiteOpenHelper {
 
    public DatabaseHelper(Context context) {
        super(context, NoteProviderMetaData.DATABASE_NAME, null, NoteProviderMetaData.DATABASE_VERSION);
    }
 
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.e("DatabaseHelper", "create table: " + NoteProviderMetaData.NoteTableMetaData.SQL_CREATE_TABLE);
        db.execSQL(NoteProviderMetaData.NoteTableMetaData.SQL_CREATE_TABLE);
    }
 
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + NoteProviderMetaData.NoteTableMetaData.TABLE_NAME);
        onCreate(db);
    }
 
}

(6) 现在来分别实现第一步中未实现的5个方法,先来实现query方法,这里借助SQLiteQueryBuilder来为查询设置投影映射以及设置相关查询条件,看源码实现:
public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    switch(sUriMatcher.match(uri)) {
    case COLLECTION_INDICATOR:
        // 设置查询的表
        queryBuilder.setTables(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME);
        // 设置投影映射
        queryBuilder.setProjectionMap(sNotesProjectionMap);
        break;
 
    case SINGLE_INDICATOR:
        queryBuilder.setTables(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME);
        queryBuilder.setProjectionMap(sNotesProjectionMap);
        queryBuilder.appendWhere(NoteProviderMetaData.NoteTableMetaData._ID + "=" + uri.getPathSegments().get(1));
        break;
 
    default:
        throw new IllegalArgumentException("Unknow URI: " + uri);
    }
 
    String orderBy;
    if(TextUtils.isEmpty(sortOrder))
    {
        orderBy = NoteProviderMetaData.NoteTableMetaData.DEFAULT_ORDERBY;
    } else {
        orderBy = sortOrder;
    }
    SQLiteDatabase db = mDbHelper.getReadableDatabase();
    Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, orderBy);
 
    return cursor;
}

返回的是一个Cursor对象,它是一个行集合,包含0和多个记录,类似于JDBC中的ResultSet,可以前后移动游标,得到每行每列中的数据。注意的是,使用它需要调用moveToFirst(),因为游标默认是在第一行之前。
(7)实现insert方法,实现把记录插入到基础数据库中,然后返回新创建的记录的URI。
public Uri insert(Uri uri, ContentValues values) {        
    if (sUriMatcher.match(uri) != COLLECTION_INDICATOR) {
        throw new IllegalArgumentException("Unknown URI " + uri);
    }
 
    SQLiteDatabase db = mDbHelper.getWritableDatabase();
    long rowID = db.insert(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, null, values);
 
    if(rowID > 0) {
        Uri retUri = ContentUris.withAppendedId(NoteProviderMetaData.NoteTableMetaData.CONTENT_URI, rowID);
        return retUri;
    }
 
    return null;
}

(8) 实现update方法,根据传入的列值和where字句来更新记录,返回更新的记录数,看源码:
@Override
public int update(Uri uri, ContentValues values, String selection,
        String[] selectionArgs) {
    SQLiteDatabase db = mDbHelper.getWritableDatabase();
    int count = -1;
    switch(sUriMatcher.match(uri)) {
    case COLLECTION_INDICATOR:
        count = db.update(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, values, null, null);
        break;
 
    case SINGLE_INDICATOR:
        String rowID = uri.getPathSegments().get(1);
        count = db.update(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, values, NoteProviderMetaData.NoteTableMetaData._ID + "=" + rowID, null);
        break;
 
    default:
        throw new IllegalArgumentException("Unknow URI : " + uri);
 
    }
    this.getContext().getContentResolver().notifyChange(uri, null);
    return count;
}

notifyChange函数是在更新数据时,通知其他监听对象。
(9)实现delete方法,该方法返回删除的记录数。
public int delete(Uri uri, String selection, String[] selectionArgs) {
    SQLiteDatabase db = mDbHelper.getWritableDatabase();
    int count = -1;
    switch(sUriMatcher.match(uri)) {
    case COLLECTION_INDICATOR:
        count = db.delete(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, selection, selectionArgs);
        break;
 
    case SINGLE_INDICATOR:
        String rowID = uri.getPathSegments().get(1);
        count = db.delete(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, NoteProviderMetaData.NoteTableMetaData._ID + "=" + rowID, null);
        break;
 
    default:
        throw new IllegalArgumentException("Unknow URI :" + uri);
 
    }
    // 更新数据时,通知其他ContentObserver
    this.getContext().getContentResolver().notifyChange(uri, null);
    return count;
}

(10) 实现getType方法,根据URI返回MIME类型,这里主要用来区分URI是获取集合还是单条记录,这个方法在这里暂时没啥用处,在使用Intent时有用。
public String getType(Uri uri) {
    switch(sUriMatcher.match(uri)) {
        case COLLECTION_INDICATOR:
            return NoteProviderMetaData.NoteTableMetaData.CONTENT_TYPE;
 
        case SINGLE_INDICATOR:
            return NoteProviderMetaData.NoteTableMetaData.CONTENT_ITEM_TYPE;
 
        default:
            throw new IllegalArgumentException("Unknow URI: " + uri);
    }
}

(11) 在AndroidManifest.xml中注册该ContentProvider,这样系统才找得到,当然你也可以设置相关的权限,这里就不设置了。
<provider android:name="NoteContentProvider" android:authorities="com.alexzhou.provider.NoteProvider"/>

到现在为止,自定义ContentProvider的全部代码已经完成,下面创建一个简单的应用来测试一下。
主要测试insert、update、delete、query这四个函数。
1. 现在数据库中没有数据,所以先测试insert,插入一条数据。主要代码如下:
public static final String AUTHORITY = "com.alexzhou.provider.NoteProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/notes");
 
private void insert() {
     ContentValues values = new ContentValues();
     values.put("title", "hello");
     values.put("content", "my name is alex zhou");
     long time = System.currentTimeMillis();
     values.put("create_date", time);
     Uri uri = this.getContentResolver().insert(CONTENT_URI, values);
     Log.e("test ", uri.toString());
    }

这里需要获得CONTENT_URI值,其他的应用可以通过ContentResolver接口访问ContentProvider提供的数据。logcat的输出如下:


因为现在数据库和表还不存在,所以首先会创建表,返回的URI的值是第一条记录的URI。
2. 测试query方法

private void query() {
    Cursor cursor = this.getContentResolver().query(CONTENT_URI, null, null, null, null);
    Log.e("test ", "count=" + cursor.getCount());
    cursor.moveToFirst();
    while(!cursor.isAfterLast()) {
        String title = cursor.getString(cursor.getColumnIndex("title"));
        String content = cursor.getString(cursor.getColumnIndex("content"));
        long createDate = cursor.getLong(cursor.getColumnIndex("create_date"));
        Log.e("test ", "title: " + title);
        Log.e("test ", "content: " + content);
        Log.e("test ", "date: " + createDate);
 
        cursor.moveToNext();
    }
    cursor.close();
}

logcat输入如下,正是刚才插入的值。


3. 测试update方法

private void update() {
    ContentValues values = new ContentValues();
    values.put("content", "my name is alex zhou !!!!!!!!!!!!!!!!!!!!!!!!!!");
    int count = this.getContentResolver().update(CONTENT_URI, values, "_id=1", null);
    Log.e("test ", "count="+count);
    query();
}

logcat输出:


刚才插入的值已被更新了。
(4) 测试delete方法

private void delete() {
    int count = this.getContentResolver().delete(CONTENT_URI, "_id=1", null);
    Log.e("test ", "count="+count);
    query();
}

看logcat输出:


再次查询时,已经没有了数据。

ok,到此为止,应该对ContentProvider的作用和创建一个自定义的ContentProvider基本了解了吧。


转载请注明来自: Alex Zhou的程序世界,本文链接: http://codingnow.cn/android/1078.html

作者:zhoujianghai 发表于2013-2-27 22:53:52 原文链接
阅读:90 评论:0 查看评论

相关 [android contentprovider] 推荐:

Android入门:ContentProvider

- - ITeye博客
一、ContentProvider介绍. ContentProvider翻译为“内容提供者”;. 定义:指该应用包含一些方法,供外界访问,其他应用程序可以调用该方法,比如如果应用A创建了一个数据库“test.db”,默认是私有的,即其他应用程序不能对其进行操作,但是如果应用A使用了ContentProvider,则其他应用程序可以访问该数据库;.

Android ContentProvider总结

- - CSDN博客推荐文章
1) ContentProvider为存储和读取数据提供了统一的接口. 2) 使用ContentProvider,应用程序可以实现数据共享. 3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等). 1)ContentProvider简介.

android ContentProvider使用详解

- - CSDN博客移动开发推荐文章
由于之前主要做手机游戏相关的开发,所以ContentProvider了解的不多,今天就来学习一下. 首先来了解一下ContentProvider是什么. ContentProvider是Android的四大组件之一,可见它在Android中的作用非同小可. 它主要的作用是:实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限.

存储文件的ContentProvider

- - ITeye博客
       基于SQLite的ContentProvider我们见得多了,但是我们在做Android应用时,有时候程序需要下载网络上的图片,这时候我们希望能够把图片缓存到客户端本地,下次再要显示该图片时就不用再从网络上下载了,直接从本地缓存读取,这就需要用到存储文件的ContentProvider.

Android 遥控车

- CasparZ - LinuxTOY
您确定您真的会用 Android 手机玩赛车. 16 岁的法国学生 Jonathan Rico 使用 Android 手机通过蓝牙实现了对改装玩具汽车的遥控. 操控的方式和那些标榜的智能手机游戏一样,使用重力感应,差别是这次控制的是现实世界中的遥控汽车. 收藏到 del.icio.us |.

Android免费?毛

- Ruby - FeedzShare
来自: 36氪 - FeedzShare  . 发布时间:2011年08月17日,  已有 2 人推荐. 微软CEO Steve Ballmer在预测竞争对手产品时通常口无遮拦. 比如他去年抨击Google的Android战略时,很多人都不屑一顾. 接着Android蚕食了微软的地盘,后来又开始侵犯苹果的地盘.

GetEd2k (Android应用)

- 某牢 - eMule Fans 电骡爱好者
GetEd2k是一个Android应用程序,作者是anacletus. 此应用可以帮助你把网页中的电驴(eDonkey) 链接添加到你个人电脑的电驴客户端里,不过前提是你的客户端开启了用于远程控制的Web interface(Web服务器,网页接口,Web界面),当然,eMule(电骡), MLDonkey 和 aMule 都支持该功能,所以这三种主流电驴客户端的用户都可以使用GetEd2k.

Android 4.0发布

- coofucoo - Solidot
Shawn the R0ck 写道 "2011年10月19日早上10点,谷歌与三星联手在香港发布了Android 4.0和Galaxy Nexus. " Android 4.0 的主要特性包括:更精细的UI,加强多任务和通知功能,锁屏下可打开摄像头和浏览通知,改进文本输入和拼写检查;增强视频录制和图像编辑功能,支持剪裁和旋转图片、消除红眼、添加效果等;面部识别解锁;Android Beam允许两台支持NFC的设备之间交换应用程序、联系人、音乐和视频;Wi-Fi Direct,蓝牙HDP,等等.

NoScript For Android发布

- John - Solidot
用于屏蔽脚本的浏览器流行扩展NoScript发布了Android版本. 开发者称已经在Firefox for Android测试过,此外也应该能工作在基于Maemo的设备上. 移动版NoScript可以帮助移动用户抵抗基于脚本的攻击. Android平台上的扩展功能和桌面版相似,允许用户对每个网站单独设置脚本执行许可.