zoukankan      html  css  js  c++  java
  • AndroidContentProvider ContentResolver和ContentObserver的使用

    1、ContentProvider、ContentResolver和ContentObserver

    ContentProvider是Android的四大组件之一,可见它在Android中 的作用非同小可。它主要的作用是:实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用 中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者SQLite等。
    一个应用实现ContentProvider来提供内容给别的应用来操作, 通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以。

    ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的 触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地 ContentObserver也分为“表“ContentObserver、“行”ContentObserver,当然这是与它所监听的Uri MIME Type有关的。

    2、Contacts Demo

    1)、基本功能实现

    接下来通过一个简单的存储联系人信息的demo,来学习怎么创建自定义的ContentProvider,这里数据源选用SQLite,最常用的也是这个。
    (1) 创建一个类NoteContentProvider,继承ContentProvider,需要实现下面5个方法:
    query
    insert
    update
    delete
    getType

    01.public class ContactsContentProvider extends ContentProvider{
    02. 
    03.@Override
    04.public boolean onCreate() {
    05.// TODO Auto-generated method stub
    06.return false;
    07.}
    08. 
    09.@Override
    10.public int delete(Uri arg0, String arg1, String[] arg2) {
    11.// TODO Auto-generated method stub
    12.return 0;
    13.}
    14. 
    15.@Override
    16.public String getType(Uri arg0) {
    17.// TODO Auto-generated method stub
    18.return null;
    19.}
    20. 
    21.@Override
    22.public Uri insert(Uri arg0, ContentValues arg1) {
    23.// TODO Auto-generated method stub
    24.return null;
    25.}
    26. 
    27.@Override
    28.public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
    29.String arg4) {
    30.// TODO Auto-generated method stub
    31.return null;
    32.}
    33. 
    34.@Override
    35.public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
    36.// TODO Auto-generated method stub
    37.return 0;
    38.}
    39. 
    40.}




    (2)先来设计一个数据库,用来联系人信息,主要包含_ID,name,telephone,create_date,content五个字段。 group_name字段等后面升级部分再做使用。创建ProviderMetaData类,封装URI和数据库、表、字段相关信息,源码如下:

    01.public class ProviderMetaData {
    02. 
    03.public static final String AUTHORITY = "com.johnny.contactsprovider";
    04.public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
    05. 
    06.public static final class ContactsData implements BaseColumns{
    07.public static final String TABLE_NAME = "contacts";
    08.public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE_NAME);
    09. 
    10.public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact";
    11.public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact";
    12. 
    13.public static final String CONTACT_NAME = "name";
    14.public static final String CONTACT_TELEPHONE = "telephone";
    15.public static final String CONTACT_CREATE_DATE = "create_date";
    16.public static final String CONTACT_CONTENT = "content";
    17.public static final String CONTACT_GROUP = "group_name";
    18. 
    19.public static final String DEFAULT_ORDERBY = "create_date DESC";
    20. 
    21.public static final String SQL_CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
    22.+ _ID + " INTEGER PRIMARY KEY,"
    23.+ CONTACT_NAME + " VARCHAR(50),"
    24.+ CONTACT_TELEPHONE + " VARCHAR(11),"
    25.+ CONTACT_CONTENT +" TEXT,"
    26.+ CONTACT_CREATE_DATE + " INTEGER"
    27.+ ");" ;
    28.}
    29.}



    AUTHORITY代表授权,该字符串和在Android描述文件AndroidManifest.xml中注册该ContentProvider时的 android:authorities值一样,ContactsData继承BaseColumns,后者提供了标准的_id字段,表示行ID。
    熟悉Content Provider(内容提供者)的应该知道,我们可以通过UriMatcher类注册不同类型的Uri,我们可以通过这些不同的Uri来查询不同的结果。根据Uri返回的结果,Uri Type可以分为:返回多条数据的Uri、返回单条数据的Uri。
    Android遵循类似的约定来定义MIME类型,每个内容类型的Android MIME类型有两种形式:多条记录(集合)和单条记录。
    多条记录
    vnd.android.cursor.dir/contact
    单条记录
    vnd.android.cursor.item/contact
    vnd表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型/之 后的内容可以按照格式随便填写。在使用Intent时,会用到MIME这玩意,根据Mimetype打开符合条件的活动。

    (3) ContentProvider是根据URI来获取数据的,那它怎么区分不同的URI呢,因为无论是获取笔记列表还是获取一条笔记都是调用query方法,现在来实现这个功能。需要用到类UriMatcher,该类可以帮助我们识别URI类型,下面看实现源码:

    01.static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    02.static final HashMap<String, String> CONTACTS_PROJECTION_MAP = new HashMap<String, String>();
    03.private static final int CONTACTS = 1;
    04.private static final int CONTACTS_ID = 2;
    05.static{
    06.final UriMatcher matcher = URI_MATCHER;
    07.matcher.addURI(ProviderMetaData.AUTHORITY, "contacts", CONTACTS);
    08.matcher.addURI(ProviderMetaData.AUTHORITY, "contacts/#", CONTACTS_ID);
    09. 
    10.HashMap<String, String> map = CONTACTS_PROJECTION_MAP;
    11.map.put(ContactsData._ID, ContactsData._ID);
    12.map.put(ContactsData.CONTACT_NAME, ContactsData.CONTACT_NAME);
    13.map.put(ContactsData.CONTACT_TELEPHONE, ContactsData.CONTACT_TELEPHONE);
    14.map.put(ContactsData.CONTACT_CONTENT, ContactsData.CONTACT_CONTENT);
    15.map.put(ContactsData.CONTACT_CREATE_DATE, ContactsData.CONTACT_CREATE_DATE);
    16.}



    这段代码是NoteContentProvider类中的,UriMatcher的工作原理:首先需要在UriMatcher中注册URI模式,每一个模 式跟一个唯一的编号关联,注册之后,在使用中就可以根据URI得到对应的编号,当模式不匹配时,UriMatcher将返回一个NO_MATCH常量,这 样就可以区分了。
    (4) 还需为查询设置一个投影映射,主要是将抽象字段映射到数据库中真实的字段名称,因为这些字段有时是不同的名称,既抽象字段的值可以不跟数据库中的字段名称 一样。这里使用HashMap来完成,key是抽象字段名称,value对应数据库中的字段名称,不过这里我把两者的值设置是一样的,在 NoteContentProvider.java中添加如上面所示的代码。
    (5) 在NoteContentProvider.java中创建一个内部类DatabaseHelper,继承自SQLiteOpenHelper,完成数据库表的创建、更新,这样可以通过它获得数据库对象,相关代码如下。

    01.private class DatabaseHelper extends SQLiteOpenHelper{
    02. 
    03.static final String DATABASE_NAME = "test.db";
    04.static final int DATABASE_VERSION = 1;
    05. 
    06.public DatabaseHelper(Context context) {
    07.super(context, DATABASE_NAME, null, DATABASE_VERSION);
    08.// TODO Auto-generated constructor stub
    09.}
    10. 
    11.@Override
    12.public void onCreate(SQLiteDatabase db) {
    13.// TODO Auto-generated method stub
    14.db.execSQL(ContactsData.SQL_CREATE_TABLE);
    15.}
    16. 
    17.@Override
    18.public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    19.// TODO Auto-generated method stub
    20.onCreate(db);
    21.}
    22. 
    23.}



    (6) 现在来分别实现第一步中未实现的5个方法,先来实现query方法,这里借助SQLiteQueryBuilder来为查询设置投影映射以及设置相关查询条件,看源码实现:

    01.@Override
    02.public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    03.String sortOrder) {
    04.// TODO Auto-generated method stub
    05.SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    06.switch(URI_MATCHER.match(uri)){
    07.case CONTACTS_ID:
    08.queryBuilder.setTables(ContactsData.TABLE_NAME);
    09.queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);
    10.queryBuilder.appendWhere(ContactsData.TABLE_NAME + "._id="+Long.toString(ContentUris.parseId(uri)));
    11.break;
    12.case CONTACTS:
    13.queryBuilder.setTables(ContactsData.TABLE_NAME);
    14.queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);
    15.break;
    16.}
    17. 
    18.String orderBy;
    19.if(TextUtils.isEmpty(sortOrder))
    20.{
    21.orderBy = ContactsData.DEFAULT_ORDERBY;
    22.} else {
    23.orderBy = sortOrder;
    24.}
    25.SQLiteDatabase db = mDbHelper.getReadableDatabase();
    26.Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, orderBy);
    27. 
    28.return cursor;
    29.}




    返回的是一个Cursor对象,它是一个行集合,包含0和多个记录,类似于JDBC中的ResultSet,可以前后移动游标,得到每行每列中的数据。注意的是,使用它需要调用moveToFirst(),因为游标默认是在第一行之前。
    (7)实现insert方法,实现把记录插入到基础数据库中,然后返回新创建的记录的URI。

    01.@Override
    02.public Uri insert(Uri uri, ContentValues values) {
    03.// TODO Auto-generated method stub
    04.SQLiteDatabase db = mDbHelper.getWritableDatabase();
    05.long id = db.insertOrThrow(ContactsData.TABLE_NAME, null, values);
    06. 
    07.// 更新数据时,通知其他ContentObserver
    08.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);
    09. 
    10.if(id > 0){
    11.return ContentUris.withAppendedId(uri, id);
    12.}
    13.return null;
    14.}



    (8) 实现update方法,根据传入的列值和where字句来更新记录,返回更新的记录数,看源码:

    01.@Override
    02.public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    03.// TODO Auto-generated method stub
    04.SQLiteDatabase db = mDbHelper.getWritableDatabase();
    05.int modified = 0;
    06.switch(URI_MATCHER.match(uri)){
    07.case CONTACTS_ID:
    08.selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=?");
    09.selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    10.new String[]{Long.toString(ContentUris.parseId(uri))});
    11.Log.d("Test", "selectionArgs 0"+selectionArgs);
    12.modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);
    13.break;
    14.case CONTACTS:
    15.modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);
    16.Log.d("Test", "selectionArgs 1"+selectionArgs);
    17.break;
    18.}
    19. 
    20.// 更新数据时,通知其他ContentObserver
    21.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);
    22. 
    23.return modified;
    24.}



    notifyChange函数是在更新数据时,通知其他监听对象。
    (9)实现delete方法,该方法返回删除的记录数。

    01.@Override
    02.public int delete(Uri uri, String selection, String[] selectionArgs) {
    03.// TODO Auto-generated method stub
    04.SQLiteDatabase db = mDbHelper.getWritableDatabase();
    05.int deleted = 0;
    06.switch(URI_MATCHER.match(uri)){
    07.case CONTACTS_ID:
    08.selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=?");
    09.selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
    10.new String[]{Long.toString(ContentUris.parseId(uri))});
    11.Log.d("Test", "selectionArgs 0"+selectionArgs);
    12.deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);
    13.break;
    14.case CONTACTS:
    15.deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);
    16.Log.d("Test", "selectionArgs 1"+selectionArgs);
    17.break;
    18.}
    19. 
    20.// 更新数据时,通知其他ContentObserver
    21.getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);
    22. 
    23.return deleted;
    24.}



    (10) 实现getType方法,根据URI返回MIME类型,这里主要用来区分URI是获取集合还是单条记录,这个方法在这里暂时没啥用处,在使用Intent时有用。

    01.@Override
    02.public String getType(Uri uri) {
    03.// TODO Auto-generated method stub
    04.switch(URI_MATCHER.match(uri)){
    05.case CONTACTS:
    06.return ContactsData.CONTENT_TYPE;
    07.case CONTACTS_ID:
    08.return ContactsData.CONTENT_ITEM_TYPE;
    09.//        default:
    10.//            throw new IllegalArgumentException("Unknow URI: " + uri);
    11.}
    12.return null;
    13.}



    (11) 在AndroidManifest.xml中注册该ContentProvider,这样系统才找得到,当然你也可以设置相关的权限,这里就不设置了

    1.<provider
    2.android:name="com.johnny.testcontentprovider.ContactsContentProvider"
    3.android:authorities="com.johnny.contactsprovider">
    4. 
    5.</provider>



    (12)到现在为止,自定义ContentProvider的全部代码已经完成,下面创建一个简单的应用来测试一下。
    主要测试insert、update、delete、query这四个函数。

    01.private void insertContact1(){
    02.ContentValues values = new ContentValues();
    03.values.put(ContactsData.CONTACT_NAME, "James");
    04.values.put(ContactsData.CONTACT_TELEPHONE, "18888888888");
    05.values.put(ContactsData.CONTACT_CONTENT, "NBA Star");
    06.values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());
    07.Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);
    08.Log.d("Test", "uri = "+uri);
    09.}
    10. 
    11.private void deleteContact1(){
    12.int count = getContentResolver().delete(ContactsData.CONTENT_URI, ContactsData.CONTACT_NAME+"='James'", null);
    13.Log.d("Test", "count = "+count);
    14.}
    15. 
    16.private void updateContact1(){
    17.ContentValues values = new ContentValues();
    18.values.put(ContactsData.CONTACT_TELEPHONE, "16666666666");
    19.int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'", null);
    20.Log.d("Test", "count = "+count);
    21.}
    22. 
    23.private void queryContact1(){
    24.Cursor cursor = this.getContentResolver().query(ContactsData.CONTENT_URI, null, ContactsData.CONTACT_NAME+"='James'", null, null);
    25.Log.e("test ", "count=" + cursor.getCount());
    26.cursor.moveToFirst();
    27.while(!cursor.isAfterLast()) {
    28.String name = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_NAME));
    29.String telephone = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_TELEPHONE));
    30.long createDate = cursor.getLong(cursor.getColumnIndex(ContactsData.CONTACT_CREATE_DATE));
    31.Log.e("Test", "name: " + name);
    32.Log.e("Test", "telephone: " + telephone);
    33.Log.e("Test", "date: " + createDate);
    34. 
    35.cursor.moveToNext();
    36.}
    37.cursor.close();
    38.}




    (13)创建数据库监听器ContentObserver
    在MainActivity中加入以下代码:

    01.private ContentObserver mContentObserver = new ContentObserver(new Handler()) {
    02. 
    03.@Override
    04.public void onChange(boolean selfChange) {
    05.// TODO Auto-generated method stub
    06.Log.d("Test", "mContentObserver onChange");
    07.super.onChange(selfChange);
    08.}
    09. 
    10.};
    11.@Override
    12.protected void onCreate(Bundle savedInstanceState) {
    13.super.onCreate(savedInstanceState);
    14.setContentView(R.layout.activity_main);
    15. 
    16.if (savedInstanceState == null) {
    17.getSupportFragmentManager().beginTransaction()
    18..add(R.id.container, new PlaceholderFragment()).commit();
    19.}
    20. 
    21.getContentResolver().registerContentObserver(ContactsData.CONTENT_URI, true, mContentObserver);
    22. 
    23.}



    每次通过insert、delete、update改变数据库内容时,都会调用ContentObserver的onChange方法,因此,可以在这个方法内做出针对数据库变化的反应,比如更新UI等。

    2)、数据库的升级

    当应用发布一段时间之后,我们需要改变数据库的结构,那么就需要对数据库的升级了:
    将DatabaseHelper类中的DATABASE_VERSION设置为2,并且在onUpgrade函数中实现升级的代码:

    01.private class DatabaseHelper extends SQLiteOpenHelper{
    02. 
    03.static final String DATABASE_NAME = "test.db";
    04.static final int DATABASE_VERSION = 2;
    05. 
    06.public DatabaseHelper(Context context) {
    07.super(context, DATABASE_NAME, null, DATABASE_VERSION);
    08.// TODO Auto-generated constructor stub
    09.}
    10. 
    11.@Override
    12.public void onCreate(SQLiteDatabase db) {
    13.// TODO Auto-generated method stub
    14.db.execSQL(ContactsData.SQL_CREATE_TABLE);
    15.}
    16. 
    17.@Override
    18.public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    19.// TODO Auto-generated method stub
    20.Log.d("Test", "onUpgrade oldVersion = "+oldVersion+", newVersion = "+newVersion);
    21.//onCreate(db);
    22.for(int i = oldVersion+1;i <= newVersion;i++){
    23.switch(i){
    24.case 2:
    25.db.execSQL("ALTER TABLE " + ContactsData.TABLE_NAME + " ADD COLUMN " + ContactsData.CONTACT_GROUP + " TEXT");
    26.break;
    27.}
    28.}
    29.}
    30. 
    31.}


    下面是升级前后数据库的结果:

    用下面代码为DATABASE_VERSION = 2的数据库中的James设在组别和加入Howard联系人:

    01.private void modifyContact1(){
    02.ContentValues values = new ContentValues();
    03.values.put(ContactsData.CONTACT_GROUP, "Miami");
    04.int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'", null);
    05.Log.d("Test", "count = "+count);
    06.}
    07. 
    08.private void insertContact2(){
    09.ContentValues values = new ContentValues();
    10.values.put(ContactsData.CONTACT_NAME, "Howard");
    11.values.put(ContactsData.CONTACT_TELEPHONE, "13333333333");
    12.values.put(ContactsData.CONTACT_CONTENT, "NBA Star");
    13.values.put(ContactsData.CONTACT_GROUP, "Rockets");
    14.values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());
    15.Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);
    16.Log.d("Test", "uri = "+uri);
    17.}


    结果如下:

     

  • 相关阅读:
    phalcon—— PHP基础知识(一)
    仿淘宝商品浏览界面, 向上拉查看详情
    linux执行run文件显示cannot execute binary file
    atitit.client连接oracle数据库的方式总结
    LeetCode203:Remove Linked List Elements
    DotNetBar.Bar图标列表的使用
    3509.com 纵横天下虚拟主机,垃圾中的战斗机
    RT-Thread内核之线程调度(三)
    CSDN-Code平台公钥设置
    2014年工作中遇到的20个问题:81-100
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/4334862.html
Copyright © 2011-2022 走看看