Android ContentProvider 完全解析及DEMO(最具说服力)

来源:互联网 发布:网络连接突然出现叹号 编辑:程序博客网 时间:2024/06/07 05:06

转载来自:  http://blog.csdn.net/yhaolpz/article/details/51304345


一:为什么使用ContentProvider,它有什么作用?


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

2) 使用ContentProvider,应用程序可以实现数据共享

3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)


多个程序打开数据库,读和写都必须先通过ConrtentProvide ,然后再修改的!
否则3个程序同时打开数据库是会出现问题的!


Android系统提供了像SharedPreferences这类简单的跨越程序边界的访问方法,但这些方法都存在一定的局限性。ContentProvider(数据提供者)是应用程序之间共享数据的一种接口机制,是一种更为高级的数据共享方法。

二:ContentProvider机理

调用关系

ContentProvider调用关系

在创建ContentProvider前,首先要实现底层的数据源,数据源包括数据库、文件系统或网络等,然后继承ContentProvider类中实现基本数据操作的接口函数。调用者不能直接调用ContentProvider的接口函数,需要通过ContentResolver对象,通过URI间接调用ContentProvider。 

 ContentProvider提供的数据形式

ContentProvider的数据集类似于数据库的数据表,每行是一条记录,每列具有相同的数据类型。每条记录都包含一个长整型的字段 _ID,用来唯一标识每条记录。ContentProvider可以提供多个数据集,调用者使用URI对不同数据集的数据进行操作。

通用资源标识符(Uniform Resource Identifier)

URI是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对任何(包括本地和互联网)的资源通过特定的协议进行交互操作。在ContentProvider机制中,使用ContentResolver对象通过URI定位ContentProvider提供的资源。 
ContentProvider使用的URI语法结构如下:

    content://<authority>/<data_path>/<id>
  • 1
  • 1
  • content:// 是通用前缀,表示该UIR用于ContentProvider定位资源。
  • < authority > 是授权者名称,用来确定具体由哪一个ContentProvider提供资源。因此一般< authority >都由类的小写全称组成,以保证唯一性。
  • 里面的<>代表的是泛型,不要写入进去,比如:content://com.example.peopleprovider/people
  • < data_path > 是数据路径,用来确定请求的是哪个数据集。如果ContentProvider近提供一个数据集,数据路径则可以省略;如果ContentProvider提供多个数据集,数据路径必须指明具体数据集。数据集的数据路径可以写成多段格式,例如people/girl和people/boy。
  • < id > 是数据编号,用来唯一确定数据集中的一条记录,匹配数据集中_ID字段的值。如果请求的数据不只一条,< id >可以省略。

如请求整个people数据集的URI为:

content://com.example.peopleprovider/people
  • 1
  • 1

而请求people数据集中第3条数据的URI则应写为:

content://com.example.peopleprovider/people/3

三:创建数据提供者

1. 创建一个类让其继承ContentProvider,并重载6个函数

  • onCreate() 
    一般用来初始化底层数据集和建立数据连接等工作

  • getType() 
    用来返回指定URI的MIME数据类型,若URI是单条数据,则返回的MIME数据类型以vnd.android.cursor.item开头;若URI是多条数据,则返回的MIME数据类型以vnd.android.cursor.dir/开头。

如果要处理的数据类型是一种比较新的类型

  • 你就必须先定义一个新的MIME类型,以供ContentProvider.geType(url)来返回。
  • MIME类型有两种形式: 
    1. 一种是为指定的单个记录的
    2. 另一种是为多条记录的。

这里给出一种常用的格式:


这里给出一种常用的格式:

vnd.android.cursor.item/vnd.yourcompanyname.contenttype // 单个记录的MIME类型比如, 一个请求列车信息的URIcontent://com.example.transportationprovider/trains/122 可能就会返回typevnd.android.cursor.item/vnd.example.rail这样一个MIME类型
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
vnd.android.cursor.dir/vnd.yourcompanyname.contenttype // 多个记录的MIME类型比如, 一个请求所有列车信息的URIcontent://com.example.transportationprovider/trains 可能就会返回vnd.android.cursor.dir/vnd.example.rail这样一个MIME 类型

  • insert()、delete()、update()、query() 
    用于对数据集的增删改查操作。

2. 声明CONTENT_URI,实现UriMatcher

private static final int MULTIPLE_PEOPLE = 1;//访问表的所有列private static final int SINGLE_PEOPLE = 2;//访问单独的列private static final UriMatcher uriMatcher ;static {    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);    uriMatcher.addURI(People.AUTHORITY, People.PATH_MULTIPLE, MULTIPLE_PEOPLE);    uriMatcher.addURI(People.AUTHORITY, People.PATH_SINGLE, SINGLE_PEOPLE);}

其中UriMatcher类引用官方文档中的解释:

Utility class to aid in matching URIs in content providers.

可见UriMatcher本质上是一个文本过滤器,用在contentProvider中帮助我们过滤,分辨出查询者想要查询哪个数据表。 
UriMatcher的构造函数中,UriMatcher.NO_MATCH是URI无匹配时的返回代码,值为-1。 addURI() 方法用来添加新的匹配项,语法为:

public void addURI(String authority, String path, int code)
  • 1
  • 1

其中authority表示匹配的授权者名称,path表示数据路径(#代表任何数字),code表示返回代码。

@Overridepublic String getType(Uri uri) {    switch (uriMatcher.match(uri)){        case MULTIPLE_PEOPLE://多條數據的處理            return People.MIME_TYPE_MULTIPLE;        case SINGLE_PEOPLE://單條數據的處理            return People.MIME_TYPE_SINGLE;        default:            throw new IllegalArgumentException("Unkown uro:"+uri);    }}

3. 注册ContentProvider

在AndroidManifest.xml文件中的 application节点下使用< provider >标签注册。示例:

<provider    android:authorities="contenprovide.peng.cx.com.mycontentprovidedemo"    android:name=".PeopleProvider" />
上例中注册了一个授权者名称为contenprovide.peng.cx.com.mycontentprovidedemo,其实现类为 Peopleprovider 。

四:使用数据提供者

每个Android组件都有一个ContentResolver对象,通过调用getContentResolver() 方法可得到ContentResolver对象。

demo实例:



public class People {    public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";    public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";    public static final String MIME_ITEM = "vnd.example.people";    public static final String MIME_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MIME_ITEM ;    public static final String MIME_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MIME_ITEM ;    public static final String AUTHORITY = "com.example.peopleprovider";    public static final String PATH_SINGLE = "people/#";    public static final String PATH_MULTIPLE = "people";    /**     * 封裝標準的形式:content://<authority>/<data_path>/<id>     */    public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;    /**     * 暴露和共享的URL     */    public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);    /**     * 數據庫表的字段     */    public static final String KEY_ID = "_id";    public static final String KEY_NAME = "name";    public static final String KEY_AGE = "age";    public static final String KEY_HEIGHT = "height";}

/** * 继承ContentProvider ,实现他的所有方法  on 2017/5/3. */public class PeopleProvider extends ContentProvider{    private static final String DB_NAME="people.db";    private static final String DB_TABLE="peopleinfo";    private static final int DB_VERSION = 1;    private SQLiteDatabase db;    private DBOpenHelper dbOpenHelper;    private static final int MULTIPLE_PEOPLE = 1;//访问表的所有列    private static final int SINGLE_PEOPLE = 2;//访问单独的列    private static final UriMatcher uriMatcher ;    static {        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);        uriMatcher.addURI(People.AUTHORITY, People.PATH_MULTIPLE, MULTIPLE_PEOPLE);        uriMatcher.addURI(People.AUTHORITY, People.PATH_SINGLE, SINGLE_PEOPLE);    }    @Override    public boolean onCreate() {        Context context = getContext();        dbOpenHelper = new DBOpenHelper(context, DB_NAME, null, DB_VERSION);        db = dbOpenHelper.getWritableDatabase();        if(db == null){            return false;        }else{            return true;        }    }    @Nullable    @Override    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();//API        qb.setTables(DB_TABLE);        switch (uriMatcher.match(uri)){            case SINGLE_PEOPLE:                qb.appendWhere(People.KEY_ID+"="+uri.getPathSegments().get(1));                break;            default:                break;        }        Cursor cursor = qb.query(db,                projection,                selection,                selectionArgs,                null,                null,                sortOrder);        cursor.setNotificationUri(getContext().getContentResolver(), uri);        return cursor;    }    @Nullable    @Override    public String getType(Uri uri) {        switch (uriMatcher.match(uri)){            case MULTIPLE_PEOPLE://多條數據的處理                return People.MIME_TYPE_MULTIPLE;            case SINGLE_PEOPLE://單條數據的處理                return People.MIME_TYPE_SINGLE;            default:                throw new IllegalArgumentException("Unkown uro:"+uri);        }    }    @Nullable    @Override    public Uri insert(Uri uri, ContentValues values) {        //如果添加成功,利用新添加的Id        long id =db.insert(DB_TABLE, null, values);        if(id>0){            //content://contacts/people/45 这个URI就可以写成如下形式:           // Uri person = ContentUris.withAppendedId(People.CONTENT_URI,  45);            Uri newUri = ContentUris.withAppendedId(People.CONTENT_URI, id);            //通知监听器,数据已经改变            getContext().getContentResolver().notifyChange(newUri, null);            return newUri;        }        throw new SQLException("failed to insert row into " + uri);    }    @Override    public int delete(Uri uri, String selection, String[] selectionArgs) {        int count = 0;        switch (uriMatcher.match(uri)){            case MULTIPLE_PEOPLE:                count = db.delete(DB_TABLE, selection, selectionArgs);                break;            case SINGLE_PEOPLE:                String segment = uri.getPathSegments().get(1);                count = db.delete(DB_TABLE, People.KEY_ID + "=" + segment, selectionArgs);                break;            default:                throw new IllegalArgumentException("Unsupported URI:" + uri);        }        getContext().getContentResolver().notifyChange(uri,null);        return count;    }    @Override    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {        int count;        switch (uriMatcher.match(uri)){            case MULTIPLE_PEOPLE:                count = db.update(DB_TABLE, values, selection, selectionArgs);                break;            case SINGLE_PEOPLE:                String segment = uri.getPathSegments().get(1);                count = db.update(DB_TABLE, values, People.KEY_ID + "=" + segment, selectionArgs);                break;            default:                throw new IllegalArgumentException("Unknow URI: " + uri);        }        getContext().getContentResolver().notifyChange(uri, null);        return count;    }    private static class DBOpenHelper extends SQLiteOpenHelper {        private static final  String DB_CREATE = "create table "+                DB_TABLE+"("+People.KEY_ID+" integer primary key autoincrement, "+                People.KEY_NAME+" text not null, "+People.KEY_AGE+" integer, "+                People.KEY_HEIGHT+" float);";        public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {            super(context, name, factory, version);        }        @Override        public void onCreate(SQLiteDatabase db) {            db.execSQL(DB_CREATE);        }        @Override        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {            db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);            onCreate(db);        }    }

/** * 只需要People.CONTENT_URI * @param name * @param age * @param heigth */public void add(String name,int age,float heigth){    ContentValues values = new ContentValues();    values.put(People.KEY_NAME, name);    values.put(People.KEY_AGE, age);    values.put(People.KEY_HEIGHT, heigth);    Uri newUri = resolver.insert(People.CONTENT_URI, values);    tv_show.setText("添加成功,URI:" + newUri);}/** * 同樣只需要URL */public void delete(){    resolver.delete(People.CONTENT_URI, null, null);    String msg = "数据全部删除";    tv_show.setText(msg);}public void update(String name,int age,float height,String id){    ContentValues values = new ContentValues();    values.put(People.KEY_NAME, name);    values.put(People.KEY_AGE, age);    values.put(People.KEY_HEIGHT, height);    Uri uri = Uri.parse(People.CONTENT_URI_STRING + "/" + id);    int result = resolver.update(uri, values, null, null);    String msg = "更新ID" + id + "的数据" + (result > 0 ? "成功" : "失败");    tv_show.setText(msg);}public void query(){    Cursor cursor = resolver.query(People.CONTENT_URI,            new String[]{People.KEY_ID, People.KEY_NAME, People.KEY_AGE, People.KEY_HEIGHT},            null, null, null);    if (cursor == null) {        tv_show.setText("数据库中没有数据");        return;    }    tv_show.setText("数据库:" + String.valueOf(cursor.getCount()) + "条记录");    String msg= "";    if (cursor.moveToFirst()) {        do {            msg += "ID: " + cursor.getString(cursor.getColumnIndex(People.KEY_ID)) + ",";            msg += "姓名: " + cursor.getString(cursor.getColumnIndex(People.KEY_NAME)) + ",";            msg += "年龄: " + cursor.getInt(cursor.getColumnIndex(People.KEY_AGE)) + ",";            msg += "身高: " + cursor.getFloat(cursor.getColumnIndex(People.KEY_HEIGHT)) + ",";        } while (cursor.moveToNext());    }    tv_display.setText(msg);}

补充讲解:


//content://contacts/people/45 这个URI就可以写成如下形式:// Uri person = ContentUris.withAppendedId(People.CONTENT_URI,  45); Uri newUri = ContentUris.withAppendedId(People.CONTENT_URI, id);

Uri newUri = ContentUris.withAppendedId(People.CONTENT_URI, id)
我们使用SQLiteQueryBuilder来辅助数据库查询操作,使用这个类的好处是我们可以不把数据库表的字段暴露出来,而是提供别名给第三方应用程序使用,这样就可以把数据库表内部设计隐藏起来,方便后续扩展和维护。
  • SQLiteQueryBuilder

public class 
SQLiteQueryBuilder 
extends Object

This is a convience class that helps build SQL queries to be sent to 
SQLiteDatabase objects.

  • uri.getPathSegments()

public abstract List getPathSegments () 
Added in API level 1 
Gets the decoded path segments. 
Returns 
decoded path segments, each without a leading or trailing ‘/’

  • uri.getPathSegments().get(position)

public abstract E get (int location) 
Added in API level 1 
Returns the element at the specified location in this List. 
Parameters 
location 
the index of the element to return. 
Returns 
the element at the specified location. 
Throws 

问题:

1.PeopleProvider:什么时候创建数据库?怎么使用ContentProvider?

MyProvider是由ActivityThread负责启动的,ActivityThread对应应用进程的主线程,即在应用进程启动时,会将ContentProvider启动起来。

所以程序要先运行一遍,然后生成了数据库就可以,注册了ContentProvider,会自动执行ContentProvider的OnCreate方法

只有一个表,怎么会有2个URL,对的,用于提供不同的查询条件!

多个记录和单个记录的操作

  content://contacts/people/       这个URI将返回设备上的所有联系人信息

content://contacts/people/45     这个URI返回单个结果(联系人信息中ID为45的联系人记录)

2.怎么得到系统电话本,图片的URL和字段呢?

通过看源码:在源码/Provide里面的

总结:

1.增删改查都必须用到给的URL

2.contentprovider的用户都不可能直接访问到contentprovider实例,只能通过ContentResolver在中间代理。


3.说说 ContentProvider、ContentResolver、ContentObserver 之间的关系

a. ContentProvider 内容提供者,用于对外提供数据 
b. ContentResolver.notifyChange(uri)发出消息 ,内容解析者,用于获取内容提供者提供的数据 
c. ContentResolver 内容解析者,用于获取内容提供者提供的数据 
d. ContentObserver 内容监听器,可以监听数据的改变状态 
e. ContentResolver.registerContentObserver()监听消息。


面试题:多个进程同时调用一个ContentProvider的query获取数据,ContentPrvoider是如何反应的呢?
标准答案:一个content provider可以接受来自另外一个进程的数据请求。尽管ContentResolver与ContentProvider类隐藏了实现细节,但是ContentProvider所提供的query(),insert(),delete(),update()都是在ContentProvider进程的线程池中被调用执行的,而不是进程的主线程中。这个线程池是有Binder创建和维护的,其实使用的就是每个应用进程中的Binder线程池。

面试题:你觉得Android设计ContentProvider的目的是什么呢?
标准答案:1. 隐藏数据的实现方式,对外提供统一的数据访问接口;
2.更好的数据访问权限管理。ContentProvider可以对开发的数据进行权限设置,不同的URI可以对应不同的权限,只有符合权限要求的组件才能访问到ContentProvider的具体操作。
3.ContentProvider封装了跨进程共享的逻辑,我们只需要Uri即可访问数据。由系统来管理ContentProvider的创建、生命周期及访问的线程分配,简化我们在应用间共享数据(进程间通信)的方式。我们只管通过ContentResolver访问ContentProvider所提示的数据接口,而不需要担心它所在进程是启动还是未启动。

面试题:运行在主线程的ContentProvider为什么不会影响主线程的UI操作?
标准答案:
ContentProvider的onCreate()是运行在UI线程的,而query(),insert(),delete(),update()是运行在线程池中的工作线程的,所以调用这向个方法并不会阻塞ContentProvider所在进程的主线程,但可能会阻塞调用者所在的进程的UI线程!

所以,调用ContentProvider的操作仍然要放在子线程中去做。虽然直接的CRUD的操作是在工作线程的,但系统会让你的调用线程等待这个异步的操作完成,你才可以继续线程之前的工作。





参考博客:

http://blog.csdn.net/yhaolpz/article/details/51304345

http://blog.csdn.net/u014136472/article/details/49907713

http://www.cnblogs.com/devinzhang/archive/2012/01/20/2327863.html

源码地址:不知道为什么上传不了


http://blog.csdn.net/yhaolpz/article/details/51304345

http://blog.csdn.net/u014136472/article/details/49907713

http://www.cnblogs.com/devinzhang/archive/2012/01/20/2327863.html
0 0
原创粉丝点击