ContentProvider小结

本文深入讲解Android中的ContentProvider,介绍其基本概念、使用方法及如何实现数据共享。内容覆盖核心函数解析、URI定义规则和数据库操作流程。

看了mars老师最新一集的android视频,讲到ContentProvider。看完后感觉晕乎乎的,于是照例,先对mars老师的源码进行分析,再到网上找了些资料总结,以加深印象。

首先复习下mars老师视频中所讲的内容。

ContentProvider的基本概念:

1.ContentProvider提供为存储和获取数据提供了统一的接口

2.使用ContentProvider可以在不同应用程序之间共享数据

3.Android为常见的一些数据提供了ContentProvider(包括音频。视频,图片和通讯录等等)

ContentProvider使用表的形式来组织数据。

URI(统一资源标识符)

1.每一个ContentProvider都拥有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据

2.Android所提供的ContentProvider都存放在android.provider包中

ContentProvider提供的函数:

1.query(): 查询

2.insert(); 插入

3.update(); 更新

4.delete(); 删除

5.getType(); 得到数据类型

6.onCreate(); 创建时的回调函数

实现ContentProvider的过程:
1.定义一个CONTENT_URI常量
2.定义一个类,继承ContentProvider
3.实现query,insert,update,delete,getType和onCreate方法
4.在androidmanifest.xml中进行声明

//DatabaseHelper.java

package apple.com;

import android.content.Context;

import android.database.sqlite.SQLiteDatabase;

import android.database.sqlite.SQLiteOpenHelper;

import android.database.sqlite.SQLiteDatabase.CursorFactory;

//DatabaseHelper作为一个访问SQLite的助手类,提供两个方面的功能,

//第一,getReadableDatabase(),getWritableDatabase()可以获得SQLiteDatabse对象,通过该对象可以对数据库进行操作

//第二,提供了onCreate()和onUpgrade()两个回调函数,允许我们在创建和升级数据库时,进行自己的操作

public class DatabaseHelper extends SQLiteOpenHelper {

private static final int VERSION = 1;

//在SQLiteOepnHelper的子类当中,必须有该构造函数

public DatabaseHelper(Context context, String name, CursorFactory factory,

int version) {

//必须通过super调用父类当中的构造函数

super(context, name, factory, version);

// TODO Auto-generated constructor stub

}

public DatabaseHelper(Context context,String name){

this(context,name,VERSION);

}

public DatabaseHelper(Context context,String name,int version){

this(context, name,null,version);

}

//该函数是在第一次创建数据库的时候执行,实际上是在第一次得到SQLiteDatabse对象的时候,才会调用这个方法

@Override

public void onCreate(SQLiteDatabase db) {

// TODO Auto-generated method stub

System.out.println("create a Database");

//execSQL函数用于执行SQL语句

db.execSQL("create table user(id int,name varchar(20))");

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

// TODO Auto-generated method stub

System.out.println("update a Database");

}

}

//FirstContentProvider.java

package apple.com;

import java.util.HashMap;

import android.content.ContentProvider;

import android.content.ContentUris;

import android.content.ContentValues;

import android.content.UriMatcher;

import android.database.Cursor;

import android.database.sqlite.SQLiteDatabase;

import android.database.sqlite.SQLiteQuery;

import android.database.sqlite.SQLiteQueryBuilder;

import android.net.Uri;

import android.text.TextUtils;

import apple.com.FirstProviderMetaData.UserTableMetaData;

public class FirstContentProvider extends ContentProvider{

/*

* 我们使用URI来访问ContentProvider,UriMatcherd定义了一个规则,用以检测URI是否符合标准

* 检查的原理是形成一个映射,将URI与一个数字相关连。如果URI符合某个规则,就返回相应的数字

*/

public static final UriMatcher uriMatcher = null;

public static final int INCOMING_USER_COLLECTION = 1;

public static final int INCOMING_USER_SINGLE = 2;

private DatabaseHelper dh = null;

static

{

/*

* addURI($1,$2,$3);

* 参数1的作用是传入AUTHORIY,参数2是传入目录,参数3是传入与URI对应的数字

* #代表用户的ID

*/

uriMatcher.addURI(FirstProviderMetaData.AUTHORIY,"/users",INCOMING_USER_COLLECTION);

uriMatcher.addURI(FirstProviderMetaData.AUTHORIY,"/users/#",INCOMING_USER_SINGLE);

}

//为表的列起别名

public static HashMap<String,String> userProjectMap = null;

static

{

userProjectMap = new HashMap<String,String>();

//由于无特别需要,因此别名与原来的名字相同

userProjectMap.put(UserTableMetaData._ID,UserTableMetaData._ID);

userProjectMap.put(UserTableMetaData.USER_NAME,UserTableMetaData.USER_NAME);

}

public int delete(Uri arg0, String arg1, String[] arg2) {

// TODO Auto-generated method stub

return 0;

}

//根据传入的URI返回该URI所表示的数据类型

public String getType(Uri uri) {

switch(uriMatcher.match(uri)) {

//判断符合哪个规则,返回相应的值

//如果URI想访问一系列的对象

case INCOMING_USER_COLLECTION:

return UserTableMetaData.CONTENT_TYPE;

break;

//如果URI只想访问其中一个对象

case INCOMING_USER_SINGLE:

return UserTableMetaData.CONTENT_TYPE_ITEM;

break;

default:

throw new IllegalArgumentException("Unknow URI"+uri);

}

return null;

}

//返回值是URI,此URI表示的是刚刚此函数所插入的数据

public Uri insert(Uri uri, ContentValues values) { //ContentValues是一个键为String的键值对

SQLiteDatabase db = dh.getWritableDatabase();

long rowId = db.insert(UserTableMetaData.TABLE_NAME,null,values); //插入values

if(rowId>0) { //插入成功,创建表的时候ID会自增长,当插入成功将返回插入的ID号。

//ContentUris是一个工具类,为ContentURI追加ID

Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);

/*通知监听器,数据已经改变

getContext()是得到当前所使用的上下文

ContentProvider()会返回一个ContentResolver对象,可以对ContentProvider进行操作

*/

getContext().getContentResolver().notifyChange(insertedUserUri,null);

return insertedUserUri;

}

// TODO Auto-generated method stub

return null;

}

//回调函数,在ContentProvider创建的时候执行

public boolean onCreate() {

dh = new DatabaseHelper(getContext(),FirstProviderMetaData.DATABASE_NAME);

return true;

}

@Override

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,

String sortOrder) {

//创建一个查询语句

SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

switch(uriMatcher.match(uri)) { //先判断查询的URI对象

case INCOMING_USER_COLLECTION:

qb.setTables(UserTableMetaData.TABLE_NAME);

qb.setProjectionMap(userProjectMap);

break;

case INCOMING_USER_SINGLE:

qb.setTables(UserTableMetaData.TABLE_NAME);

qb.setProjectionMap(userProjectMap);

/*添加一个where条件。getPathSegments()返回的是一个List对象,这个list是得到Uri

里的Path部分,并把其’/‘去掉。例如content://apple.cpFirstContentProvider/users/1调用getPathSegment()

得到user(第0个元素)和1(第一个元素)。后面再调用get(1)得到URI中的ID的值

*/

qb.appendWhere(UserTableMetaData._ID+"="+uri.getPathSegments().get(1));

break;

}

//排序

String orderBy;

if(TextUtils.isEmpty(sortOrder)) {

orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;

}

else {

orderBy = sortOrder;

}

SQLiteDatabase db = dh.getWritableDatabase();

Cursor c =qb.query(db, projection,selection,selectionArgs,null,null,sortOrder);

//通知

c.setNotificationUri(getContext().getContentResolver(), uri);

return c;

}

@Override

public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {

// TODO Auto-generated method stub

return 0;

}

}//FirstProviderMetaData.java

package apple.com;

/*

* 将ContentProvider所需的常量定义在此类中

*/

import android.net.Uri;

import android.provider.BaseColumns;

public class FirstProviderMetaData {

public static final String AUTHORIY = "apple.cp.FirstContentProvider";

//数据库的名称

public static final String DATABASE_NAME = "FirstProvider.db";

//数据库的版本

public static final int DATABASE_VERSION = 1;

//数据库的表名

public static final String USERS_TABLE_NAME = "users";

//实现的BaseColumns接口中已经定义了_ID

public static final class UserTableMetaData implements BaseColumns {

//数据库的表名,是ContentProvider的一个子表

public static final String TABLE_NAME = "users";

/*定义一个Uri对象,它是通过字符串转换为Uri对象

必须定义一个唯一的字符串.最好的解决方案是使用类名来定义,如本例使用apple.cp.FirstContentProvider来定义

*/

public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORIY+"/users");

//因为我们使用ContentProvider来存取数据,必须知道数据的类型,数据类型通过CONTENT_TYPE和CONTENT_TYPE_ITEM

//CONTENT_TYPE为整张表的数据类型

public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.firstprovider.user";

//访问某一条数据则使用CONTENT_TYPE_ITEM

public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.firstprovider.user";

//列名

public static final String USER_NAME = "name";

//默认的排序方式

public static final String DEFAULT_SORT_ORDER = "_id desc";

}

}

//如何使用查询和插入功能:

//插入

class InsertListener implements OnClickListener {

public void onClick(View v) {

ContentValues values = new ContentValues();

values.put(FirstProviderMetaData.USERS_TABLE_NAME, "apple");

//FirstProviderMetaData.UserTableMetaData.CONTENT_URI代表你要插入的URI

Uri uri = getContentResolver().

insert(FirstProviderMetaData.UserTableMetaData.CONTENT_URI,values);

}

}

//查询

class QueryListener implements OnClickListener {

public void onClick(View v) {

Cursor c = getContentResolver().query(FirstProviderMetaData.UserTableMetaData.CONTENT_URI,null,null,null);

while(c.moveToNext()) {

System.out.println(c.getColumnName(c.getColumnIndex(UserTableMetaData.USER_NAME)));

}

}

}

最后,千万要记得在Androidmanifest.xml文件中进行声明:

<provider android : name = "apple.com.FirstContentProvider"

android:authorities = "apple.cp.FirstContentProvider"/>


另外附上博文一篇:http://elsila.blog.163.com/blog/static/173197158201101773127463/


URI与URL

在Android中广泛应用URI,而不是URL。URL标识资源的物理位置,相当于文件的路径;而URI则是标识资源的逻辑位置,并不提供资源的具体位置。比如说电话薄中的数据,如果用URL来标识的话,可能会是一个很复杂的文件结构,而且一旦文件的存储路径改变,URL也必须得改动。但是若是URI,则可以用诸如content : //contract /people这样容易记录的逻辑地址来标识,而且并不需要关心文件的具体位置,即使文件位置改动也不需要做变化,当然这都是对于用户来说,后台程序中URI到具体位置的映射还是需要程序员来改动的。


ContentProvider
在Android中,ContentProvider是数据对外的接口,程序通过ContentProvider访问数据而不需要关心数据具体的存储及访问过程,这样既提高了数据的访问效率,同时也保护了数据。Activity类中有一个继承自ContentWapper的getContentResolver()无参数方法,该方法返回一个ContentResolver对象,通过调用其query、insert、update、delete方法访问数据。这几个方法的第一个参数均为URI型,用来标识资源。


Android ContentProvider URI
Android的ContentProvider URI有固定的形式:
content : //contract / people
前缀:固定为content : //
认证:contract 资源的唯一标识符
路径:people 具体的资源类型

在Android 应用程序之间数据共享—-ContentResolver中,已经说明了Android是如何实现应用程序之间数据共享的,并详细解析了如何获取其他应用程序共享的数据。ContentProviders存储和检索数据,通过它可以让所有的应用程序访问到,这也是应用程序之间唯一共享数据的方法。那么如何将应用程序的数据暴露出去?

通过以前文章的学习,知道ContentResolver是通过ContentProvider来获取其他与应用程序共享的数据,那么ContentResolver与ContentProvider的接口应该差不多的。

其中ContentProvider负责组织应用程序的数据;向其他应用程序提供数据;ContentResolver则负责获取ContentProvider提供的数据;修改/添加/删除更新数据等;

ContentProvider 是如何向外界提供数据的?
Android提供了ContentProvider,一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProviders是以类似数据库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URI来表示外界需要访问的“数据库”。至于如何从URI中识别出外界需要的是哪个“数据库”,这就是Android底层需要做的事情了,不在此详细说。简要分析下ContentProvider向外界提供数据操作的接口:

query(Uri, String[], String, String[], String)

insert(Uri, ContentValues)

update(Uri, ContentValues, String, String[])

delete(Uri, String, String[])

这些操作与数据库的操作基本上完全一样,在此不详细说,具体的解析可以参考Android Sqlite解析篇中的详细说明。需要特殊说明的地方是URI:

在URI的D部分可能包含一个_ID ,这个应该出现在SQL语句中的,可以以种特殊的方式出现,这就要求我们在提供数据的时候,需要来额外关注这个特殊的信息。Android SDK推荐的方法是:在提供数据表字段中包含一个ID,在创建表时INTEGER PRIMARY KEY AUTOINCREMENT标识此ID字段。

ContentProvider 是如何组织数据的?
组织数据主要包括:存储数据,读取数据,以数据库的方式暴露数据。数据的存储需要根据设计的需求,选择合适的存储结构,首选数据库,当然也可以选择本地其他文件,甚至可以是网络上的数据。数据的读取,以数据库的方式暴露数据这就要求,无论数据是如何存储的,数据最后必须以数据的方式访问。

可能还有2个问题,是需要关注的。

ContentProvider是什么时候创建的,是谁创建的?访问某个应用程序共享的数据,是否需要启动这个应用程序?这个问题在Android SDK中没有明确说明,但是从数据共享的角度出发,ContentProvider应该是Android在系统启动时就创建了,否则就谈不上数据共享了。这就要求在AndroidManifest.XML中使用<provider>元素明确定义。
可能会有多个程序同时通过ContentResolver访问一个ContentProvider,会不会导致像数据库那样的“脏数据”?这个问题一方面需要数据库访问的同步,尤其是数据写入的同步,在AndroidManifest.XML中定义ContentProvider的时候,需要考虑是<provider>元素multiprocess属性的值;另外一方面Android在ContentResolver中提供了notifyChange()接口,在数据改变时会通知其他ContentObserver,这个地方应该使用了观察者模式,在ContentResolver中应该有一些类似register,unregister的接口。
至此,已经对ContentProvider提供了比较全面的分析,至于如何创建ContentProvider,可通过2种方法:创建一个属于你自己的ContentProvider或者将你的数据添加到一个已经存在的ContentProvider中,当然前提是有相同数据类型并且有写入Content provider的权限。


实现:
1.实现一个ContentProvider的子类,并实现其重载方法
public class MyProvider extends ContentProvider {
private UriMatcher mat=null;
private final static int AUTH=1;
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
Context c=this.getContext();
db=new DB(c);
mat=new UriMatcher(UriMatcher.NO_MATCH); //UriMatcher用于URI的验证
mat.addURI("com.example.fq.myprovider", "notes", AUTH);
/*上面的语句使得对content://com.example.fq.myprovider/notes调用mat.match(uri)时返回AUTH
*/
return true;
}

@Override
public int delete(Uri uri, String where, String[] args) {
// TODO Auto-generated method stub
return 0;
}

@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
if(mat.match(uri)==AUTH){
long n= db.insert(values);
return Uri.parse(uri.toString()+Long.toString(n));
}
}

@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
Cursor c=null;
if(mat.match(uri)==AUTH){
c=db.query(selection, selectionArgs);
}
return c;
}

@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}


}


2.在AndroidManifest.xml中添加Provider的声明:
<provider
android:name="MyProvider"
android:authorities="com.example.fq.myprovider"
/>

其中name对应ContentProvider的子类名,authorities对应URI中的认证部分,必须是唯一的小写字串,习惯用包名加上类名以保持唯一性。这样在检测到包含有这个认证的URI

实际会调用对应Provider子类的相关函数 在Android SDK的sample中提供的Notepad具体实例中去看源代码!

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/mvpsendoh/archive/2011/05/29/6453582.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值