赞
踩
ContentProvider——内容提供者。它是一个类,这个类主要是对Android系统中进行共享的数据进行包装,并提供了一组统一的访问接口供其他程序调用。这些被共享的数据,可以使系统自己的也可以使我们个人应用程序中的数据,ContentProvider使用表的形式来组织数据.
在Android中,数据的存储有很多种方式,最常用的就是SQLite和XML文件方式。在不同的应用程序间,其实数据是不能直接被相互访问和操作的,在这种情况下,ContentProvider很好的被用来解决了不同应用程序间数据共享的问题。
其实在Android系统中,已经为我们提供了许多ContentProvider,如:Contacts、Browser、CallLog、 Settings等等。那么,Android系统中提供了这么多的ContentProvider,另外还有我们自己公开的共享数据,我们在写程序的时 候,怎么才能让我们的应用程序知道去哪儿取、如何取这些数据呢?我们自然的会想到URI。
一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。
也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去。
外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,
重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据
URI(Uniform Resource Identifier)——统一资源定位符,URI在ContentProvider中代表了要操做的数据。
在Android系统中通常的URI格式为:content://com.wirelessqa.content.provider/profile/10
在万维网访问时通常用的URI格式为:http://www.XXXX.com/AAA/123
【扩展阅读】
1.要操作profile表中id为10的记录,可以构建这样的路径:/profile/10
2.要操作profile表中id为10的记录的name字段, profile/10/name
3.要操作profile表中的所有记录,可以构建这样的路径:/profile
4.要操作xxx表中的记录,可以构建这样的路径:/xxx
5.当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:要操作xml文件中profile节点下的name节点,可以构建这样的路径:/profile/name
6.如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:Uri uri = Uri.parse(“content://com.wirelessqa.content.provider/pofile”)
综上所述,content://com.wirelessqa.content.provider/profile/10/User 所代表的URI的意思为:标识com.wirelessqa.content.provider中proifle表中_ID为10的User项。
UriMatcher:用于匹配Uri,它的用法如下:
1. 首先把你需要匹配Uri路径全部给注册上,如下:
1 | //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。 |
2 | UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); |
3 | //如果match()方法匹配content://com.wirelessqa.content.provider/profile路径,返回匹配码为1 |
4 | uriMatcher.addURI(“com.wirelessqa.content.provider”, “profile”, 1 ); //添加需要匹配uri,如果匹配就会返回匹配码 |
5 | //如果match()方法匹配 content://com.wirelessqa.content.provider/profile/路径,返回匹配码为2 |
6 | uriMatcher.addURI(“com.wirelessqa.content.provider”, “profile/#”, 2 ); //#号为通配符 |
2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用 addURI()方法传入的第三个参数,假设匹配 content://com.wirelessqa.content.provider/profile路径,返回的匹配 码为1。
ContentUris:用于获取Uri路径后面的ID部分,它有两个比较实用的方法:
1 | withAppendedId(uri, id) // 用于为路径加上ID部分 |
2 | parseId(uri) //用于从路径中获取ID部分 |
ContentResolver:当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。
想要自己的程序里面的数据能够被其他程序所访问到,有以下步骤:
第一:首先生成一个继承contentprovider的类.
第二:在androidMainfest.xml里面添加一个provider的标签就可以了.
1 | <provider android:name= "MyProvider" android:authorities= "com.wirelessqa.content.provider" /> |
是不是很简单?其他程序访问的时候只要按以下步骤就可以访问到了:
1 | Uri uri=Uri.Uri.parse( "content://" +AUTHORY+ "/profile" ); |
AUTHORY其实就是 android:authorities的值.,注意.这里必须一样..否则系统是找不到的.也是就是 String AUTHORY=”content://com.wirelessqa.content.provider”
然后获取一个 ContentResolver mContentResolver=getContentResolver();这样就其他程序就可以反问我们的数据了
ContentResolver对应的几个方法:
其实和contentprovider里面的方法是一样的..他们所对应的数据,最终是会被传到我们在之前程序里面定义的那个contentprovider类的方法,至于你想要在这几个方法里面做什么事,随你
在AndroidManifest.xml的<application>和</application>之间加入:
1 | <provider android:name= "MyProvider" android:authorities= "com.wirelessqa.content.provider" /> |
01 | package com.wirlessqa.content.provider; |
02 |
03 | import android.net.Uri; |
04 |
05 | /** |
06 | * Profile类用于存放各种常量 |
07 | * |
08 | * @author www.wirelessqa.com 2013-2-26 下午11:01:46 |
09 | */ |
10 | public class Profile { |
11 |
12 | public static final String TABLE_NAME = "profile"; // 表格名称 |
13 |
14 | public static final String COLUMN_ID = "_id"; // 列表一,_ID,自动增加 |
15 |
16 | public static final String COLUMN_NAME = "name"; // 列表二,名称 |
17 |
18 | public static final String AUTOHORITY = "com.wirlessqa.content.provider"; |
19 | public static final int ITEM = 1; |
20 | public static final int ITEM_ID = 2; |
21 |
22 | // 如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头, |
23 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/profile"; |
24 | // 如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头 |
25 | public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/profile"; |
26 |
27 | public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/profile"); |
28 | } |
01 | package com.wirlessqa.content.provider; |
02 |
03 | import android.content.Context; |
04 | import android.database.SQLException; |
05 | import android.database.sqlite.SQLiteDatabase; |
06 | import android.database.sqlite.SQLiteOpenHelper; |
07 |
08 | /** |
09 | * 定义一个数据库类 |
10 | * |
11 | * @author www.wirelessqa.com 2013-2-26 下午11:01:46 |
12 | */ |
13 | public class DBHelper extends SQLiteOpenHelper { |
14 |
15 | private static final String DATABASE_NAME = "wirelessqa.db" ; // 数据库名 |
16 |
17 | private static final int DATABASE_VERSION = 1 ; // 版本号 |
18 |
19 | public DBHelper(Context context){ |
20 | super (context, DATABASE_NAME, null , DATABASE_VERSION); |
21 | } |
22 |
23 | @Override |
24 | public void onCreate(SQLiteDatabase db) throws SQLException { |
25 | // 创建的数据表中必须含有"_id"这个字段,这个字段是自增长的,插入的时候不用管这个字段,数据库会自己递增地加上 |
26 | db.execSQL( "CREATE TABLE IF NOT EXISTS " + Profile.TABLE_NAME + "(" + Profile.COLUMN_ID |
27 | + " INTEGER PRIMARY KEY AUTOINCREMENT," + Profile.COLUMN_NAME + " VARCHAR NOT NULL);" ); |
28 | } |
29 |
30 | @Override |
31 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) throws SQLException { |
32 | // 删除并创建表格 |
33 | db.execSQL( "DROP TABLE IF EXISTS " + Profile.TABLE_NAME + ";" ); |
34 | onCreate(db); |
35 | } |
36 | } |
001 | package com.wirlessqa.content.provider; |
002 |
003 | import android.content.ContentProvider; |
004 | import android.content.ContentUris; |
005 | import android.content.ContentValues; |
006 | import android.content.UriMatcher; |
007 | import android.database.Cursor; |
008 | import android.database.SQLException; |
009 | import android.database.sqlite.SQLiteDatabase; |
010 | import android.net.Uri; |
011 |
012 | /** |
013 | * contentprovider的调用者有可能是Activity,Service,Application这3种context,被谁调用,getContext就是谁 |
014 | * @author www.wirelessqa.com 2013-2-26 下午11:01:46 |
015 | */ |
016 | public class MyProvider extends ContentProvider { |
017 |
018 | DBHelper mDbHelper = null ; |
019 | SQLiteDatabase db = null ; |
020 |
021 | private static final UriMatcher mMatcher; |
022 | // 1.第一步把你需要匹配Uri路径全部给注册上 |
023 | static { |
024 | // UriMatcher:用于匹配Uri |
025 | // 常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。 |
026 | mMatcher = new UriMatcher(UriMatcher.NO_MATCH); |
027 | // 如果match()方法匹配content://com.wirlessqa.content.provider/profile路径,返回匹配码为1 |
028 | mMatcher.addURI(Profile.AUTOHORITY, Profile.TABLE_NAME, Profile.ITEM); // 添加需要匹配uri,如果匹配就会返回匹配码 |
029 | // 如果match()方法匹配 content://com.wirlessqa.content.provider/profile/#路径,返回匹配码为2 |
030 | mMatcher.addURI(Profile.AUTOHORITY, Profile.TABLE_NAME + "/#" , Profile.ITEM_ID); // #号为通配符 |
031 |
032 | // 注册完需要匹配的Uri后,就可以使用mMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码, |
033 | // 匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.wirelessqa.content.provider/profile路径,返回的匹配码为1 |
034 | } |
035 |
036 | // onCreate()方法在ContentProvider创建后就会被调用,Android系统运行后,ContentProvider只有在被第一次使用它时才会被创建。 |
037 | @Override |
038 | public boolean onCreate() { |
039 | mDbHelper = new DBHelper(getContext()); |
040 |
041 | db = mDbHelper.getReadableDatabase(); |
042 |
043 | return true ; |
044 | } |
045 |
046 |
047 | @Override |
048 | public int delete(Uri uri, String selection, String[] selectionArgs) { |
049 | long rowId; |
050 | if (mMatcher.match(uri) != Profile.ITEM) { |
051 | throw new IllegalArgumentException( "Unknown URI" + uri); |
052 | } |
053 | rowId = db.delete(Profile.TABLE_NAME, selection, selectionArgs); |
054 | // if(rowId>0){ |
055 | // Uri noteUri = ContentUris.withAppendedId(Profile.CONTENT_URI, rowId); |
056 | // getContext().getContentResolver().notifyChange(noteUri, null); |
057 | // } |
058 | return 0 ; |
059 | } |
060 |
061 | @Override |
062 | public String getType(Uri uri) { |
063 | switch (mMatcher.match(uri)) { |
064 | case Profile.ITEM: |
065 | return Profile.CONTENT_TYPE; |
066 | case Profile.ITEM_ID: |
067 | return Profile.CONTENT_ITEM_TYPE; |
068 | default : |
069 | throw new IllegalArgumentException( "Unknown URI" + uri); |
070 | } |
071 | } |
072 |
073 | // 外部应用程序通过这个方法向 ContentProvider添加数据。 |
074 | @Override |
075 | public Uri insert(Uri uri, ContentValues values) { |
076 | long rowId; |
077 | // mMatcher.match(uri)对输入的Uri进行匹配,如果匹配就返回匹配码 |
078 | if (mMatcher.match(uri) != Profile.ITEM) { |
079 | throw new IllegalArgumentException( "Unknown URI" + uri); |
080 | } |
081 | rowId = db.insert(Profile.TABLE_NAME, null , values); //向数据库里插入数据 |
082 | if (rowId > 0 ) { |
083 | // ContentUris.withAoppendedId 用于为路径加上ID部分 |
084 | Uri noteUri = ContentUris.withAppendedId(Profile.CONTENT_URI, rowId); |
085 | // 当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成 |
086 | //ContentResolver是属于context的,通过getContentResolver获取 |
087 | //ContentResolver可以通过registerContentObserver注册观察者(观察者是ContentObserver 的派生类) |
088 | //一旦ContentProvider操作的数据变化后,调用ContentResolver的notifyChange方法即可通知到观察者(回调观察者的onChange方法) |
089 | //注册观察者不是必须的,所有notifyChange不是必须调用的 |
090 | getContext().getContentResolver().notifyChange(noteUri, null ); |
091 | return noteUri; |
092 | } |
093 |
094 | throw new SQLException( "Failed to insert row into " + uri); |
095 | } |
096 |
097 | @Override |
098 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { |
099 | Cursor c = null ; |
100 | switch (mMatcher.match(uri)) { |
101 | case Profile.ITEM: |
102 | c = db.query(Profile.TABLE_NAME, projection, selection, selectionArgs, null , null , sortOrder); |
103 | break ; |
104 | case Profile.ITEM_ID: |
105 | c = db.query(Profile.TABLE_NAME, projection, Profile.COLUMN_ID + "=" + uri.getLastPathSegment(), |
106 | selectionArgs, null , null , sortOrder); |
107 | break ; |
108 | default : |
109 | throw new IllegalArgumentException( "Unknown URI" + uri); |
110 | } |
111 | //从而在ContentService中注册contentservice的观察者,这个观察者是cursor的内部成员(cursor是一个接口,此处真正的cursor是sqlitecursor) |
112 | //这样每个查询返回的cursor都能在contentprovider对应数据改变时得到通知,因为这些cursor都有一个成员注册成了contentservice的观察者 |
113 | c.setNotificationUri(getContext().getContentResolver(), uri); |
114 | return c; |
115 | } |
116 |
117 | @Override |
118 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { |
119 | // TODO Auto-generated method stub |
120 | return 0 ; |
121 | } |
122 |
123 | } |
1. contentprovider的调用者有可能是Activity,Service,Application这3种context,被谁调用getContext就是谁
2. ContentResolver是属于context的,通过getContentResolver获取,ContentResolver可以通过registerContentObserver注册观察者(观察者是ContentObserver 的派生类),一旦ContentProvider操作的数据变化后,调用ContentResolver的notifyChange方法即可通知到观察者(回调观察者的onChange方法),注册观察者不是必须的,所有notifyChange不是必须调用的
3. ContentValues 一次只能放入一行数据(可以使多个字段,即多个名值对)
4. onCreate只在ContentProvider第一次被调用的时候调用,多次调用共享的是一个ContentProvider
5. Cursor即数据库查询结果的操作游标,用户随机访问查询结果,获取查询结果的数目等
6. 在Content Resolver中有几个需要注意的接口:
notifyChange (Uri uri, ContentObserver observer, boolean syncToNetwork);
registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserver observer);
unregisterContentObserver (ContentObserver observer);
7.在Cursor中也有几个类似的接口:
setNotificationUri (ContentResolver cr, Uri uri);
registerContentObserver (ContentObserver observer);
unregisterContentObserver (ContentObserver observer);
8.要在query中调用
setNotificationUri(ContentResolver cr, Uri notifyUri)从而在ContentService中注册contentservice的观察者,这个观察者是cursor的内部成员(cursor是一个接口,此处真正的cursor是sqlitecursor),这样每个查询返回的cursor都能在contentprovider对应数据改变时得到通知,因为这些cursor都有一个成员注册成了contentservice的观察者
那么数据改变时,是怎么通知这些cursor的观察者成员呢?
这就需要在delete,update,insert这些改变数据的方法中调用contentresolver的notifychange方法,这个notifychange实际调用的是contentservice的notifychange,在这个notifychange方法里,contentservice查找所有在其中注册的观察者,找出对这次更新数据感兴趣的观察者(通过uri),然后通知它们数据改变
contentservice有很多观察者,它是系统服务,管理系统中所有contentprovider,通过uri匹配查找观察者,通知符合要求的观察者(实际就是通知对应的cursor)
cursor得到通知以后做些什么呢?
事实上,cursor也可以有很多观察者,因为一个查询出来的结果集可能会被多个地方使用(比如多个listview使用一个cursor),cursor对应的数据改变的时候,它也会通知到所有关注它的观察者(调用它们的onchange)
那么,cursor的观察者是怎么注册进去的呢?
是通过cursor的registerContentObserver这个方法注册进去的
以下例作为解析,在simplecursoradapter(继承自cursoradapter)的构造函数中,调用父类cursoradapter的构造函数,在这个构造函数里,
调用了cursor的registerContentObserver,把一个继承自contentobserver的成员对象作为观察者注册进了cursor的观察者里。这样cursor变化了,就会通知simplecursoradapter,simplecursoradapter里就可以重新查询结果并显示在listview中
以上其实就是两个观察者模式,cursor观察contentservice,同时cursor又被cursoradapter观察(都是通过其成员变量观察,不是直接观察),我们也可以通过contentservice和cursoradapter提供的接口注册我们自己的观察者,也就是说contentservice的观察者可以不是cursor,cursor的观察者可以不是cursoradapter
01 | package com.wirlessqa.content.provider; |
02 |
03 | import android.app.ListActivity; |
04 | import android.content.ContentResolver; |
05 | import android.content.ContentValues; |
06 | import android.database.Cursor; |
07 | import android.os.Bundle; |
08 | import android.widget.SimpleCursorAdapter; |
09 |
10 | /** |
11 | * @author www.wirelessqa.com 2013-2-26 下午11:00:29 |
12 | */ |
13 | public class MainActivity extends ListActivity { |
14 |
15 | private SimpleCursorAdapter adapter = null ; |
16 | private Cursor mCursor = null ; |
17 | private ContentResolver mContentResolver = null ; |
18 |
19 | @Override |
20 | public void onCreate(Bundle savedInstanceState) { |
21 | super .onCreate(savedInstanceState); |
22 | initData(); |
23 | initAdapter(); |
24 | } |
25 |
26 | public void initData() { |
27 | mContentResolver = getContentResolver(); |
28 | // 删除一条记录可以用下面的方法 |
29 | // String where = "_id = '1'"; |
30 | // mContentResolver.delete(Profile.CONTENT_URI, where, null); |
31 | // 填充数据 |
32 | for ( int i = 0 ; i < 20 ; i++) { |
33 | // ContentValues 和HashTable类似都是一种存储的机制 但是两者最大的区别就在于 |
34 | // contenvalues只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西 |
35 | ContentValues values = new ContentValues(); |
36 | values.put(Profile.COLUMN_NAME, i + " 网址:www.wirelessqa.com" ); |
37 | // 通过ContentResolver来向数据库插入数据 |
38 | mContentResolver.insert(Profile.CONTENT_URI, values); |
39 | } |
40 | } |
41 |
42 | public void initAdapter() { |
43 | // 查询表格,并获得Cursor |
44 | // 查询全部数据 |
45 | mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[] { Profile.COLUMN_ID, Profile.COLUMN_NAME }, |
46 | null , null , null ); |
47 |
48 | // 查询部分数据 |
49 | // String selection = Profile.COLUMN_ID + " LIKE '%1'"; |
50 | // mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, |
51 | // selection, null, null); |
52 |
53 | // 查询一个数据 |
54 | // Uri uri = ContentUris.withAppendedId(Profile.CONTENT_URI, 50); |
55 | // mCursor = mContentResolver.query(uri, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, null, null, null); |
56 |
57 | startManagingCursor(mCursor); |
58 |
59 | // 设置adapter |
60 | adapter = new SimpleCursorAdapter( this , android.R.layout.simple_list_item_2, mCursor, new String[] { |
61 | Profile.COLUMN_ID, Profile.COLUMN_NAME }, new int [] { android.R.id.text1, android.R.id.text2 }); |
62 | setListAdapter(adapter); |
63 | } |
64 |
65 | } |
此文参考多份网络上的文章
源码下载:http://download.csdn.net/detail/wirelessqa/5091983
本文链接:【Android数据存储】ContentProvider详细介绍(附实例源码)
转载声明:本站文章若无特别说明,皆为原创,转载请注明来源:WirelessQA,谢谢!^^
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。