我们通过一个Content Provider小例子进行详细说明。数据源是一个SQLite数据库,名字为books.db,该数据库只含有一个表格,名字为books。表格中含有name,isbn,author,created_date和modified_date几列。我们通过一个名为BookProvider的内容提供者将数据源运行封装,并对外提供增删改查的接口。

首先:定义Content Provider的结构

创建一个Provider,我们首先需要定义好这个provider的结构。通过constant的定义,阐明接口的含义。本例在BookProviderMetaData类中定义,包括数据库,URI,列名等等,具体如下:

/* 定义一个类专门给出provider结构,以常量方式给出,根据具体的数据源结构,数据库对应一个authority,而不同的表格为不同的page segment,本例只有一个表格。本例为二层结构。Content://authority/table_name/book_item */
public class BookProviderMetaData
 { 
    /* 步骤1:定义content://authority以及对应数据源(db)的相关属性 */
    /* 步骤1.1 定义provider的内容:以constant的方式定义authority*/
    public static final String AUTHORITY = "cn.flowingflying.provider.BookProvider";
    /* 步骤1.2 定义数据源的相关信息,本例对相关的SQLite数据库进行定义 */
    public static final String DATABASE_NAME = "books.db"; 
    public static final int DATABASE_VERSION = 1; 
    public static final String BOOKS_TABLE_NAME = "books"; 
    /* 步骤1.3 定义私有的构造函数,目的是本类只进行结构描述,只用于定义引用,不能对本类创建对象 */
    private BookProviderMetaData(){}; 
    
    /* 步骤2:采用内部类定义content://authority/page_segment、具体的item和对应数据源(table),本例只有一个表格,其uri的引用方式为BookProviderMetaData.BookTableMetaData.CONTENT_URI。SQLite的表格实现BaseColumns接口,其中_id是该接口自带的,常量为_ID,每行由系统自动生成唯一的_id */
   public static final class BookTableMetaData implements BaseColumns{ 
        /* 步骤2.3 定义私有构造类,表明本类制作定义引用,不生成对象 */
        private BookTableMetaData(){}; //不应该进行该对象的创建。只用于描述
       /* 步骤2.1 定义provider的结构,本层结构已经映射到具体的item,定义相关的uri和MIME*/
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/books");
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.cn.flowingflying.book";
        public static final String CONTENT_ITEM_TYPE="vnd.android.cursor.item/vnd.cn.flowingflying.book";
       /* 步骤2.2 定义数据源的相关的结构,即表格的相关信息*/ 
        public static final String TABLE_NAME = "books";
        public static final String BOOK_NAME = "name"; 
        public static final String BOOK_ISDN = "isbn"; 
        public static final String BOOK_AUTHOR = "author"; 
        public static final String CREATED_DATE = "created"; 
        public static final String MODIFIED_DATE = "modified"; 
        public static final String DEFAULE_SORT_ORDER = "modified DESC"; 
    } 
}

其次:继承ContentProvider抽象类,实现我们自己的Provider

BookProvider是android.content.ContentProvider的extend,需要对创建onCreate()、获取类型getType(),以及数据的增删改查 query(),add(),delete(),update进行重写。

/* 创建我们的BookProvider,先对数据源进行处理,为下一步对provider提供的增删改查等接口进行准备,即先处理数据源,然后对数据源的I/O接口(provider)进行处理。 */
public class BookProvider extends ContentProvider{     
    /* 步骤3: setup projection Map,将Provider对外开放的projection名字与内部数据源数据库的列名进行对应。通过HashMap将provider的对外公布名字(即projection)和实际数据源的内部名字进行映射。目的是提供一个稳定provider接口。当数据源发生变化时,例如变更了表格的列名,这是只需要修改相应的映射关系,仍能保持对外接口的一致,不影响通过provider访问数据的其他应用。同时也可用于消除起义,例如数据库列名为name,可以定义projection的名字为people.name。这个映射关系在QueryBuilder类,通过setProjectMap()设定,见query()的实现*/
    private static HashMap<String,String> sBooksProjectionMap; 
    static{ 
        sBooksProjectionMap = new HashMap<String, String>(); 
        sBooksProjectionMap.put(BookTableMetaData._ID, BookTableMetaData._ID); 
        sBooksProjectionMap.put(BookTableMetaData.BOOK_NAME, BookTableMetaData.BOOK_NAME);
        sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN, BookTableMetaData.BOOK_ISBN);
        sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR, BookTableMetaData.BOOK_AUTHOR);
        sBooksProjectionMap.put(BookTableMetaData.CREATED_DATE, BookTableMetaData.CREATED_DATE);
        sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE, BookTableMetaData.MODIFIED_DATE);
    } 
    
    /* 步骤1:对数据源进行处理,本例采用内部类方式。本例数据源采用SQLite,SQLite通过对SQLiteOpenHelper的继承来实现,可以参考Android学习笔记(四一):SQLite的使用 , 必须重写onCreate()和onUpgrade()以及构造函数 */ 
    private class DatabaseBookHelper extends SQLiteOpenHelper{ 
        /* 步骤1.1 重写构造函数,给出数据库信息 */ 
        DatabaseBookHelper(Context context){ 
            super(context,BookProviderMetaData.DATABASE_NAME,null,BookProviderMetaData.DATABASE_VERSION);
        } 
        
       /* 步骤1.2:@Override onCreate(),通CREATE TABLE创建数据库的表格*/
        public void onCreate(SQLiteDatabase db) { 
            db.execSQL("CREATE TABLE " + BookProviderMetaData.BOOKS_TABLE_NAME + " ("
                    + BookTableMetaData._ID + " INTEGER PRIMARY KEY," 
                    + BookTableMetaData.BOOK_NAME + " TEXT," 
                    + BookTableMetaData.BOOK_ISBN + " TEXT," 
                    + BookTableMetaData.BOOK_AUTHOR + " TEXT," 
                    + BookTableMetaData.CREATED_DATE + " INTEGER," 
                    + BookTableMetaData.MODIFIED_DATE + " INTEGER" 
                    + ");"); 
        }

/* 步骤1.3:@Override onUpgrade,当发现数据库的版本低于当前版本时,对数据库进行升级,本例将删除原来的 全部数据,重新建立新的表格*/
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL("DROP TABLE IF EXISTS " + BookProviderMetaData.BOOKS_TABLE_NAME);
            onCreate(db); 
        } 
        
    } //End of 步骤1

private DatabaseBookHelper mDataSource = null; //步骤2:设置并获取数据源对象 
    
   /* 步骤4:提供判读输入uri样式的机制。 
     * 通过UriMatch对input的Uri进行匹配,判断uri是否合法,是collection还是item 
     * 在uriMatch对象上通过addURI登记uri的样式和一个唯一的number,当匹配时,uriMatch将返回该number。*/ 

    private static final UriMatcher sUriMatch; 
    // 定义匹配时返回的number,本例只有1个table,故有collection和item两种。
    private static final int BOOK_COLLECTION_URI_INDICATOR = 1; 
    private static final int BOOK_ITEM_URI_INDICATION = 2; 
    static{ 
        // UriMatther的创建函数可以含有如果既没有path_segment也没有authorities时返回的number。
        // 此外如果不匹配,UriMatch也会返回NO_MATCH,本例也可以写成sUriMatch = new UriMatcher(); 
        sUriMatch = new UriMatcher(UriMatcher.NO_MATCH); 
        // 登记uri的样式及其对应的number值 
        sUriMatch.addURI(BookProviderMetaData.AUTHORITY, "books", BOOK_COLLECTION_URI_INDICATOR);
        sUriMatch.addURI(BookProviderMetaData.AUTHORITY, "books/#", BOOK_ITEM_URI_INDICATION);
    } 
        
    @Override 
    public int delete(Uri uri, String where, String[] whereArgs) { 
        return 0; 
    }

@Override 
    public String getType(Uri uri) { 
        return null; 
    }

@Override 
    public Uri insert(Uri uri, ContentValues initalValue) { 
        return null; 
    }

/* 步骤2:关联数据源,本例创建数据库的对象 */   
    public boolean onCreate() { 
        mDataSource = new DatabaseBookHelper(getContext()); 
        return false; 
    }

@Override 
    public Cursor query(Uri uri, String[] protection, String selection, String[] selectionArgs, String order) {
        return null; 
    }

@Override 
    public int update(Uri uri, ContentValues values, String whereClause, String[] whereArgs) {
        return 0; 
    }     
}

第三:实现BookProvider的询问MIME,以及数据的增删改查

(1)获取数据类型,重写getType()

/* 通过getType(),Provider对外提供uri获取的数据类型(MIME Type),分为collection和item两种情况。*/
public String getType(Uri uri)
 { 
    switch(sUriMatch.match(uri)){ 
    case BOOK_COLLECTION_URI_INDICATOR: 
        return BookTableMetaData.CONTENT_TYPE; 
    case BOOK_ITEM_URI_INDICATION: 
        return BookTableMetaData.CONTENT_ITEM_TYPE; 
    default:   //无效uri 
        throw new IllegalArgumentException("Unknown Uri " + uri); 
    } 
}

(2)数据查询,重写query(),返回游标

public Cursor query(Uri uri, String[] protection, String selection, String[] selectionArgs, String order) {
    //1、通过SQLiteQueryBuilder,设置数据库查询的信息。Uri有两种情况,一种是collect,一种已经指定某个item,   两者需要区别对待,item将获取_ID,并在where中增加一个匹配条件。   
    SQLiteQueryBuilder dbQuery = new SQLiteQueryBuilder(); 
    switch(sUriMatch.match(uri)){ 
    case BOOK_COLLECTION_URI_INDICATOR: 
        dbQuery.setTables(BookTableMetaData.TABLE_NAME); 
        dbQuery.setProjectionMap(sBooksProjectionMap); //设定与project的映射关系 
        break; 
    case BOOK_ITEM_URI_INDICATION: 
        dbQuery.setTables(BookTableMetaData.TABLE_NAME); 
        dbQuery.setProjectionMap(sBooksProjectionMap);  //设定与project的映射关系 
       // 通过getPathSegments( )获取具体的ID,path segment从0还是计算,本例中0为books,1为#
        dbQuery.appendWhere(BookTableMetaData._ID + "=" + uri.getPathSegments().get(1));
        break; 
    default: 
        throw new IllegalArgumentException("Unknown Uri " + uri); 
    } 
    //2、对缺省进行设置,如排序 
    String orderBy; 
    if(TextUtils.isEmpty(order)){ 
        orderBy = BookTableMetaData.DEFAULE_SORT_ORDER; 
    }else{ 
        orderBy = order; 
    } 
   //3、查询数据库 
    SQLiteDatabase db = mDataSource.getReadableDatabase(); 
    Cursor c = dbQuery.query(db, protection, selection, selectionArgs, null, null, orderBy);
    
    //4、向系统注册通知:观察所要查询的数据,即Uri对应的数据,是否发生变化。开发者通过provider接口获取数据, 可通过通知获知数据已经发生变更。
    c.setNotificationUri(getContext().getContentResolver(), uri); 
    return c; 
}

(3)数据增加,重写insert(),返回新增数据的uri

public Uri insert(Uri uri, ContentValues initalValue) { 
   //1、检查uri是否合法,由于新增item,故uri只能是collection方式 
    if(sUriMatch.match(uri)  != BOOK_COLLECTION_URI_INDICATOR){ 
        throw new IllegalArgumentException("Unknown Uri " + uri); 
    }

//2、从ContentValues中获取数据,检查数据是否有效和完备 
    ContentValues cv ; 
    if(initalValue == null){   //允许插入空的一行数据 
        cv = new ContentValues(); 
    }else{ 
        cv = new ContentValues(initalValue); 
    } 
   //检查数据是否缺失,并填入缺省值
    if(!cv.containsKey(BookTableMetaData.BOOK_NAME)){ 
        throw new SQLException("Failed to insert row because Book Name is needed " + uri);
    } 
    if(!cv.containsKey(BookTableMetaData.BOOK_ISBN)){ 
        cv.put(BookTableMetaData.BOOK_ISBN, "Unknown ISBN"); 
    } 
    if(!cv.containsKey(BookTableMetaData.BOOK_AUTHOR)){ 
        cv.put(BookTableMetaData.BOOK_AUTHOR, "Unknown author"); 
    }        
    Long now = Long.valueOf(System.currentTimeMillis()); 
    if(!cv.containsKey(BookTableMetaData.CREATED_DATE)){ 
        cv.put(BookTableMetaData.CREATED_DATE, now); 
    } 
    if(!cv.containsKey(BookTableMetaData.MODIFIED_DATE)){ 
        cv.put(BookTableMetaData.MODIFIED_DATE, now); 
    }

//3、在数据库表格中增加row 
    SQLiteDatabase db = mDataSource.getWritableDatabase(); 
    long rowId = db.insert(BookTableMetaData.TABLE_NAME, BookTableMetaData.BOOK_NAME, cv);
    if(rowId > 0){ 
        Uri insertUri = ContentUris.withAppendedId(BookTableMetaData.CONTENT_URI, rowId);
        //4、通知该uri的数据发生变化 
        getContext().getContentResolver().notifyChange(insertUri, null); 
        return insertUri; 
    } 
    throw new SQLException("Failed to insert row into " + uri); 
}

(4)数据删除,重写delete(),返回成功删除的item数目

public int delete(Uri uri, String where, String[] whereArgs) {
    int count = 0; //记录删除item数目 
    SQLiteDatabase db = mDataSource.getWritableDatabase(); 
    //删除:根据uri分为collection和item两种情况 
    switch(sUriMatch.match(uri)){ 
    case BOOK_COLLECTION_URI_INDICATOR: 
        count = db.delete(BookTableMetaData.TABLE_NAME, where, whereArgs); 
        break; 
    case BOOK_ITEM_URI_INDICATION: 
        String id = uri.getPathSegments().get(1); //从0开始计算,本例中0为content://xxxx/books/2中的books 
        count = db.delete(BookTableMetaData.TABLE_NAME, BookTableMetaData._ID + "=" + id
                +(TextUtils.isEmpty(where) ? "" : " AND ("  + where + ")"), whereArgs);
        break; 
    default: 
        throw new IllegalArgumentException("Unknown Uri " + uri); 
    } 
    getContext().getContentResolver().notifyChange(uri, null);  //通知该uri的数据发生变化 
    return count; 
}

(5)数据更改,重写update(),返回成功修改的item数目

public int update(Uri uri, ContentValues values, String whereClause, String[] whereArgs) {
    int count = 0; //记录更新item数目 
    SQLiteDatabase db = mDataSource.getWritableDatabase(); 
    //更新:根据uri分为collection和item两种情况 
    switch(sUriMatch.match(uri)){ 
    case BOOK_COLLECTION_URI_INDICATOR: 
        count = db.update(BookTableMetaData.TABLE_NAME, values, whereClause, whereArgs);
        break; 
    case BOOK_ITEM_URI_INDICATION: 
        count = db.update(BookTableMetaData.TABLE_NAME, values, 
                BookTableMetaData._ID + "=" + uri.getPathSegments().get(1) 
                + (!TextUtils.isEmpty(whereClause)? " AND (" + whereClause + ")" : ""), whereArgs);
        break; 
    default: 
        throw new IllegalArgumentException("Unknown Uri " + uri); 
    } 
    getContext().getContentResolver().notifyChange(uri,null); //通知该uri的数据发生变化 
    return count; 
}

最后:在Manifest上注册BookProvider

<provider android:name=".BookProvider" android:authorities="cn.flowingflying.provider.BookProvider" />

相关链接: 我的Android开发相关文章

转自http://blog.csdn.net/flowingflying/article/details/9242995

【转】Pro Android学习笔记(七):了解Content Provider(下上)的更多相关文章

  1. 【转】 Pro Android学习笔记(六七):HTTP服务(1):HTTP GET

    目录(?)[-] HTTP GET小例子 简单小例子 出现异常NetworkOnMainThreadException 通过StrictMode进行处理 URL带键值对 Andriod应用可利用ser ...

  2. 【转】 Pro Android学习笔记(五五):调试和分析(3):adb命令、模拟器控制台和StrictMode

    目录(?)[-] adb命令 模拟器Console StrictMode adb命令 我们在学习SQLite的使用,介绍过部分adb命令的使用,见Pro Android学习笔记(五):了解Conten ...

  3. 【转】 Pro Android学习笔记(五六):配置变化

    目录(?)[-] Activity的destorycreate过程 Fragment的destorycreate过程 onSaveInstanceState saveFragmentInstanceS ...

  4. 【转】 Pro Android学习笔记(四十):Fragment(5):适应不同屏幕或排版

    目录(?)[-] 设置横排和竖排的不同排版风格 改写代码 对于fragment,经常涉及不同屏幕尺寸和不同的排版风格.我们在基础小例子上做一下改动,在横排的时候,仍是现实左右两个fragment,在竖 ...

  5. 【转】 Pro Android学习笔记(二二):用户界面和控制(10):自定义Adapter

    目录(?)[-] 设计Adapter的布局 代码部分 Activity的代码 MyAdapter的代码数据源和构造函数 MyAdapter的代码实现自定义的adapter MyAdapter的代码继续 ...

  6. 【转】 Pro Android学习笔记(十九):用户界面和控制(7):ListView

    目录(?)[-] 点击List的item触发 添加其他控件以及获取item数据 ListView控件以垂直布局方式显示子view.系统的android.app.ListActivity已经实现了一个只 ...

  7. Pro Android学习笔记 ActionBar(1):Home图标区

     Pro Android学习笔记(四八):ActionBar(1):Home图标区 2013年03月10日 ⁄ 综合 ⁄ 共 3256字 ⁄ 字号 小 中 大 ⁄ 评论关闭 ActionBar在A ...

  8. 【转】 Pro Android学习笔记(八二):了解Package(1):包和进程

    文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowingflying/ 在之前,我们已经学习了如何签发apk,见P ...

  9. 【转】 Pro Android学习笔记(五二):ActionBar(5):list模式

    可以在action bar中加入spinner的下来菜单,有关spinner,可以参考Pro Android学习笔记(二十):用户界面和控制(8):GridView和Spinner. list的样式和 ...

  10. 【转】 Pro Android学习笔记(四二):Fragment(7):切换效果

    目录(?)[-] 利用setTransition 利用setCustomAnimations 通过ObjectAnimator自定义动态效果 程序代码的编写 利用fragment transactio ...

随机推荐

  1. Linux下Kafka单机安装配置方法

    Kafka是一个分布式的.可分区的.可复制的消息系统.它提供了普通消息系统的功能,但具有自己独特的设计.这个独特的设计是什么样的呢? 首先让我们看几个基本的消息系统术语: •Kafka将消息以topi ...

  2. Java 获取本地IP地址

    private static String getIpAddress( ){ String ip = ""; Collection<InetAddress> colIn ...

  3. eclipse maven 刷新报错

    问题描述: An internal error occurred during: "Loading descriptor for cmbc_wms.".java.lang.Null ...

  4. python 基础 1.3 使用pycharm给python传递参数及pycharm调试模式

    一.通过pycharm 给python传递函数 1. 在pycharm终端中写入要获取的参数,进行获取 1>启动pycharm 中Terminal(终端) 窗口 点击pycharm左下角的图标, ...

  5. EasyPlayerPro RTMP播放器助力远程娃娃机直播抓娃娃技术方案

    远程娃娃机 目前市面上娃娃机的方案有很多种.核心的技术流程就是实现远程直播加上对娃娃机手臂的远程操作.其中最主要的技术还是视频直播方案,需要低延时,视频秒开等流媒体技术. 最简单的直播方案 视频直播方 ...

  6. Vue 父子通话

    dom: <app-entry ref="child" :id='entryFaid'  v-on:refreshbizlines="EntryUpdateData ...

  7. 搭建SVN服务器详细教程

    搭建SVN服务器详细教程 本教程会从最基本的下载安装到上传代码,下载代码这条线来详细讲述如何完成SVN服务器的搭建 下载并安装VisualSVN server 下载并安装TortoiseSVN 导入项 ...

  8. Nginx Cache中$request_filename(转)

    对于Nginx的$request_filename变量指的就是请求的资源路径.在原先 OpenCDN节点端配置里面是这样的. location ~ .*\.(png|html|htm|ico|jpg| ...

  9. CUDA: 流

    1. 页锁定主机内存 c库函数malloc()分配标准的,可分页(Pagable)的内存,cudaHostAlloc()分配页锁定的主机内存.页锁定内存也称为固定内存(Pinned Memory)或者 ...

  10. 后端CORS解决跨域问题

    一 . 为什么会有跨域问题 是因为浏览器的同源策略是对ajax请求进行阻拦了,但是不是所有的请求都给做跨域,像是一般的href 属性,a标签什么的都不拦截. 二 . 解决跨域的方法 解决跨域有两种方法 ...