ContentProvider小结

看了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_TYPECONTENT_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)

insert(Uri, ContentValues)

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

delete(Uri, String, String[])

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

URI

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

ContentProvider 是如何组织数据的?

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

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

  1. ContentProvider是什么时候创建的,是谁创建的?访问某个应用程序共享的数据,是否需要启动这个应用程序?这个问题在Android SDK中没有明确说明,但是从数据共享的角度出发,ContentProvider应该是Android在系统启动时就创建了,否则就谈不上数据共享了。这就要求在AndroidManifest.XML中使用<provider>元素明确定义。
  2. 可能会有多个程序同时通过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具体实例中去看源代码!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值