Android ContentProvider、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

public class ContactsContentProvider extends ContentProvider{

    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        return false;
    }

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

    @Override
    public String getType(Uri arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Uri insert(Uri arg0, ContentValues arg1) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
            String arg4) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
        // TODO Auto-generated method stub
        return 0;
    }

}

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

public class ProviderMetaData {

    public static final String AUTHORITY = "com.johnny.contactsprovider";
    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);

    public static final class ContactsData implements BaseColumns{
        public static final String TABLE_NAME = "contacts";
        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE_NAME);

        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact";
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact";

        public static final String CONTACT_NAME = "name";
        public static final String CONTACT_TELEPHONE = "telephone";
        public static final String CONTACT_CREATE_DATE = "create_date";
        public static final String CONTACT_CONTENT = "content";
    public static final String CONTACT_GROUP = "group_name";

        public static final String DEFAULT_ORDERBY = "create_date DESC";

        public static final String SQL_CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
                                        + _ID + " INTEGER PRIMARY KEY,"
                                        + CONTACT_NAME + " VARCHAR(50),"
                                        + CONTACT_TELEPHONE + " VARCHAR(11),"
                                        + CONTACT_CONTENT +" TEXT,"
                                        + CONTACT_CREATE_DATE + " INTEGER"
                                        + ");" ;
    }
}

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类型,下面看实现源码:

static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    static final HashMap<String, String> CONTACTS_PROJECTION_MAP = new HashMap<String, String>();
    private static final int CONTACTS = 1;
    private static final int CONTACTS_ID = 2;
    static{
        final UriMatcher matcher = URI_MATCHER;
        matcher.addURI(ProviderMetaData.AUTHORITY, "contacts", CONTACTS);
        matcher.addURI(ProviderMetaData.AUTHORITY, "contacts/#", CONTACTS_ID);

        HashMap<String, String> map = CONTACTS_PROJECTION_MAP;
        map.put(ContactsData._ID, ContactsData._ID);
        map.put(ContactsData.CONTACT_NAME, ContactsData.CONTACT_NAME);
        map.put(ContactsData.CONTACT_TELEPHONE, ContactsData.CONTACT_TELEPHONE);
        map.put(ContactsData.CONTACT_CONTENT, ContactsData.CONTACT_CONTENT);
        map.put(ContactsData.CONTACT_CREATE_DATE, ContactsData.CONTACT_CREATE_DATE);
    }

这段代码是NoteContentProvider类中的,UriMatcher的工作原理:首先需要在UriMatcher中注册URI模式,每一个模式跟一个唯一的编号关联,注册之后,在使用中就可以根据URI得到对应的编号,当模式不匹配时,UriMatcher将返回一个NO_MATCH常量,这样就可以区分了。

(4) 还需为查询设置一个投影映射,主要是将抽象字段映射到数据库中真实的字段名称,因为这些字段有时是不同的名称,既抽象字段的值可以不跟数据库中的字段名称一样。这里使用HashMap来完成,key是抽象字段名称,value对应数据库中的字段名称,不过这里我把两者的值设置是一样的,在NoteContentProvider.java中添加如上面所示的代码。

(5) 在NoteContentProvider.java中创建一个内部类DatabaseHelper,继承自SQLiteOpenHelper,完成数据库表的创建、更新,这样可以通过它获得数据库对象,相关代码如下。

private class DatabaseHelper extends SQLiteOpenHelper{

        static final String DATABASE_NAME = "test.db";
        static final int DATABASE_VERSION = 1;

        public DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
            // TODO Auto-generated constructor stub
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            // TODO Auto-generated method stub
            db.execSQL(ContactsData.SQL_CREATE_TABLE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // TODO Auto-generated method stub
            onCreate(db);
        }

    }

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

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        // TODO Auto-generated method stub
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        switch(URI_MATCHER.match(uri)){
        case CONTACTS_ID:
            queryBuilder.setTables(ContactsData.TABLE_NAME);
            queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);
            queryBuilder.appendWhere(ContactsData.TABLE_NAME + "._id="+Long.toString(ContentUris.parseId(uri)));
            break;
        case CONTACTS:
            queryBuilder.setTables(ContactsData.TABLE_NAME);
            queryBuilder.setProjectionMap(CONTACTS_PROJECTION_MAP);
            break;
        }

        String orderBy;
        if(TextUtils.isEmpty(sortOrder))
        {
            orderBy = ContactsData.DEFAULT_ORDERBY;
        } else {
            orderBy = sortOrder;
        }
        SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, orderBy);

        return cursor;
    }

返回的是一个Cursor对象,它是一个行集合,包含0和多个记录,类似于JDBC中的ResultSet,可以前后移动游标,得到每行每列中的数据。注意的是,使用它需要调用moveToFirst(),因为游标默认是在第一行之前。

(7)实现insert方法,实现把记录插入到基础数据库中,然后返回新创建的记录的URI。

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        long id = db.insertOrThrow(ContactsData.TABLE_NAME, null, values);

        // 更新数据时,通知其他ContentObserver
        getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);

        if(id > 0){
            return ContentUris.withAppendedId(uri, id);
        }
        return null;
    }

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

  @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        int modified = 0;
        switch(URI_MATCHER.match(uri)){
        case CONTACTS_ID:
            selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=?");
            selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                    new String[]{Long.toString(ContentUris.parseId(uri))});
            Log.d("Test", "selectionArgs 0"+selectionArgs);
            modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);
            break;
        case CONTACTS:
            modified = db.update(ContactsData.TABLE_NAME, values, selection, selectionArgs);
            Log.d("Test", "selectionArgs 1"+selectionArgs);
            break;
        }

        // 更新数据时,通知其他ContentObserver
        getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);

        return modified;
    }

notifyChange函数是在更新数据时,通知其他监听对象。

(9)实现delete方法,该方法返回删除的记录数。

   @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        SQLiteDatabase db = mDbHelper.getWritableDatabase();
        int deleted = 0;
        switch(URI_MATCHER.match(uri)){
        case CONTACTS_ID:
            selection = DatabaseUtils.concatenateWhere(selection,ContactsData.TABLE_NAME + "._id=?");
            selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                    new String[]{Long.toString(ContentUris.parseId(uri))});
            Log.d("Test", "selectionArgs 0"+selectionArgs);
            deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);
            break;
        case CONTACTS:
            deleted = db.delete(ContactsData.TABLE_NAME, selection, selectionArgs);
            Log.d("Test", "selectionArgs 1"+selectionArgs);
            break;
        }

        // 更新数据时,通知其他ContentObserver
        getContext().getContentResolver().notifyChange(ContactsData.CONTENT_URI, null);

        return deleted;
    }

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

  @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        switch(URI_MATCHER.match(uri)){
        case CONTACTS:
            return ContactsData.CONTENT_TYPE;
        case CONTACTS_ID:
            return ContactsData.CONTENT_ITEM_TYPE;
//        default:
//            throw new IllegalArgumentException("Unknow URI: " + uri);
        }
        return null;
    }

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

      <provider
            android:name="com.johnny.testcontentprovider.ContactsContentProvider"
            android:authorities="com.johnny.contactsprovider">

        </provider>

(12)到现在为止,自定义ContentProvider的全部代码已经完成,下面创建一个简单的应用来测试一下。

主要测试insert、update、delete、query这四个函数。

    private void insertContact1(){
        ContentValues values = new ContentValues();
        values.put(ContactsData.CONTACT_NAME, "James");
        values.put(ContactsData.CONTACT_TELEPHONE, "18888888888");
        values.put(ContactsData.CONTACT_CONTENT, "NBA Star");
        values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());
        Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);
        Log.d("Test", "uri = "+uri);
    }

    private void deleteContact1(){
        int count = getContentResolver().delete(ContactsData.CONTENT_URI, ContactsData.CONTACT_NAME+"='James'", null);
        Log.d("Test", "count = "+count);
    }

    private void updateContact1(){
        ContentValues values = new ContentValues();
        values.put(ContactsData.CONTACT_TELEPHONE, "16666666666");
        int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'", null);
        Log.d("Test", "count = "+count);
    }

    private void queryContact1(){
        Cursor cursor = this.getContentResolver().query(ContactsData.CONTENT_URI, null, ContactsData.CONTACT_NAME+"='James'", null, null);
        Log.e("test ", "count=" + cursor.getCount());
        cursor.moveToFirst();
        while(!cursor.isAfterLast()) {
            String name = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_NAME));
            String telephone = cursor.getString(cursor.getColumnIndex(ContactsData.CONTACT_TELEPHONE));
            long createDate = cursor.getLong(cursor.getColumnIndex(ContactsData.CONTACT_CREATE_DATE));
            Log.e("Test", "name: " + name);
            Log.e("Test", "telephone: " + telephone);
            Log.e("Test", "date: " + createDate);

            cursor.moveToNext();
        }
        cursor.close();
    }

(13)创建数据库监听器ContentObserver

在MainActivity中加入以下代码:

    private ContentObserver mContentObserver = new ContentObserver(new Handler()) {

        @Override
        public void onChange(boolean selfChange) {
            // TODO Auto-generated method stub
            Log.d("Test", "mContentObserver onChange");
            super.onChange(selfChange);
        }

    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment()).commit();
        }

        getContentResolver().registerContentObserver(ContactsData.CONTENT_URI, true, mContentObserver);

    }

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

2)、数据库的升级

当应用发布一段时间之后,我们需要改变数据库的结构,那么就需要对数据库的升级了:

将DatabaseHelper类中的DATABASE_VERSION设置为2,并且在onUpgrade函数中实现升级的代码:

   private class DatabaseHelper extends SQLiteOpenHelper{

        static final String DATABASE_NAME = "test.db";
        static final int DATABASE_VERSION = 2;

        public DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
            // TODO Auto-generated constructor stub
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            // TODO Auto-generated method stub
            db.execSQL(ContactsData.SQL_CREATE_TABLE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // TODO Auto-generated method stub
            Log.d("Test", "onUpgrade oldVersion = "+oldVersion+", newVersion = "+newVersion);
            //onCreate(db);
            for(int i = oldVersion+1;i <= newVersion;i++){
                switch(i){
                case 2:
                    db.execSQL("ALTER TABLE " + ContactsData.TABLE_NAME + " ADD COLUMN " + ContactsData.CONTACT_GROUP + " TEXT");
                    break;
                }
            }
        }

    }

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

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

    private void modifyContact1(){
        ContentValues values = new ContentValues();
        values.put(ContactsData.CONTACT_GROUP, "Miami");
        int count = getContentResolver().update(ContactsData.CONTENT_URI,values, ContactsData.CONTACT_NAME+"='James'", null);
        Log.d("Test", "count = "+count);
    }

    private void insertContact2(){
        ContentValues values = new ContentValues();
        values.put(ContactsData.CONTACT_NAME, "Howard");
        values.put(ContactsData.CONTACT_TELEPHONE, "13333333333");
        values.put(ContactsData.CONTACT_CONTENT, "NBA Star");
        values.put(ContactsData.CONTACT_GROUP, "Rockets");
        values.put(ContactsData.CONTACT_CREATE_DATE, System.currentTimeMillis());
        Uri uri = getContentResolver().insert(ContactsData.CONTENT_URI, values);
        Log.d("Test", "uri = "+uri);
    }

结果如下:

参考:http://codingnow.cn/android/1078.html

Android ContentProvider、ContentResolver和ContentObserver的使用,布布扣,bubuko.com

时间: 2024-10-06 23:24:57

Android ContentProvider、ContentResolver和ContentObserver的使用的相关文章

Android ContentProvider数据共享

一.构造一个自己的Provider实现App之间数据共享 1.我们先来了解一下   Uri(统一资源定位符) 定义:每一个Content Provider使用一个公开的URI唯一标示其数据集,Android所提供的ContentProvider都存放android.provider包中 结构:分为A.B.C.D四个部分 A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的: B:URI 的标识,唯一标识ContentProvider,外部调用者可以根据这个标识来找到

android contentprovider内容提供者

contentprovider内容提供者:让其他app可以访问私有数据库(文件) 1.AndroidManifest.xml 配置provider <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.dbtest&q

Android ContentProvider完整案例

ContentData类,提供数据常量: /** * 提供ContentProvider对外的各种常量,当外部数据需要访问的时候,就可以参考这些常量操作数据. * @author HB * */ public class ContentData { public static final String AUTHORITY = "hb.android.contentProvider"; public static final String DATABASE_NAME = "te

Android ContentProvider 简单学习

当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.以前我们学习过文件的操作模式,通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样可以对外共享数据,但数据的访问方式会因数据存储的方式而不同,如:采用xml文件对外共享数据,需要进行xml解析来读写数据:采用sharedpreferences共享数据,需要使用sharedpreferences A

Android基础 : Android ContentProvider

Android 应用程序通过ContentProvider实现方式统一的数据共享功能. 外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,在Activity当中通过getContentResolver()可以得到当前应用的 ContentResolver实例. 参考文章 Android ContentProvider和getContentResolver

Android ContentProvider基本用法

转自:https://www.jianshu.com/p/601086916c8f 一.基本概念 ContentProvider是Android系统中提供的专门用户不同应用间进行数据共享的组件,提供了一套标准的接口用来获取以及操作数据,准许开发者把自己的应用数据根据需求开放给其他应用进行增删改查,而无须担心直接开放数据库权限而带来的安全问题.系统预置了许多ContentProvider用于获取用户数据,比如消息.联系人.日程表等. 二.ContentResolver 在ContentProvid

android,ContentProvider+ContentObserver+ContentResolver,用法。

这个是传智播客老师讲android开发时的一个图. 一. PersonProvider继承ContentProvider,实现ContentProvider中的数据操作类. 在需要监听的操作中添加添加数据变化通知. this.getContext().getContentResolver().notifyChange(uri, null); 第二个参数,数据变化的监听者,可以不设置,也即是设为null,如果给定了这个监听者,不管外面有多少个应用要设置监听,进行监听数据变化,这个getConten

ContentProvider ContentResolver ContentObserver 内容:提供、访问、监听

内容提供 public class PersonContentProvider extends ContentProvider{ private static final String AUTHORITY = "com.itheima28.sqlitedemo.providers.PersonContentProvider"; private static final int PRESON_INSERT_CODE = 0; // 操作person表添加的操作的uri匹配码 UriMat

Android ContentProvider+ContentObserver

说明:文章仅供本人学习记录所用. 1.理解含义: ContentProvider: 内容提供者,将数据以表的形式进行操作.主要实现应用程序间数据共享,操作系统本地数据(包括短       信.音频.视屏.数据库). ContentObserver:内容观察者,监听数据变化. 2.使用方法: ContentProvider: 1)步骤:新建MyProvider类继承ContentProvider类;注册URI;重写方法(onCreate.query.bulkInsert.insert.