【Android数据存储】ContentProvider详细介绍(附实例源码)

1.ContentProvider是什么?

    ContentProvider——内容提供者。它是一个类,这个类主要是对Android系统中进行共享的数据进行包装,并提供了一组统一的访问接口供其他程序调用。这些被共享的数据,可以使系统自己的也可以使我们个人应用程序中的数据,ContentProvider使用表的形式来组织数据.

2.为什么要有ContentProvider?

  在Android中,数据的存储有很多种方式,最常用的就是SQLite和XML文件方式。在不同的应用程序间,其实数据是不能直接被相互访问和操作的,在这种情况下,ContentProvider很好的被用来解决了不同应用程序间数据共享的问题。

   其实在Android系统中,已经为我们提供了许多ContentProvider,如:Contacts、Browser、CallLog、 Settings等等。那么,Android系统中提供了这么多的ContentProvider,另外还有我们自己公开的共享数据,我们在写程序的时 候,怎么才能让我们的应用程序知道去哪儿取、如何取这些数据呢?我们自然的会想到URI。

      一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。

 也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去。
 外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,
 重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据

3.URI是什么?

    URI(Uniform Resource Identifier)——统一资源定位符,URI在ContentProvider中代表了要操做的数据。

在Android系统中通常的URI格式为:content://com.wirelessqa.content.provider/profile/10

在万维网访问时通常用的URI格式为:http://www.XXXX.com/AAA/123

  • content://——schema,这个是Android中已经定义好的一个标准。我个人一直认为这和我们的http://有异曲同工之妙,都是代表的协议。ContentProvider(内容提供者)的scheme已经由Android所规定为:content://
  • com.wirelessqa.content.provider——authority(主机名),用于唯一标识这个ContentProvider,外部调用者通过这个authority来找到它。相当于www.XXXX.com, 代表的是我们ContentProvider所在的”域名”,这个”域名”在我们Android中一定要是唯一的,否则系统怎么能知道该找哪一个 Provider呢?所以一般情况下,建议采用完整的包名加类名来标识这个ContentProvider的authority。
  • /profile/10——路径,用来标识我们要操作的数据。/profile/10表示的意思是——找到profile中id为10的记录。其实这个相当于/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项。

4.URI常用方法有哪些?

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方法,来操作数据。

5. ContentProvider中公开的几个方法

    • public boolean onCreate():该方法在ContentProvider创建后就会被调用,Android系统运行后,ContentProvider只有在被第一次使用它时才会被创建。
    • public Uri insert(Uri uri, ContentValues values):外部应用程序通过这个方法向 ContentProvider添加数据。
      • uri—— 标识操作数据的URI
      • values—— 需要添加数据的键值对
    • public int delete(Uri uri, String selection, String[] selectionArgs):外部应用程序通过这个方法从 ContentProvider中删除数据。
      • uri——标识操作数据的URI
      • selection——构成筛选添加的语句,如”id=1″ 或者 “id=?”
      • selectionArgs——对应selection的两种情况可以传入null 或者 new String[]{“1″}
    • public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):外部应用程序通过这个方法对 ContentProvider中的数据进行更新。
      • values——对应需要更新的键值对,键为对应共享数据中的字段,值为对应的修改值
      • 其余参数同delete方法
    • public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):外部应用程序通过这个方法从ContentProvider中获取数据,并返回一个Cursor对象。
      • projection——需要从Contentprovider中选择的字段,如果为空,则返回的Cursor将包含所有的字段。
      • sortOrder——默认的排序规则
      • 其余参数同delete方法    
    • public String getType(Uri uri):该方法用于返回当前Url所代表数据的MIME类型。
      如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,
      例如:要得到所有person记录的Uri为content://
      com.wirelessqa.content.provider/profile,那么返回的MIME类型字符串应该为:”vnd.android.cursor.dir/profile”。
      如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,
      例如:得到id为10的person记录,Uri为content://
      com.wirelessqa.content.provider/profile/10,那么返回的MIME类型字符串为:”vnd.android.cursor.item/profile”。

6.实现步骤

想要自己的程序里面的数据能够被其他程序所访问到,有以下步骤:

第一:首先生成一个继承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对应的几个方法:

  • query(Uri, String[], String, String[], String) which returns data to the caller
  • insert(Uri, ContentValues) which inserts new data into the content provider
  • update(Uri, ContentValues, String, String[]) which updates existing data in the content provider
  • delete(Uri, String, String[]) which deletes data from the content provider

其实和contentprovider里面的方法是一样的..他们所对应的数据,最终是会被传到我们在之前程序里面定义的那个contentprovider类的方法,至于你想要在这几个方法里面做什么事,随你

7.实例讲解

AndroidManifest.xml

在AndroidManifest.xml的<application>和</application>之间加入:

1 <provider android:name="MyProvider" android:authorities="com.wirelessqa.content.provider" />

Profile.java

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 }

 DBHelper.java

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 }

 MyProvider.java

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, nullnull, sortOrder);
103                 break;
104             case Profile.ITEM_ID:
105                 c = db.query(Profile.TABLE_NAME, projection, Profile.COLUMN_ID + "=" + uri.getLastPathSegment(),
106                              selectionArgs, nullnull, 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         

你可能感兴趣的:(ContentProvide)