Android数据存储操作⑥ContentProvider、ContentResolver
ContentProvider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。
也就是说,一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据暴露出去。
外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,外界可以通过这一套标准及统一的接口和程序里的数据打交道,其常见方法如下:
• query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri 进行查询,返回一个Cursor。
• insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。
• update(Uri uri, ContentValues values, String where, String[] selectionArgs) 更新Uri 指定位置的数据。
• delete(Uri url, String where, String[] selectionArgs) 删除指定Uri 并且符合一定条件的数据
外界的程序通过ContentResolver 接口可以访问ContentProvider 提供的数据,在Activity 当中通过getContentResolver()可以得到当前应用的ContentResolver 实例。
其常用方法如下:
• query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri 进行查询,返回一个Cursor。
• insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。
• update(Uri uri, ContentValues values, String where, String[] selectionArgs) 更新Uri 指定位置的数据。
• delete(Uri url, String where, String[] selectionArgs) 删除指定Uri 并且符合一定条件的数据
在ContentProvider 和ContentResolver 当中用到了Uri 的形式通常有两种,一种是指定全部数据,另一种是指定某个ID 的数据。我们看下面的例子。
• content://contacts/people/ 这个 Uri 指定的就是全部的联系人数据。
• content://contacts/people/1 这个 Uri 指定的是ID 为1 的联系人的数据。
在上边两个类中用到的Uri 一般由3 部分组成
• 第一部分是:"content://" 。
• 第二部分是要获得数据的一个字符串片段。
• 最后就是ID(如果没有指定ID,那么表示返回全部)。
实现自定义的ContentProvider步骤及示例代码如下:
(1)继承ContentProvider,本例中为DiaryContentProvider类。
(2)定义一个 public static final 的Uri 类型的变量,并且命名为CONTENT_URI。DiaryContentProvider 所能处理的Uri都是基于CONTENT_URI 来构建的,需要注意的是,这个CONTENT_URI 中的内容以content://开头,并且全部小写,且全局惟一。下边是在这个例子中的一个普通URI,通过分析这个URI,可以了解content URI 的构成,
代码如下所示:content://com.ex09_2_contentprovider.diarycontentprovider/diaries/1
●第一部分是content://,这部分是支持存在的,也是不用做什么修改的。
●第二部分是授权(AUTHORITY)部分,在这个例子里边就是com.ex09_2_contentprovider.diarycontentprovider,授权部分是惟一的,
一般为在程序是我们实现的那个ContentProvider的全称,并且全都小写。这部份是和在AndroidManifest.xml文件当中的<providerAndroid:name="DiaryContentProvider" Android:authorities="com.ex09_2_contentprovider.diarycontentprovider" />部分对应的。
●第三部分是请求数据的类型。例如,在这个例子当中定义的类型是diaries。当然这一部分可以是0 个片段或者多个片段构成,
如果content provider 只是暴露出了一种类型的数据,那么这部分可以为空,但是如果暴露出了多种,尤其是包含子类的的时候,就不能为空。
例如,日记本程序里边可以暴露出来两种数据,一种是用户自己的"diaries/my",另一种是其他人的"diaries/others。
●第四部分就是“1”,当然这部分是允许为空的。如果为空,表示请求全部数据;如果不为空,表示请求特定ID的数据。
(3).构建用户的数据存储系统。在这个例子当中,是将数据存储到数据库系统当中。通常是将数据存储在数据库系统中,但是也可以将数据存储在其他的地方,如文件系统等。
2 DatabaseHelper(Context context) {
3 super(context, DATABASE_NAME, null, DATABASE_VERSION);
4 }
5 @Override
6 public void onCreate(SQLiteDatabase db) {
7 db.execSQL("CREATE TABLE " + DIARY_TABLE_NAME + " ("
8 + DiaryColumns._ID + " INTEGER PRIMARY KEY,"
9 + DiaryColumns.TITLE + " TEXT," + DiaryColumns.BODY
10 + " TEXT," + DiaryColumns.CREATED + " TEXT" + ");");
11 }
12 @Override
13 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
14 db.execSQL("DROP TABLE IF EXISTS notes");
15 onCreate(db);
16 }
17 }
(4).实现ContentProvider 这个抽象类的抽象方法,具体如下所示:
• public boolean onCreate(),当ContentProvider 生成的时候调用此方法,一般在此方法中打开或初始化底层数据库。
• public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) ,此方法返回一个Cursor 对象作为查询结果集。
• public Uri insert(Uri uri, ContentValues initialValues),此方法负责往数据集当中插入一列,并返回这一列的Uri。
• public int delete(Uri uri, String where, String[] whereArgs),此方法负责删除指定Uri 的数据。
• public int update(Uri uri, ContentValues values, String where,String[] whereArgs) ,此方法负责更新指定Uri 的数据。
• public String getType(Uri uri) ,返回所给Uri 的MIME 类型。
2 private static final String DATABASE_NAME = "database";
3 private static final int DATABASE_VERSION = 1;
4 private static final String DIARY_TABLE_NAME = "diary";
5 private static HashMap<String, String> sDiariesProjectionMap;
6 private static final int DIARIES = 1;
7 private static final int DIARY_ID = 2;
8 private static final UriMatcher sUriMatcher;
其中的UriMatcher 是匹配Uri 的一个辅助类。通过 UriMatcher 类我们可以很方便的判断一个URi 的类型,特别是判断这个Uri 是对单个数据的请求,还是对全部数据的请求。
在我们的DiaryContentProvider 中的static 模块中,有下边的代码:
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); sUriMatcher.addURI(Diary.AUTHORITY, "diaries", DIARIES); sUriMatcher.addURI(Diary.AUTHORITY, "diaries/#", DIARY_ID);
• sUriMatcher.addURI(Diary.AUTHORITY, "diaries", sUriMatcher.match(uri))表示,
如果我们的Uri 是content://com.ex09_2_contentprovider.diarycontentprovider/diaries/,那么sUriMatcher.match(uri) 的返回值就是DIARIES。
• sUriMatcher.addURI(Diary.AUTHORITY, "diaries/#", DIARY_ID)表示,
如果我们的Uri 是content://com.ex09_2_contentprovider.diarycontentprovider/diaries/id,那么sUriMatcher.match(uri)的返回值就是DIARY_ID。
第四部说说的insert()方法代码如下:
2 if (sUriMatcher.match(uri) != DIARIES) {
3 throw new IllegalArgumentException("Unknown URI " + uri);
4 }
5 ContentValues values;
6 if (initialValues != null) {
7 values = new ContentValues(initialValues);
8 } else {
9 values = new ContentValues();
10 }
11 if (values.containsKey(Diary.DiaryColumns.CREATED) == false) {
12 values.put(Diary.DiaryColumns.CREATED, getFormateCreatedDate());
13 }
14 if (values.containsKey(Diary.DiaryColumns.TITLE) == false) {
15 Resources r = Resources.getSystem();
16 values.put(Diary.DiaryColumns.TITLE, r
17 .getString(Android.R.string.untitled));
18 }
19 if (values.containsKey(Diary.DiaryColumns.BODY) == false) {
20 values.put(Diary.DiaryColumns.BODY, "");
21 }
22 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
23 long rowId = db.insert(DIARY_TABLE_NAME, DiaryColumns.BODY, values);//insert()返回的是一个Uri,而不是一个记录的id
24 if (rowId > 0) {
25 Uri diaryUri= ContentUris.withAppendedId(
26 Diary.DiaryColumns.CONTENT_URI, rowId);
27 return diaryUri;
28 }
29 throw new SQLException("Failed to insert row into " + uri);
30 }
ContentUris是ContentUri的一个辅助类,其withAppendedId()方法负责把id 和contentUri 连接成一个新的Uri。比如在我们这个例子当中是这么使用的:ContentUris.withAppendedId(Diary.DiaryColumns.CONTENT_URI, rowId).如果rowId 为100 的话,那么现在的这个Uri 的内容就是:
content://com.ex09_2_contentprovider.diarycontentprovider/diaries/100.
ContentUris还有一个比较实用的方法parseId(Uri contentUri),这个方法负责把content URI 后边的id 解析出来.比如现在这个contentURI 是content://com.ex09_2_contentprovider.diarycontentprovider/diaries/100,那么这个函数的返回值就是100.
删除方法delete():
2 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
3 /*
4 *getPathSegments()方法得到一个String 的List,在我们例子
5 *当中uri.getPathSegments().get(1)为rowId,如果是*uri.getPathSegments().get(0),那值就是"diaries"
6 */
7 String rowId = uri.getPathSegments().get(1);
8 return db
9 .delete(DIARY_TABLE_NAME, DiaryColumns._ID + "=" + rowId, null);
10 }
更新方法update():
2 String[] whereArgs) {
3 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
4 String rowId = uri.getPathSegments().get(1);
5 return db.update(DIARY_TABLE_NAME, values, DiaryColumns._ID + "="
6 + rowId, null);
7 }
查询方法query():
2 String[] selectionArgs, String sortOrder) {
3 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();//是一个构造SQL 查询语句的辅助类
4 switch (sUriMatcher.match(uri)) {
5 case DIARIES://根据返回值可以判断这次查询请求时,它是请求全部数据还是某个id 的数据。
6 qb.setTables(DIARY_TABLE_NAME);
7 break;
8 case DIARY_ID:
9 qb.setTables(DIARY_TABLE_NAME);
10 qb.appendWhere(DiaryColumns._ID + "="
11 + uri.getPathSegments().get(1));
12 break;
13 default:
14 throw new IllegalArgumentException("Unknown URI " + uri);
15 }
16 String orderBy;
17 if (TextUtils.isEmpty(sortOrder)) {
18 orderBy = Diary.DiaryColumns.DEFAULT_SORT_ORDER;
19 } else {
20 orderBy = sortOrder;
21 }
22 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
23 /*第一个参数为要查询的数据库实例。
24 第二个参数是一个字符串数组,里边的每一项代表了需要返回的列名。
25 第三个参数相当于sql 语句中的where 部分。
26 第四个参数是一个字符串数组,里边的每一项依次替代在第三个参数中出现的问号(?)
27 第五个参数相当于sql 语句当中的groupby 部分。
28 第六个参数相当于sql 语句当中的having 部分。
29 第七个参数描述是怎么进行排序。
30 第八个参数相当于sql 当中的limit 部分。控制返回的数据的个数。
31 */
32 Cursor c = qb.query(db, projection, selection, selectionArgs, null,
33 null, orderBy);
34 return c;
35 }
重写后的getType():
2 /*
3 此方法返回一个所给Uri 的指定数据的MIME 类型。它的返回值如果以vnd.Android.cursor.item 开头,那么就代
4 表这个Uri 指定的是单条数据。如果是以vnd.Android.cursor.dir 开头的话,那么说明这个Uri 指定的是全部数
5 据。
6 */
7 switch (sUriMatcher.match(uri)) {
8 case DIARIES:
9 //"vnd.Android.cursor.dir/vnd.google.diary"
10 return DiaryColumns.CONTENT_TYPE;
11 case DIARY_ID:
12 //vnd.Android.cursor.item/vnd.google.diary
13 return DiaryColumns.CONTENT_ITEM_TYPE;
14 default:
15 throw new IllegalArgumentException("Unknown URI " + uri);
16 }
17 }