看了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;
publicclass FirstProviderMetaData {
publicstaticfinal String AUTHORIY = "apple.cp.FirstContentProvider";
//数据库的名称
publicstaticfinal String DATABASE_NAME = "FirstProvider.db";
//数据库的版本
publicstaticfinalintDATABASE_VERSION = 1;
//数据库的表名
publicstaticfinal String USERS_TABLE_NAME = "users";
//实现的BaseColumns接口中已经定义了_ID
publicstaticfinalclass UserTableMetaData implements BaseColumns {
//数据库的表名,是ContentProvider的一个子表
publicstaticfinal String TABLE_NAME = "users";
/*定义一个Uri对象,它是通过字符串转换为Uri对象
必须定义一个唯一的字符串.最好的解决方案是使用类名来定义,如本例使用apple.cp.FirstContentProvider来定义
*/
publicstaticfinal Uri CONTENT_URI = Uri.parse("content://"+AUTHORIY+"/users");
//因为我们使用ContentProvider来存取数据,必须知道数据的类型,数据类型通过CONTENT_TYPE和CONTENT_TYPE_ITEM
//CONTENT_TYPE为整张表的数据类型
publicstaticfinal String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.firstprovider.user";
//访问某一条数据则使用CONTENT_TYPE_ITEM
publicstaticfinal String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.firstprovider.user";
//列名
publicstaticfinal String USER_NAME = "name";
//默认的排序方式
publicstaticfinal String DEFAULT_SORT_ORDER = "_id desc";
}
}
//如何使用查询和插入功能:
//插入
class InsertListener implements OnClickListener {
publicvoid 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 {
publicvoid 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)
update(Uri, ContentValues, 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具体实例中去看源代码!