android---------provider-----------------

来源:互联网 发布:java旅游项目 编辑:程序博客网 时间:2024/05/16 01:22

注:讲解部分转自:http://www.cnblogs.com/linjiqin/archive/2011/05/28/2061396.html

例子部分转自:http://aijiawang-126-com.iteye.com/blog/655268

一、使用ContentProvider(内容提供者)共享数据

ContentProvider在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据进行添删改查。关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。那么,这里为何要使用ContentProvider对外共享数据呢?是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。
使用ContentProvider对外共享数据的好处是统一了数据的访问方式。
当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:

复制代码
public class PersonContentProvider extends ContentProvider{ public boolean onCreate() public Uri insert(Uri uri, ContentValues values) public int delete(Uri uri, String selection, String[] selectionArgs) public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) public String getType(Uri uri)}
复制代码

第二步需要在AndroidManifest.xml使用<provider>对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider ,ContentProvider采用了authorities(主机名/域名)对它进行唯一标识,你可以把ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:

<manifest.... > <application android:icon="@drawable/icon" android:label="@string/app_name"> <provider android:name=".PersonContentProvider" android:authorities="com.ljq.providers.personprovider"/> </application></manifest>

          

             

二、Uri介绍

Uri代表了要操作的数据,Uri主要包含了两部分信息:1》需要操作的ContentProvider ,2》对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:          

                 

ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content://
主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它
路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
要操作person表中id为10的记录,可以构建这样的路径:/person/10
要操作person表中id为10的记录的name字段, person/10/name
要操作person表中的所有记录,可以构建这样的路径:/person
要操作xxx表中的记录,可以构建这样的路径:/xxx
当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person")

                

             

三、UriMatcher类使用介绍

因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris 。掌握它们的使用,会便于我们的开发工作。
UriMatcher类用于匹配Uri,它的用法如下:
首先第一步把你需要匹配Uri路径全部给注册上,如下:

复制代码
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);//如果match()方法匹配content://com.ljq.provider.personprovider/person路径,返回匹配码为1sMatcher.addURI("com.ljq.provider.personprovider", "person", 1);//添加需要匹配uri,如果匹配就会返回匹配码//如果match()方法匹配content://com.ljq.provider.personprovider/person/230路径,返回匹配码为2sMatcher.addURI("com.ljq.provider.personprovider", "person/#", 2);//#号为通配符switch (sMatcher.match(Uri.parse("content://com.ljq.provider.personprovider/person/10"))) { case 1 break; case 2 break; default://不匹配 break;}
复制代码

注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.ljq.provider.personprovider/person路径,返回的匹配码为1 

              

                 

四、ContentUris类使用介绍

ContentUris类用于操作Uri路径后面的ID部分,它有两个比较实用的方法:
withAppendedId(uri, id)用于为路径加上ID部分:

Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person")Uri resultUri = ContentUris.withAppendedId(uri, 10); //生成后的Uri为:content://com.ljq.provider.personprovider/person/10

parseId(uri)方法用于从路径中获取ID部分:

Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person/10")long personid = ContentUris.parseId(uri);//获取的结果为:10

          

              

五、使用ContentProvider共享数据

ContentProvider类主要方法的作用:
public boolean onCreate():该方法在ContentProvider创建后就会被调用,Android开机后,ContentProvider在其它应用第一次访问它时才会被创建。
public Uri insert(Uri uri, ContentValues values):该方法用于供外部应用往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs):该方法用于供外部应用从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):该方法用于供外部应用更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):该方法用于供外部应用从ContentProvider中获取数据。
public String getType(Uri uri):该方法用于返回当前Url所代表数据的MIME类型。

如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,

例如:要得到所有person记录的Uri为content://com.ljq.provider.personprovider/person,那么返回的MIME类型字符串应该为:"vnd.android.cursor.dir/person"。

如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,

例如:得到id为10的person记录,Uri为content://com.ljq.provider.personprovider/person/10,那么返回的MIME类型字符串为:"vnd.android.cursor.item/person"。

              

                

六、使用ContentResolver操作ContentProvider中的数据

当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver 类提供了与ContentProvider类相同签名的四个方法:
public Uri insert(Uri uri, ContentValues values):该方法用于往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs):该方法用于从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):该方法用于更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):该方法用于从ContentProvider中获取数据。

这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作,

假设给定的是:Uri.parse("content://com.ljq.providers.personprovider/person/10"),那么将会对主机名为com.ljq.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。

使用ContentResolver对ContentProvider中的数据进行添加、删除、修改和查询操作:

复制代码
ContentResolver resolver = getContentResolver();Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person");//添加一条记录ContentValues values = new ContentValues();values.put("name", "linjiqin");values.put("age", 25);resolver.insert(uri, values); //获取person表中所有记录Cursor cursor = resolver.query(uri, null, null, null, "personid desc");while(cursor.moveToNext()){ Log.i("ContentTest", "personid="+ cursor.getInt(0)+ ",name="+ cursor.getString(1));}//把id为1的记录的name字段值更改新为zhangsanContentValues updateValues = new ContentValues();updateValues.put("name", "zhangsan");Uri updateIdUri = ContentUris.withAppendedId(uri, 2);resolver.update(updateIdUri, updateValues, null, null);//删除id为2的记录Uri deleteIdUri = ContentUris.withAppendedId(uri, 2);resolver.delete(deleteIdUri, null, null);
复制代码

            

               

七、监听ContentProvider中数据的变化

如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下:

public class PersonContentProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("person", "personid", values); getContext().getContentResolver().notifyChange(uri, null); }}

如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:

复制代码
getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"), true, new PersonObserver(new Handler()));public class PersonObserver extends ContentObserver{ public PersonObserver(Handler handler) { super(handler); } public void onChange(boolean selfChange) { //此处可以进行相应的业务处理 }}

--------------------------------例子部分:
当数据需要在应用程序间共享时,我们就可以利用ContentProvider为数据定义一个URI。之后其他应用程序对数据进行查询或者修改时,只需要从当前上下文对象获得一个ContentResolver(内容解析器)传入相应的URI就可以了。本节中将以前面创建的code.db数据库为例,向读者介绍如何定义一个ContentProvider,以及如何在其他程序中使用ContentResolver访问URI所指定的数据。 

9.3.1 定义ContentProvider(1) 

要为当前应用程序的私有数据定义URI,就需要专门定义一个继承自ContentProvider的类,然后根据不同的操作调用的方法去实现这些方法的功能。下面我们用SQLite2这个例子,来为它的数据库code.db定义一个URI。 

首先,在SQLite2的包中创建一个新类ContryCode.java,来装入所有与数据库操作有关的静态字段,以便于打包成JAR文件供其他应用程序调用。 

Java代码  收藏代码
  1. 1 package com.studio.android.chp9.ex3;    
  2. 2    
  3. 3 import android.net.Uri;    
  4. 4    
  5. 5 public class CountryCode {    
  6. 6    
  7. 7 public static final String DB_NAME = "code.db";    
  8. 8 public static final String TB_NAME = "countrycode";    
  9. 9 public static final int VERSION = 1;    
  10. 10    
  11. 11 public static final String ID = "_id";    
  12. 12 public static final String COUNTRY = "country";    
  13. 13 public static final String CODE = "code";    
  14. 14    
  15. 15 public static final String AUTHORITY =    
  16. 16 "com.studio.andriod.provider.countrycode";    
  17. 17 public static final int ITEM = 1;    
  18. 18 public static final int ITEM_ID = 2;    
  19. 19    
  20. 20 public static final String CONTENT_TYPE =    
  21. 21 "vnd.android.cursor.dir/vnd.studio.android.countrycode";    
  22. 22 public static final String CONTENT_ITEM_TYPE =    
  23. 23 "vnd.android.cursor.item/vnd.studio.android.countrycode";    
  24. 24    
  25. 25 public static final Uri CONTENT_URI =    
  26. 26 Uri.parse("content://" + AUTHORITY + "/item");    
  27. 27 }  

其中DB_NAME、TB_NAME和VERSION分别定义了数据库和表的名称以及数据库的版本号。ID、COUNTRY和CODE分别定义的是表中的各个列的列名。AUTHORITY定义了标识ContentProvider的字符串,ITEM和ITEM_ID分别用于UriMatcher(资源标识符匹配器)中对路径item和item/id的匹配号码。CONTENT_TYPE和CONTENT_ITEM_TYPE定义了数据的MIME类型。需要注意的是,单一数据的MIME类型字符串应该以vnd.android.cursor.item/开头,数据集的MIME类型字符串则应该以vnd.android.cursor.dir/开头。CONTENT_URI定义的是查询当前表数据的content://样式URI。 

接下来,同样是在SQLite2的包中创建一个继承自ContentProvider的类MyProvider.java,来实现对数据操作的各个方法。这里将用到MyHelper来辅助获得SQLiteDatabase对象,虽然也可以直接使用Context.OpenOrCreate()方法获得,但不及使用数据库打开辅助类方便。 

Java代码  收藏代码
  1. public class MyProvider extends ContentProvider {    
  2. MyHelper dbHelper;    
  3. private static final UriMatcher sMatcher;    
  4. static {    
  5. sMatcher = new UriMatcher(UriMatcher.NO_MATCH);    
  6. sMatcher.addURI(CountryCode.AUTHORITY,    
  7. "item",CountryCode.ITEM);    
  8. sMatcher.addURI(CountryCode.AUTHORITY,    
  9. "item/#", CountryCode.ITEM_ID);    
  10. }    
  11. ...    
  12. }   

这里UriMatcher类型的静态字段是用来匹配传入到ContentProvider中的Uri的类。其构造方法传入的匹配码是使用match()方法匹配根路径时返回的值,这个匹配码可以为一个大于零的数表示匹配根路径或传入-1,即常量UriMatcher.NO_MATCH表示不匹配根路径。addURI()方法是用来增加其他URI匹配路径的,第一个参数传入标识ContentProvider的AUTHORITY字符串。第二个参数传入需要匹配的路径,这里的#代表匹配任意数字,另外还可以用*来匹配任意文本。第三个参数必须传入一个大于零的匹配码,用于match()方法对相匹配的URI返回相对应的匹配码。 

ContentProvider里针对数据的各种操作定义了6个抽象方法,下面是对各个方法的实现和讲解。 

Java代码  收藏代码
  1. @Override    
  2. public boolean onCreate() {    
  3. dbHelper = new MyHelper(getContext(), CountryCode.DB_NAME,    
  4. null,CountryCode.VERSION);    
  5. return true;    
  6. }   

每当ContentProvider启动时都会回调onCreate()方法。此方法主要进行一些ContentProvider初始化的工作,返回true表示初始化成功,返回false则初始化失败。在这个ContentProvider中,主要是构造了需要操作数据库的辅助类对象。 

Java代码  收藏代码
  1. @Override    
  2. public String getType(Uri uri) {    
  3. switch (sMatcher.match(uri)) {    
  4. case CountryCode.ITEM:    
  5. return CountryCode.CONTENT_TYPE;    
  6. case CountryCode.ITEM_ID:    
  7. return CountryCode.CONTENT_ITEM_TYPE;    
  8. default:    
  9. throw new IllegalArgumentException("Unknown URI " + uri);    
  10. }    
  11. }   


getTyper()是用来返回数据的MIME类型的方法。使用sMatcher对URI进行匹配,并返回相应的MIME类型字符串,若无法匹配传入的URI,抛出IllegalArgumentException异常。 



Java代码  收藏代码
  1. @Override    
  2. public int delete(Uri uri, String where, String[] args) {    
  3. SQLiteDatabase db = dbHelper.getWritableDatabase();    
  4. int count;    
  5. switch (sMatcher.match(uri)) {    
  6. case CountryCode.ITEM:    
  7. count = db.delete(CountryCode.TB_NAME, where,args);    
  8. break;    
  9. case CountryCode.ITEM_ID:    
  10. String id = uri.getPathSegments().get(1);    
  11. count = db.delete(CountryCode.TB_NAME, CountryCode.ID + "=" + id    
  12. + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), args);    
  13. break;    
  14. default:    
  15. throw new IllegalArgumentException("Unknown URI " + uri);    
  16. }    
  17. getContext().getContentResolver().notifyChange(uri, null);    
  18. return count;    
  19. }    
  20. @Override    
  21. public int update(Uri uri, ContentValues values,    
  22. String where, String[] args) {    
  23. SQLiteDatabase db = dbHelper.getWritableDatabase();    
  24. int count;    
  25. switch (sMatcher.match(uri)) {    
  26. case CountryCode.ITEM:    
  27. count = db.update(CountryCode.TB_NAME,values, where,args);    
  28. break;    
  29. case CountryCode.ITEM_ID:    
  30. String id = uri.getPathSegments().get(1);    
  31. count = db.update(CountryCode.TB_NAME,values,CountryCode.ID+"=" + id    
  32. + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), args);    
  33. break;    
  34. default:    
  35. throw new IllegalArgumentException("Unknown URI " + uri);    
  36. }    
  37. getContext().getContentResolver().notifyChange(uri, null);    
  38. return count;    
  39. }   

delete()和update()方法分别用于数据的删除和修改操作,返回的是所影响数据的数目。我们这里两种方法的实现比较相似。首先利用数据库辅助对象获取一个SQLiteDatabase对象。然后根据传入的Uri用sMatcher进行匹配,对单个数据或数据集进行删除或修改,以便于在调用SQLiteDatabase对象的删除或修改方法时where语句中使用不同的表达式。这里通过调用getContext()方法获得调用update()方法的Context对象,再利用这个Context对象来获取一个ContentResolver的对象。notifyChange()方法则用来通知注册在此URI上的观察者(observer)数据发生了改变。最后返回删除或修改数据的行数。 

Java代码  收藏代码
  1. @Override    
  2. public Uri insert(Uri uri, ContentValues initialValues) {    
  3. SQLiteDatabase db = dbHelper.getWritableDatabase();    
  4. long rowId;    
  5. if (sMatcher.match(uri) != CountryCode.ITEM) {    
  6. throw new IllegalArgumentException("Unknown URI " + uri);    
  7. }    
  8. rowId =    
  9. db.insert(CountryCode.TB_NAME,CountryCode.ID,initialValues);    
  10. if (rowId > 0) {    
  11. Uri noteUri =    
  12. ContentUris.withAppendedId(CountryCode.CONTENT_URI, rowId);    
  13. getContext().getContentResolver().notifyChange(noteUri, null);    
  14. return noteUri;    
  15. }    
  16. throw new SQLException("Failed to insert row into " + uri);    
  17. }   

insert()方法用来插入数据,最后返回新插入数据的URI。在此方法的实现中,只接受数据集的URI,即指向表的URI。然后利用数据库辅助对象获得的SQLiteDatabase对象,调用insert()方法向指定表中插入数据。最后通知观察者数据发生变化,返回插入数据的URI。 

Java代码  收藏代码
  1. @Override    
  2. public Cursor query(Uri uri, String[] projection,    
  3. String selection, String[] args,String order) {    
  4. SQLiteDatabase db = dbHelper.getReadableDatabase();    
  5. Cursor c;    
  6. switch (sMatcher.match(uri)) {    
  7. case CountryCode.ITEM:    
  8. c = db.query(CountryCode.TB_NAME, projection, selection,    
  9. args,null,null,order);    
  10. break;    
  11. case CountryCode.ITEM_ID:    
  12. String id = uri.getPathSegments().get(1);    
  13. c = db.query(CountryCode.TB_NAME, projection, CountryCode.ID + "="    
  14. + id    
  15. + (!TextUtils.isEmpty(selection)    
  16. " AND (" + selection + ')' : ""),    
  17. args,null,null,order);    
  18. break;    
  19. default:    
  20. throw new IllegalArgumentException("Unknown URI " + uri);    
  21. }    
  22. c.setNotificationUri(getContext().getContentResolver(), uri);    
  23. return c;    
  24. }   

query()是对数据进行查询的方法,最终将查询的结果包装入一个Cursor对象并返回。其实现首先还是通过数据库辅助对象获取一个SQLiteDatabase对象,然后使用sMatcher对传入URI进行匹配,并分别对单数据和多数据的URI构造不同的where语句表达式并查询。setNotificationUri()方法是用来为Cursor对象注册一个观察数据变化的URI。 

实现完这几个抽象方法后,一个完整的ContentProvider就被定义好了,剩下的就是在Android-Manifest.xml中对这个ContentProvider的声明了。在AndroidManifest.xml中添加如下声明代码。 

<provider android:name="MyProvider" 
android:authorities="com.studio.andriod.provider.countrycode"/> 
其中android:name需要设置成刚才定义ContentProvider类的类名,android:authorities则指定了在content://样式的URI中标识这个ContentProvider的字符串。另外,可以设置android:readPermission和android:writePermission这两个属性, 来分别指定对这个ContentProvider中数据读和写操作的权限。也可以在onCreate()方法的实现中调用setRead-Permission()和setWritePermission()方法来动态指定权限。 

为了让其他程序更加方便地使用我们自己定义的ContentProvider,一般会将要用到的静态数据导出成JAR归档文件。如本程序中将所有要用到的静态字段都放在了类CountryCode中。下面是将其打包成JAR文件的步骤。 

(1) 在项目文件夹上或CountryCode.java文件上右键,选择Export...。 


(2) 在弹出的选择导出类型的对话框中,点击选择JAR file,然后点Next。 

(3) 在选择要导出的文件框中,只选择CountryCode.java。找到Browse...按钮指定导出路径(默认为workspace),点选Finish完成导出CountryCode.jar。 

导出了JAR文件后,若需要在其他应用程序中使用,只需要为该项目添加外部的归档文件即可。在该项目文件夹上右键找到Build path→Add External Archive,如图9-5所示,然后选择要添加的JAR文件就可以在程序中使用import语句导入归档文件中的内容了。 

转自http://blog.chinaunix.net/u3/90973/showart_2161195.html 
ContentProvider是什么时候创建的,是谁创建的?访问某个应用程序共享的数据,是否需要启动这个应用程序?这个问题在 Android SDK中没有明确说明,但是从数据共享的角度出发,ContentProvider应该是Android在系统启动时就创建了,否则就谈不上数据共享了。 这就要求在AndroidManifest.XML中使用元素明确定义。 
可能会有多个程序同时通过ContentResolver访问一个ContentProvider,会不会导致像数据库那样的“脏数 据”?这个问题一方面需要数据库访问的同步,尤其是数据写入的同步,在AndroidManifest.XML中定义ContentProvider的时 候,需要考虑是元素multiprocess属性的值;另外一方面Android在ContentResolver中提供了notifyChange() 接口,在数据改变时会通知其他ContentObserver,这个地方应该使用了观察者模式,在ContentResolver中应该有一些类似 register,unregister的接口。 
至此,已经对ContentProvider提供了比较全面的分析,至于如何创建ContentProvider,可通过2种方法:创建一个属于你自己的 ContentProvider或者将你的数据添加到一个已经存在的ContentProvider中,当然前提是有相同数据类型并且有写入 Content provider的权限。在Android SDK的sample中提供的 

附件为我自己写的测试例子: 


  • BookGallery.rar (27.6 KB)
  • 下载次数: 403
  • BookProvider.rar (30.7 KB)
  • 下载次数: 447

原创粉丝点击