在Android中数据的存储一共有五种形式,分别是:Shared Preferences、网络存储、文件存储,外储存储、SQLite。但是我们知道一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享。而使用ContentProvider共享数据的好处是统一了数据访问方式。ContentProvide对数据进行封装,不用关心数据存储的细节。下面我们通过代码来介绍这个ContentProvider的使用过程。
public class ItemContentProvier { /*Authority*/ public static final String AUTHORITY="com.itchq.contentprovider"; /*Match Code*/ public static final int ITEM=1; public static final int ITEM_POS=2; /*Default sort order*/ public static final String DEFAULT_SORT_ORDER = "_id asc"; /*MIME*/ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.itchq.contentprovider"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.itchq.contentprovider"; public static final String ARTICE_ITEM="articeitem"; /*Content URI*/ public static final Uri CONTENT_ARTICE=Uri.parse("content://"+AUTHORITY+"/"+ARTICE_ITEM); public interface Artice{ public static final String ID="_id"; public static final String TITLE="title"; public static final String CONTENT_ID="contentid"; } }
上面这个类都是定义数据库一些常量,我们往往把它单独的提前出来使代码结构更清晰一点。
URI 通用资源标志符,这个Uri代表了要操作的数据,后面要访问这个数据库就可以通过这个URI来指定,用于唯一标识某个具体的ContentProvider
URI格式如下:
[content://][com.itchq.contentprovider][/artice][/1]
|------A------|-----------------B-------------------|---C---|---D--|
A:前缀表明数据受控于一个内容提供者。它从不修改,也就是schema,ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content://
B:称为Authority,它唯一地标识了一个特定的Content Provider,因此,这部分内容一般使用Content Provider所在的package来命名,使得它是唯一的,在上面的代码中使用 public static final String AUTHORITY="com.itchq.contentprovider";定义这个变量,这个还有一个地方使用到,就是在AndroidMainfest.xml中要声明这个数据库的时候需要使用,如下代码:
<provider android:name="com.itchq.contentprovider.itContentProvider" android:authorities="com.itchq.contentprovider"/>
上面这个android:authorities这个就是和我们的AUTHORITY里面定义的字符串是一样的,这个地方要注意点,如果不一样外部就不可以访问这个数据库了,我们所说的URI唯一性也是在这个地方来体现出来的,
C:称为资源路径,它表示所请求的资源的类型,这部分内容是可选的
D:资源ID,它表示所请求的是一个特定的资源,它通常是一个数字,对应前面我们所介绍的数据库表中的_id字段的内容,它唯一地标志了某一种资源下的一个特定的实例
我们在定义URI的时候一般情况行都是需要定义这个C,但是D就没有定义,简单点说C就是代表了数据库中某一个表(这个名字是随意定义不一定就是和我们的数据库表名字相同,这里只是一个比喻),而D则是表示操作这个表的哪一个字段,相当于我们的SQL语句中 where id=1这个条件
继续往下看:
/*MIME*/ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.itchq.contentprovider"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.itchq.contentprovider";
这个是MIME类型,每一个ContenProvider中都必须定义这个MIME,其实这个MIME类型一般来说就估计上面两种,被分为两部分前面是数据的大类别,后面定义具体的种类,比如上面的vnd.android.cursor.dir和vnd.android.cursor.item都是固定不会变的,后面的字段都是相同的而且随便定义,一般的数据库中都vnd.+包名。对于vnd.android.cursor.dir这个类型表示的访问多个资源的URI,就是你操作的是一整张表,比如添加新数据了,删除某一个表后者是查询表的全部记录等等,而vnd.android.cursor.item是表示访问单个资源的URI,比如更新表中某一个自定的记录,查询某一条指定的信息,就是相当SQL中的where条件语句。
Artice这个接口是我们这个数据库的表的字段,这里看出这个表有三个字段,分别是:_id,title,contentid。ITEM和ITEM_POS这两个变量分别定义URI匹配规则的匹配码。
比如如果我们访问的URI是content://com.itchq.contentprovider/articeitem,则匹配规则返回的匹配码为ITEM。如果访问的是content://com.itchq.contentprovider/articeitem/# # 是一个通配符表示如何数据,则匹配规则返回的匹配码为ITEM_POS,这两个常量我们后面还会有用到的
下面就是ContentProvider的定义了
package com.itchq.contentprovider; import java.util.HashMap; import com.itchq.contentprovider.ItemContentProvier.Artice; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; public class itContentProvider extends ContentProvider{ private static final String DB_NAME="itchq.db"; private static final String DB_TABLE_ARTICE="artice"; private static final int DB_VERSION=1; private DBHelper dbHelper = null; private static final String DB_CREATE_ARTICE = "create table " + DB_TABLE_ARTICE + " (" + Artice.ID + " integer primary key autoincrement, " + Artice.TITLE + " text not null, " + Artice.CONTENT_ID + " integer not null);"; private static final UriMatcher uriMatcher; private static final HashMap<String, String> articleProjectionMap; static{ uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(ItemContentProvier.AUTHORITY, ItemContentProvier.ARTICE_ITEM, ItemContentProvier.ITEM); uriMatcher.addURI(ItemContentProvier.AUTHORITY, ItemContentProvier.ARTICE_ITEM+"/#", ItemContentProvier.ITEM_POS); articleProjectionMap = new HashMap<String, String>(); articleProjectionMap.put(Artice.ID, Artice.ID); articleProjectionMap.put(Artice.TITLE, Artice.TITLE); articleProjectionMap.put(Artice.CONTENT_ID,Artice.CONTENT_ID); } @Override public boolean onCreate() { // TODO Auto-generated method stub dbHelper = new DBHelper(getContext(), DB_NAME, null, DB_VERSION); return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub SQLiteDatabase db = dbHelper.getReadableDatabase(); SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder(); switch(uriMatcher.match(uri)){ case ItemContentProvier.ITEM:{ sqlBuilder.setTables(DB_TABLE_ARTICE); sqlBuilder.setProjectionMap(articleProjectionMap); break; } case ItemContentProvier.ITEM_POS:{ String id = uri.getPathSegments().get(1); sqlBuilder.setTables(DB_TABLE_ARTICE); sqlBuilder.setProjectionMap(articleProjectionMap); sqlBuilder.appendWhere(Artice.ID + "=" + id); break; } default: throw new IllegalArgumentException("Error Uri: " + uri); } Cursor cursor = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, TextUtils.isEmpty(sortOrder) ? ItemContentProvier.DEFAULT_SORT_ORDER : sortOrder); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub switch (uriMatcher.match(uri)) { case ItemContentProvier.ITEM:{ return ItemContentProvier.CONTENT_TYPE; } case ItemContentProvier.ITEM_POS:{ return ItemContentProvier.CONTENT_ITEM_TYPE; } default: throw new IllegalArgumentException("Error Uri: " + uri); } } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub SQLiteDatabase db = dbHelper.getWritableDatabase(); long id = db.insert(DB_TABLE_ARTICE, Artice.ID, values); if (id < 0) { throw new SQLiteException("Unable to insert " + values + " for " + uri); } Uri newUri = ContentUris.withAppendedId(uri, id); getContext().getContentResolver().notifyChange(newUri,null); return newUri; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub SQLiteDatabase db = dbHelper.getWritableDatabase(); int count = 0; switch(uriMatcher.match(uri)){ case ItemContentProvier.ITEM:{ count = db.delete(DB_TABLE_ARTICE, selection, selectionArgs); break; } case ItemContentProvier.ITEM_POS:{ String id = uri.getPathSegments().get(1); count = db.delete(DB_TABLE_ARTICE,Artice.ID + "=" + id + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs); break; } default: throw new IllegalArgumentException("Error Uri: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub SQLiteDatabase db = dbHelper.getWritableDatabase(); int count = 0; switch(uriMatcher.match(uri)){ case ItemContentProvier.ITEM:{ count = db.update(DB_TABLE_ARTICE, values, selection, selectionArgs); break; } case ItemContentProvier.ITEM_POS:{ String id = uri.getPathSegments().get(1); count = db.update(DB_TABLE_ARTICE, values, Artice.ID + "=" + id + (!TextUtils.isEmpty(selection) ? " and (" + selection + ')' : ""), selectionArgs); break; } default: throw new IllegalArgumentException("Error Uri: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } private static class DBHelper extends SQLiteOpenHelper{ public DBHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL(DB_CREATE_ARTICE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub } } }
继承ContentProvider类需要实现默认下面这些方法
public boolean onCreate() 在创建ContentProvider时调用
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 用于查询指定Uri的ContentProvider,返回一个Cursor
public int delete(Uri uri, String selection, String[] selectionArgs) 用于从指定Uri的ContentProvider中删除数据
public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) 用于更新指定Uri的ContentProvider中的数据
public Uri insert(Uri uri, ContentValues values) 用于添加新数据到指定Uri的ContentProvider中
public String getType(Uri uri) 用于返回指定的Uri中的数据的MIME类型
这个就是前面说的,如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头。即
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.itchq.contentprovider";
如果要操作的数据属于非集合类型数据(简单点说就是指定where条件的),那么MIME类型字符串应该以vnd.android.cursor.item/开头。即
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.itchq.contentprovider";
UriMatcher类用于匹配Uri 这个就是要设置Uri的匹配规则,我们可以根据不同的Uri来操作不同的数据,比如前面我们定义的那个URI
/*Content URI*/ public static final Uri CONTENT_ARTICE=Uri.parse("content://"+AUTHORITY+"/"+ARTICE_ITEM);
分解这个URI字符串是”content://com.itchq.contentprovider/articeitem“,那么我们操作可以是content://com.itchq.contentprovider/articeitem这个字符串也也可能是content://com.itchq.contentprovider/articeitem/#这个字符串,前面表示操作整个数据库表,后面指定是操作数据库某一个字段,那我们是如何知道这个匹配码呢?这个就需要UriMatcher类来设定
public void addURI (String authority, String path, int code) ;
authority就是上面定义的public static final String AUTHORITY="com.itchq.contentprovider";这个变量,path就是就是指具体那个路径,一般就是URI规则中表示C的部分,比如上面URI规则中 public static final Uri CONTENT_ARTICE=Uri.parse("content://"+AUTHORITY+"/"+ARTICE_ITEM);那么这个path指的就是ARTICE_ITEM这个字符串变量,即public static final String ARTICE_ITEM="articeitem"; code就是后面那个匹配码,比如我们可以看一下update更新语句里面有这么一个switch函数:
switch(uriMatcher.match(uri)){ case ItemContentProvier.ITEM:{ } case ItemContentProvier.ITEM_POS:{ } default: throw new IllegalArgumentException("Error Uri: " + uri); }
上面的ItemContentProvier.ITEM 和 ItemContentProvier.ITEM_POS 就是已经定义好的匹配码,即code参数,我们通过这个ItemContentProvier.ITEM可以知道操作整个数据库表,ItemContentProvier.ITEM_POS操作的是数据库表中某一列,这个匹配一般都是一开始初始化的时候就需要全部给注册上的,
static{ uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(ItemContentProvier.AUTHORITY, ItemContentProvier.ARTICE_ITEM, ItemContentProvier.ITEM); uriMatcher.addURI(ItemContentProvier.AUTHORITY, ItemContentProvier.ARTICE_ITEM+"/#", ItemContentProvier.ITEM_POS);
这个就是注册上的,我们先看这个path参数有ItemContentProvier.ARTICE_ITEM和ItemContentProvier.ARTICE_ITEM/#,这两个就是分别表示操作的数据库表和数据库对应某一列,最后一个参数就是我们上面也说到的匹配码,构造函数的参数为UriMatcher.NO_MATCH,它表示当uriMatcher不能匹配指定的URI时,就返回代码UriMatcher.NO_MATCH常量值为-1
public int match (Uri uri)
与传入的Uri匹配,它会首先与找我们之前通过addURI方法添加进来的Uri匹配,这个URI匹配是这样的,前面部分固定是A部分:content://, B部分是authority,也就是我们addURI里面添加的第一个参数,C部分表示是路径,也就是我们在addRUI添加的第二个参数path,就是说最后匹配的URI就是这样的 content://authority/path。
authority和path都是我们在addURI参数就第一个和第二个的参数。如果匹配成功就返回之前我们设置的code值,否则返回一个UriMatcher.NO_MATCH常量值为-1
articleProjectionMap = new HashMap<String, String>(); articleProjectionMap.put(Artice.ID, Artice.ID); articleProjectionMap.put(Artice.TITLE, Artice.TITLE); articleProjectionMap.put(Artice.CONTENT_ID,Artice.CONTENT_ID);
在上面的put函数中,第一个参数表示列的别名,第二个参数表示列的真实名称。在这个例子中,我们把列的别名和和真实名称都设置成一样的。
在query函数中,我们使用SQLiteQueryBuilder来辅助数据库查询操作,使用这 个类的好处是我们可以不把数据库表的字段暴露出来,而是提供别名给第三方应用程序使用,这样就可以把数据库表内部设计隐藏起来,方便后续扩展和维护。列别 名到真实列名的映射是由上面这个HashMap成员变量来实现的,当然这里没有修改,也可以不用定义。
@Override public String getType(Uri uri) { // TODO Auto-generated method stub switch (uriMatcher.match(uri)) { case ItemContentProvier.ITEM:{ return ItemContentProvier.CONTENT_TYPE; } case ItemContentProvier.ITEM_POS:{ return ItemContentProvier.CONTENT_ITEM_TYPE; } default: throw new IllegalArgumentException("Error Uri: " + uri); } }
这个是返回当前Uri所数据的MIME类型,如果该Uri对应的数据可能包括多条记录,那么MIME类型字符串就是以 vnd.android.cursor.dir/开头,我们通过前面的addURI指定 ItemContentProvier.ITEM是返回全面的类型,如果Uri对应的数据只包含一条记录,那么MIME类型字符串就是以 vnd.android.cursor.item/开头,前面的addURI得知ItemContentProvier.ITEM_POS这个返回的是某一列信息。
还有一点是我们在itContentProvider类的内部中定义了一个DBHelper类,它继承于SQLiteOpenHelper类,它用是用辅助我 们操作数据库的。使用这个DBHelper类来辅助操作数据库的好处是只有当我们第一次对数据库时行操作时,系统才会执行打开数据库文件的操作,
剩下的更新查询删除等操作就不多说了,最后不要忘记在AndroidManifest.xml中进行定义
<provider android:name="com.itchq.contentprovider.itContentProvider" android:authorities="com.itchq.contentprovider" android:multiprocess="false" />
在配置Content Provider的时候,最重要的就是要指定它的authorities属性了,只有配置了这个属性,第三方应用程序才能通过它来找到这个Content Provider。这要需要注意的,这里配置的authorities属性的值是和我们前面在ItemContentProvier.java文件中定义的AUTHORITY 常量的值是一致的。另外一个属性multiprocess是一个布尔值,它表示这个Content Provider是否可以在每个客户进程中创建一个实例,这样做的目的是为了减少进程间通信的开销。这里我们为了减少不必要的内存开销,把属性 multiprocess的值设置为false,使得系统只能有一个Content Provider实例存在,它运行在自己的进程中。下面看看在Activity中投入和操作这些数据库的
private void add(){ ContentValues values = new ContentValues(); values.put(Artice.TITLE, "test"); values.put(Artice.CONTENT_ID, 1); Uri uri = getContentResolver().insert(ItemContentProvier.CONTENT_ARTICE, values); } private void query(){ String[] projection = new String[] { Artice.ID, Artice.TITLE, Artice.CONTENT_ID }; Cursor cursor = getContentResolver().query(ItemContentProvier.CONTENT_ARTICE, projection, null, null, ItemContentProvier.DEFAULT_SORT_ORDER); if (cursor.moveToFirst()) { do{ int id=cursor.getInt(cursor.getColumnIndex(Artice.ID)); String title=cursor.getString(cursor.getColumnIndex(Artice.TITLE)); int content_id=cursor.getInt(cursor.getColumnIndex(Artice.CONTENT_ID)); }while(cursor.moveToNext()); } }
ItemContentProvier.CONTENT_ARTICE就是我们前面定义的那个URI
这个介绍一个ContentUris类,这个类是它用于在Uri后面追加一个ID或者解析出传入的Uri所带上的ID值,就是我们说的URI规则中最后D部分
|------A------|-----------------B-------------------|---C---|---D--|
常用的两个方法如下:
public static Uri withAppendedId (Uri contentUri, long id) 用于为路径加上ID部分
Uri resultUri = ContentUris.withAppendedId(ItemContentProvier.CONTENT_ARTICE, 10);
那么这个Uri就是content://com.itchq.contentprovider/articeitem/10,那么我们在前面使用UriMatch.addURI匹配码就是
uriMatcher.addURI(ItemContentProvier.AUTHORITY, ItemContentProvier.ARTICE_ITEM+"/#", ItemContentProvier.ITEM_POS);
这个,那么这个时候返回的匹配码就是 ItemContentProvier.ITEM_POS。
public static long parseId (Uri contentUri)从路径中获取ID部分
Uri uri = Uri.parse("content://com.itchq.contentprovider/articeitem/10"
long personid = ContentUris.parseId(uri);//获取的结果为:10,前面的操作中有用到这个