转载来自:  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 >标签注册。示例:

    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:////     */    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

更多相关文章

  1. android 线程 synchronized关键字
  2. Android Studio查看SQLite数据库方法大全
  3. Android ContentProvider(内容提供者)的使用
  4. android客户端从服务器端获取json数据并解析的实现代码
  5. android bundle 在activity之间传递数据 点击一组图片放大,再次点
  6. 复制assets下的数据库到SD卡
  7. Android中WebView和JavaScript之间传递json格式数据
  8. Android使用WebView加载网页及数据
  9. Android里子线程真的不能刷新UI吗?

随机推荐

  1. Oracle 12c数据库优化器统计信息收集的最
  2. zabbix中配置当memory剩余不足20%时触发
  3. Navicat 提示Cannot create oci environm
  4. coe_xfr_sql_profile.sql和coe_load_sql_
  5. [Warning] Using a password on the comm
  6. Hibernate快速入门+简单示例
  7. ERROR 2002 (HY000): Cant connect to lo
  8. RESTful架构剖析
  9. Centos7 Firewalld 解决防火墙问题
  10. Maven的安装和Eclipse集成