转载来自: http://blog.csdn.net/yhaolpz/article/details/51304345
一:为什么使用ContentProvider,它有什么作用?
1) ContentProvider为存储和读取数据提供了统一的接口
2) 使用ContentProvider,应用程序可以实现数据共享
3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)
多个程序打开数据库,读和写都必须先通过ConrtentProvide ,然后再修改的! 否则3个程序同时打开数据库是会出现问题的!
Android系统提供了像SharedPreferences这类简单的跨越程序边界的访问方法,但这些方法都存在一定的局限性。ContentProvider(数据提供者)是应用程序之间共享数据的一种接口机制,是一种更为高级的数据共享方法。
在创建ContentProvider前,首先要实现底层的数据源,数据源包括数据库、文件系统或网络等,然后继承ContentProvider类中实现基本数据操作的接口函数。调用者不能直接调用ContentProvider的接口函数,需要通过ContentResolver对象,通过URI间接调用ContentProvider。
ContentProvider的数据集类似于数据库的数据表,每行是一条记录,每列具有相同的数据类型。每条记录都包含一个长整型的字段 _ID,用来唯一标识每条记录。ContentProvider可以提供多个数据集,调用者使用URI对不同数据集的数据进行操作。
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/3onCreate() 一般用来初始化底层数据集和建立数据连接等工作
getType() 用来返回指定URI的MIME数据类型,若URI是单条数据,则返回的MIME数据类型以vnd.android.cursor.item开头;若URI是多条数据,则返回的MIME数据类型以vnd.android.cursor.dir/开头。
这里给出一种常用的格式:
这里给出一种常用的格式:
vnd.android.cursor.item/vnd.yourcompanyname.contenttype // 单个记录的MIME类型 比如, 一个请求列车信息的URI content://com.example.transportationprovider/trains/122 可能就会返回 typevnd.android.cursor.item/vnd.example.rail 这样一个MIME类型 1234567 1234567vnd.android.cursor.dir/vnd.yourcompanyname.contenttype // 多个记录的MIME类型 比如, 一个请求所有列车信息的URI content://com.example.transportationprovider/trains 可能就会返回 vnd.android.cursor.dir/vnd.example.rail 这样一个MIME 类型insert()、delete()、update()、query() 用于对数据集的增删改查操作。
其中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表示返回代码。
@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); } }
在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在中间代理。
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