Android之ContenProvider分析

Android之ContentProvider分析

ContentProvider作为android的四大组件与其他三大组件(Activity,BroadcastReciver,Service)较其他三大组件确实有一些地方不一样。这里我们先可不必详说!我们完全可以将contentprovider理解为一个数据库,作用就是完成应用程序之间的数据的共享(比如联系人,短信……),这里就需要解决三个问题:

1、  怎么将自己需要暴露出来的数据暴露出来

2、  怎么操作(增,查,改,删)自己暴露出来的数据

3、  怎么监听暴露出来的数据的变化

本文的文章结构:

1、  简单的解析contentprovider的概念

2、  介绍contentprovider如何共享数据

3、  介绍contentprovider如何操作共享数据

4、  监听contentprovider共享出来的数据的变化

5、  实现一个ContentProvider共享数据的实例

一、简单的解析contentprovider的概念

在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences、网络存储、文件存储、外储存储、SQLite。但是我个人理解的数据存储就三种:SharedPreferences,文件存储,SQLite。这些存储基本上都是在单独的一个应用程序之中达到的数据共享。为了实现应用程序之间的数据共享,比如操作系统中的联系人,照片库等,这时我们就可能通过ContentProvider来满足我们的需求了,而Contentprovider对外共享数据的一个很大的好处是它提供了统一了数据访问方式。其次一旦某一个应用通过Contentprovider暴露了自己的数据操作的接口,那么不管该应用和程序是否启动,其他应用都可以通过该接口来操作该应用程序的内部数据(增删改查)。

ContentProvider组件实现其功能主要是依靠三个API类:

1,  ContentResolver (用于操作ContentProvider提供的数据)

2,  ContentProvider (是所有ContentProvider组件的基类)

3,  ContentObserver (用于监听ContentProvider的共享数据的改变)

二、介绍contentprovider如何共享数据

1、使用ContentProvider共享数据

1)  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类型。

2)  如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,
例如:要得到所有person记录的Uri为content://com.bing.provider.personprovider/person,那么返回的MIME类型字符串应该为:"vnd.android.cursor.dir/person"。

3)  如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,
例如:得到id为10的person记录,Uri为 content://com.bing.provider.personprovider/person/10,那么返回的MIME类型字符串为:"vnd.android.cursor.item/person"。

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
 
/**
 * Description:
 * <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a>
 * <br/>Copyright (C), 2001-2014, Yeeku.H.Lee
 * <br/>This program is protected bycopyright laws.
 * <br/>Program Name:
 * <br/>Date:
 * @author  Yeeku.H.Lee kongyeeku@163.com
 * @version  1.0
 */
public classDictProvider extends ContentProvider
{
    private static UriMatcher matcher
       =newUriMatcher(UriMatcher.NO_MATCH);
    private static final int WORDS = 1;
    private static final int WORD = 2;
    private MyDatabaseHelper dbOpenHelper;
    static
    {
       // 为UriMatcher注册两个Uri
       matcher.addURI(Words.AUTHORITY, "words",WORDS);
       matcher.addURI(Words.AUTHORITY, "word/#",WORD);
    }
 
    // 第一次调用该DictProvider时,系统先创建DictProvider对象,并回调该方法
    @Override
    public boolean onCreate()
    {
       dbOpenHelper = new MyDatabaseHelper(this.getContext(),
           "myDict.db3",1);
       return true;
    }
   
    // 返回指定Uri参数对应的数据的MIME类型
    @Override
    public String getType(Uri uri)
    {
       switch (matcher.match(uri))
       {
           // 如果操作的数据是多项记录
           case WORDS:
              return "vnd.android.cursor.dir/org.crazyit.dict";
           // 如果操作的数据是单项记录
           case WORD:
              return "vnd.android.cursor.item/org.crazyit.dict";
           default:
              throw newIllegalArgumentException("未知Uri:" + uri);
       }
    }
 
    // 查询数据的方法
    @Override
    public Cursor query(Uri uri,String[] projection, String where,
       String[]whereArgs, String sortOrder)
    {
       SQLiteDatabasedb = dbOpenHelper.getReadableDatabase();
       switch (matcher.match(uri))
       {
           // 如果Uri参数代表操作全部数据项
           case WORDS:
              // 执行查询
              return db.query("dict", projection,where,
                  whereArgs,null,null,sortOrder);
           // 如果Uri参数代表操作指定数据项
           case WORD:
              // 解析出想查询的记录ID
              long id = ContentUris.parseId(uri);
              StringwhereClause = Words.Word._ID + "="+ id;
              // 如果原来的where子句存在,拼接where子句
              if (where != null && !"".equals(where))
              {
                  whereClause= whereClause + " and " + where;
              }
              return db.query("dict", projection,whereClause, whereArgs,
                  null, null, sortOrder);
           default:
              throw newIllegalArgumentException("未知Uri:" + uri);
       }
    }
   
    // 插入数据方法
    @Override
    public Uri insert(Uri uri,ContentValues values)
    {
       // 获得数据库实例
       SQLiteDatabasedb = dbOpenHelper.getReadableDatabase();
       switch (matcher.match(uri))
       {
           // 如果Uri参数代表操作全部数据项
           case WORDS:
              // 插入数据,返回插入记录的ID
              long rowId = db.insert("dict", Words.Word._ID, values);
              // 如果插入成功返回uri
              if (rowId > 0)
              {
                  // 在已有的 Uri的后面追加ID
                  UriwordUri = ContentUris.withAppendedId(uri, rowId);
                  // 通知数据已经改变
                  getContext().getContentResolver().notifyChange(wordUri,null);
                  return wordUri;
              }
              break;
           default :
              throw newIllegalArgumentException("未知Uri:" + uri);
       }
       return null;
    }
   
    // 修改数据的方法
    @Override
    public int update(Uri uri,ContentValues values, String where,
       String[]whereArgs)
    {
       SQLiteDatabasedb = dbOpenHelper.getWritableDatabase();
       // 记录所修改的记录数
       int num = 0;
       switch (matcher.match(uri))
       {
           // 如果Uri参数代表操作全部数据项
           case WORDS:
              num= db.update("dict",values, where, whereArgs);
              break;
           // 如果Uri参数代表操作指定数据项
           case WORD:
              // 解析出想修改的记录ID
              long id = ContentUris.parseId(uri);
              StringwhereClause = Words.Word._ID + "="+ id;
              // 如果原来的where子句存在,拼接where子句
              if (where != null &&!where.equals(""))
              {
                  whereClause= whereClause + " and " + where;
              }
              num= db.update("dict",values, whereClause, whereArgs);
              break;
           default:
              throw newIllegalArgumentException("未知Uri:" + uri);
       }
       // 通知数据已经改变
       getContext().getContentResolver().notifyChange(uri,null);
       return num;
    }
   
    // 删除数据的方法
    @Override
    public int delete(Uri uri, Stringwhere, String[] whereArgs)
    {
       SQLiteDatabasedb = dbOpenHelper.getReadableDatabase();
       // 记录所删除的记录数
       int num = 0;
       // 对于uri进行匹配。
       switch (matcher.match(uri))
       {
           // 如果Uri参数代表操作全部数据项
           case WORDS:
              num= db.delete("dict",where, whereArgs);
              break;
           // 如果Uri参数代表操作指定数据项
           case WORD:
              // 解析出所需要删除的记录ID
              long id = ContentUris.parseId(uri);
              StringwhereClause = Words.Word._ID + "="+ id;
              // 如果原来的where子句存在,拼接where子句
              if (where != null &&!where.equals(""))
              {
                  whereClause= whereClause + " and " + where;
              }
              num= db.delete("dict",whereClause, whereArgs);
              break;
           default:
              throw newIllegalArgumentException("未知Uri:" + uri);
       }
       // 通知数据已经改变
       getContext().getContentResolver().notifyChange(uri,null);
       return num;
    }
}

2、Uri介绍

为系统的每一个共享资源给提供唯一的名字,比方说通话记录。
1)、每一个ContentProvider都拥有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。 
2)、Android所提供的ContentProvider都存放在android.provider包中。将其分为A,B,C,D 4个部分:

A:标准前缀:用来说明一个Content Provider控制这些数据,无法改变的;"content://"
BURI的标识:用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。这个标识在元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称
C:路径(path):通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"
DID或者全部:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部;"content://com.bing.provider.myprovider/tablename/#" #表示数据id。

PS

路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
1、要操作person表中id为10的记录,可以构建这样的路径:/person/10
2、要操作person表中id为10的记录的name字段, person/10/name
3、要操作person表中的所有记录,可以构建这样的路径:/person
4、要操作xxx表中的记录,可以构建这样的路径:/xxx
5、当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name
6、如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:Uri uri =Uri.parse("content://com.bing.provider.personprovider/person")

3、UriMatcher类使用介绍

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

//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher  sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.bing.procvide.personprovider/person路径,返回匹配码为1
sMatcher.addURI("com.bing.procvide.personprovider","person", 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配content://com.bing.provider.personprovider/person/230路径,返回匹配码为2
sMatcher.addURI("com.bing.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 

4、ContentUris类使用介绍

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

Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")
Uri resultUri =ContentUris.withAppendedId(uri, 10);
//生成后的Uri为:content://com.bing.provider.personprovider/person/10
parseId(uri)方法用于从路径中获取ID部分:

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

三、介绍contentResolver如何操作数据

ContentResolver操作ContentProvider中的数据

1)当外部应用需要对指定的ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。

2)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和对其中的什么数据进行操作,
其实和contentprovider里面的方法是一样的.他们所对应的数据,最终是会被传到我们在程序里面定义的那个contentprovider类的方法,
假设给定的是:Uri.parse("content://com.bing.providers.personprovider/person/10"),那么将会对主机名为com.bing.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。
使用ContentResolver对ContentProvider中的数据进行添加、删除、修改和查询操作:

ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.bing.provider.personprovider/person");
//添加一条记录
ContentValues values = new ContentValues();
values.put("name","bingxin");
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字段值更改新为zhangsan
ContentValues updateValues = newContentValues();
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中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下:

public class PersonContentProvider extendsContentProvider {
   public Uri insert(Uriuri, ContentValues values) {
      db.insert("person","personid", values);
      getContext().getContentResolver().notifyChange(uri,null);
   }
}
如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:

public class MonitorSms extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
       // 为content://sms的数据改变注册监听器
        getContentResolver().registerContentObserver(
           Uri.parse("content://sms"), true,
           new SmsObserver(new Handler()));
    }
 
    // 提供自定义的ContentObserver监听器类
    private final class SmsObserver extends ContentObserver
    {
       public SmsObserver(Handler handler)
       {
           super(handler);
       }
 
       public void onChange(boolean selfChange)
       {
           // 查询发送箱中的短信(处于正在发送状态的短信放在发送箱)
           Cursor cursor = getContentResolver().query(
              Uri.parse("content://sms/outbox")
              , null, null, null, null);
           // 遍历查询得到的结果集,即可获取用户正在发送的短信
           while (cursor.moveToNext())
           {
              StringBuilder sb = new StringBuilder();
              // 获取短信的发送地址
              sb.append("address=").append(cursor
                  .getString(cursor.getColumnIndex("address")));
              // 获取短信的标题
               sb.append(";subject=").append(cursor
                  .getString(cursor.getColumnIndex("subject")));
              // 获取短信的内容
              sb.append(";body=").append(cursor
                  .getString(cursor.getColumnIndex("body")));
              // 获取短信的发送时间
              sb.append(";time=").append(cursor
                  .getLong(cursor.getColumnIndex("date")));
              System.out.println("Has Sent SMS::" + sb.toString());
           }
       }
    }
}

五、实现一个ContentProvider共享数据的实例

1、  定义一个CONTENT_URI常量,提供了访问ContentProvider的标识符。 

public static final Uri CONTENT_URI
 =Uri.parse("content://com.example.codelab.transportationprovider");
其中:content是协议

Com.exmaple.codelab.transportationprovider是类名,包含完整的包名。

Uri.parse将一个字符串转换成Uri类型。
如果Provider包含子表,同样定义包含字表的CONTENT_URI。
content://com.example.codelab.transportationprovider/train
content://com.example.codelab.transportationprovider/air/domestic
content://com.example.codelab.transportationprovider/air/international
然后定义列,确保里面包含一个_id的列。
2、定义一个类,继承ContentProvider。

public class FirstContentProvider extendsContentProvider 

先介绍一下ContentProvider用到的UriMatcher。UriMatcher的一个重要的函数是match(Uri uri)。这个函数可以匹配Uri,根据传入的不同Uri返回不同的自定义整形值,以表明Uri访问的不同资源的类型。

例如:

public static final UriMatcher uriMatcher;
      static{
                     uriMatcher= new UriMatcher(UriMatcher.NO_MATCH);
                     uriMatcher.addURI(Book.AUTHORITY,"item", Book.ITEM);
                     uriMatcher.addURI(Book.AUTHORITY,"item/#", Book.ITEM_ID);
              }
这里UriMatcher类型的静态字段是用来匹配传入到ContentProvider中的Uri的类。其构造方法传入的匹配码是使用match()方法匹配根路径时返回的值,这个匹配码可以为一个大于零的数表示匹配根路径或传入-1,即常量UriMatcher.NO_MATCH表示不匹配根路径。

addURI()方法是用来增加其他URI匹配路径的,

第一个参数传入标识ContentProvider的AUTHORITY字符串。

第二个参数传入需要匹配的路径,这里的#号为通配符,代表匹配任意数字,另外还可以用*来匹配任意文本。

第三个参数必须传入一个大于零的匹配码,用于match()方法对相匹配的URI返回相对应的匹配码。例如:sMatcher.addURI(“com.test.provider.personprovider”, “person”, 1);如果match()方法匹配content://com.test.provider.personprovider/person路径,返回匹配码为1。

2、  实现query,insert,update,delete,getType和onCreate方法。 
4、在AndroidManifest.xml当中进行声明。

<!-- android:name是完成ContentProvider类的全称
             android:authorities是和FirstProvidermetaData中的常量AUTHORITY的值一样,否则会报错
         -->
        <providerandroid:name="com.bj.FirstContentProvider"android:authorities="com.bj.firstcontentprovider"></provider>
1、常量类
/**
 * 提供ContentProvider对外的各种常量,当外部数据需要访问的时候,就可以参考这些常量操作数据。
 * @author HB
 *
 */
public class ContentData {
    public static finalString AUTHORITY = "hb.android.contentProvider";
    public static finalString DATABASE_NAME = "teacher.db";
    //创建 数据库的时候,都必须加上版本信息;并且必须大于4
    public static finalint DATABASE_VERSION = 4;
    public static finalString USERS_TABLE_NAME = "teacher";
     
    public static finalclass UserTableData implements BaseColumns {
        publicstatic final String TABLE_NAME = "teacher";
        //Uri,外部程序需要访问就是通过这个Uri访问的,这个Uri必须的唯一的。
        publicstatic final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY + "/teacher");
        //数据集的MIME类型字符串则应该以vnd.android.cursor.dir/开头 
        publicstatic final String CONTENT_TYPE ="vnd.android.cursor.dir/hb.android.teachers";
        //单一数据的MIME类型字符串应该以vnd.android.cursor.item/开头 
        publicstatic final String CONTENT_TYPE_ITME ="vnd.android.cursor.item/hb.android.teacher";
        /*自定义匹配码 */ 
        publicstatic final int TEACHERS = 1; 
        /*自定义匹配码 */ 
        publicstatic final int TEACHER = 2; 
         
        publicstatic final String TITLE = "title";
        publicstatic final String NAME = "name";
        publicstatic final String DATE_ADDED = "date_added";
        publicstatic final String SEX = "SEX";
        publicstatic final String DEFAULT_SORT_ORDER = "_id desc";
         
        publicstatic final UriMatcher uriMatcher; 
        static{ 
            //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码 
            uriMatcher= new UriMatcher(UriMatcher.NO_MATCH); 
            //如果match()方法匹配content://hb.android.teacherProvider/teachern路径,返回匹配码为TEACHERS 
            uriMatcher.addURI(ContentData.AUTHORITY,"teacher", TEACHERS); 
            //如果match()方法匹配content://hb.android.teacherProvider/teacher/230,路径,返回匹配码为TEACHER 
            uriMatcher.addURI(ContentData.AUTHORITY,"teacher/#", TEACHER); 
        }
    }
}
在创建UriMatcher对象uriMatcher时,我们传给构造函数的参数为UriMatcher.NO_MATCH,它表示当uriMatcher不能匹配指定的URI时,就返回代码UriMatcher.NO_MATCH。接下来增加了三个匹配规则,分别是uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI(ContentData.AUTHORITY, "teacher", TEACHERS);uriMatcher.addURI(ContentData.AUTHORITY, "teacher/#", TEACHER);

它们的匹配码分别是teacher.ITEM、teacher.ITEM_ID和teacher.ITEM_POS,其中,符号#表示匹配任何数字。

2、SQLite操作类DBOpenHelper

/**
 * 这个类继承SQLiteOpenHelper抽象类,用于创建数据库和表。创建数据库是调用它的父类构造方法创建。
 * @author HB
 */
public class DBOpenHelper extendsSQLiteOpenHelper {
 
    // 在SQLiteOepnHelper的子类当中,必须有该构造函数,用来创建一个数据库;
    publicDBOpenHelper(Context context, String name, CursorFactory factory,
            intversion) {
        //必须通过super调用父类当中的构造函数
        super(context,name, factory, version);
        //TODO Auto-generated constructor stub
    }
 
    // publicDBOpenHelper(Context context, String name) {
    // this(context,name, VERSION);
    // }
 
    publicDBOpenHelper(Context context, String name, int version) {
        this(context,name, null, version);
    }
     
    /**
     * 只有当数据库执行创建 的时候,才会执行这个方法。如果更改表名,也不会创建,只有当创建数据库的时候,才会创建改表名之后的数据表
     */
    @Override
    public void onCreate(SQLiteDatabasedb) {
System.out.println("createtable");
        db.execSQL("createtable " + ContentData.UserTableData.TABLE_NAME
                +"(" + ContentData.UserTableData._ID
                +" INTEGER PRIMARY KEY autoincrement,"
                +ContentData.UserTableData.NAME + " varchar(20),"
                +ContentData.UserTableData.TITLE + " varchar(20),"
                +ContentData.UserTableData.DATE_ADDED + " long,"
                +ContentData.UserTableData.SEX + " boolean)" + ";");
    }
 
    @Override
    public voidonUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 
    }
 
}
3、内容提供者代码

/**
 * 这个类给外部程序提供访问内部数据的一个接口
 * @author HB
 *
 */
public class TeacherContentProvider extendsContentProvider {
     
    privateDBOpenHelper dbOpenHelper = null; 
    // UriMatcher类用来匹配Uri,使用match()方法匹配路径时返回匹配码 
       
       
    /**
     * 是一个回调函数,在ContentProvider创建的时候,就会运行,第二个参数为指定数据库名称,如果不指定,就会找不到数据库;
     * 如果数据库存在的情况下是不会再创建一个数据库的。(当然首次调用在这里也不会生成数据库必须调用SQLiteDatabase的 getWritableDatabase,getReadableDatabase两个方法中的一个才会创建数据库)
     */
    @Override 
    public booleanonCreate() {
        //这里会调用DBOpenHelper的构造函数创建一个数据库;
        dbOpenHelper= new DBOpenHelper(this.getContext(), ContentData.DATABASE_NAME,ContentData.DATABASE_VERSION);
        returntrue; 
    } 
    /**
     * 当执行这个方法的时候,如果没有数据库,他会创建,同时也会创建表,但是如果没有表,下面在执行insert的时候就会出错
     * 这里的插入数据也完全可以用sql语句书写,然后调用db.execSQL(sql)执行。
     */
    @Override 
    public Uriinsert(Uri uri, ContentValues values){ 
        //获得一个可写的数据库引用,如果数据库不存在,则根据onCreate的方法里创建;
        SQLiteDatabasedb = dbOpenHelper.getWritableDatabase(); 
        longid = 0; 
         
        switch(uriMatcher.match(uri)) { 
        caseTEACHERS: 
            id= db.insert("teacher", null, values);    // 返回的是记录的行号,主键为int,实际上就是主键值 
            returnContentUris.withAppendedId(uri, id); 
        caseTEACHER: 
            id= db.insert("teacher", null, values);
            Stringpath = uri.toString(); 
            returnUri.parse(path.substring(0, path.lastIndexOf("/"))+id); // 替换掉id 
        default: 
            thrownew IllegalArgumentException("Unknown URI " + uri); 
        }
    } 
       
    @Override 
    public intdelete(Uri uri, String selection, String[] selectionArgs) { 
        SQLiteDatabasedb = dbOpenHelper.getWritableDatabase(); 
        intcount = 0; 
        switch(uriMatcher.match(uri)) { 
        caseTEACHERS: 
            count= db.delete("teacher", selection, selectionArgs); 
            break; 
        caseTEACHER: 
            //下面的方法用于从URI中解析出id,对这样的路径content://hb.android.teacherProvider/teacher/10 
            //进行解析,返回值为10 
            longpersonid = ContentUris.parseId(uri); 
            Stringwhere = "_ID=" + personid;   // 删除指定id的记录 
            where+= !TextUtils.isEmpty(selection) ? " and (" + selection +")" : "";   // 把其它条件附加上 
            count= db.delete("teacher", where, selectionArgs); 
            break; 
        default: 
            thrownew IllegalArgumentException("Unknown URI " + uri); 
        } 
        db.close(); 
        returncount; 
    } 
   
    @Override 
    public intupdate(Uri uri, ContentValues values, String selection, 
            String[]selectionArgs) { 
        SQLiteDatabasedb = dbOpenHelper.getWritableDatabase(); 
        intcount = 0; 
        switch(uriMatcher.match(uri)) { 
        caseTEACHERS: 
            count= db.update("teacher", values, selection, selectionArgs); 
            break; 
        caseTEACHER: 
            //下面的方法用于从URI中解析出id,对这样的路径content://com.ljq.provider.personprovider/person/10 
            //进行解析,返回值为10 
            longpersonid = ContentUris.parseId(uri); 
            Stringwhere = "_ID=" + personid;// 获取指定id的记录 
            where+= !TextUtils.isEmpty(selection) ? " and (" + selection +")" : "";// 把其它条件附加上 
            count= db.update("teacher", values, where, selectionArgs); 
            break; 
        default: 
            thrownew IllegalArgumentException("Unknown URI " + uri); 
        } 
        db.close(); 
        returncount; 
    } 
       
    @Override 
    public StringgetType(Uri uri) { 
        switch(uriMatcher.match(uri)) { 
        caseTEACHERS: 
            returnCONTENT_TYPE; 
        caseTEACHER: 
            returnCONTENT_TYPE_ITME; 
        default: 
            thrownew IllegalArgumentException("Unknown URI " + uri); 
        } 
    } 
   
    @Override 
    public Cursorquery(Uri uri, String[] projection, String selection, 
            String[]selectionArgs, String sortOrder) { 
        SQLiteDatabasedb = dbOpenHelper.getReadableDatabase(); 
        switch(uriMatcher.match(uri)) { 
        caseTEACHERS: 
            returndb.query("teacher", projection, selection, selectionArgs, null, null,sortOrder); 
        caseTEACHER: 
            //进行解析,返回值为10 
            longpersonid = ContentUris.parseId(uri); 
            Stringwhere = "_ID=" + personid;// 获取指定id的记录 
            where+= !TextUtils.isEmpty(selection) ? " and (" + selection +")" : "";// 把其它条件附加上 
            returndb.query("teacher", projection, where, selectionArgs, null, null,sortOrder); 
        default: 
            thrownew IllegalArgumentException("Unknown URI " + uri); 
        } 
    } 
}
1、这里我们在ArticlesProvider类的内部中定义了一个DBHelper类,它继承于SQLiteOpenHelper类,它用是用辅助我们操作数据库的。使用这个DBHelper类来辅助操作数据库的好处是只有当我们第一次对数据库时行操作时,系统才会执行打开数据库文件的操作。拿我们这个例子来说,只有第三方应用程序第一次调用query、insert、update或者delete函数来操作数据库时,我们才会真正去打开相应的数据库文件。这样在onCreate函数里,就不用执行打开数据库的操作,因为这是一个耗时的操作,而在onCreate函数中,要避免执行这些耗时的操作。

2、我们在实现自己的ContentProvider时,必须继承于ContentProvider类,并且实现以下六个函数:

-- onCreate(),用来执行一些初始化的工作。

-- query(Uri, String[], String, String[],String),用来返回数据给调用者。

-- insert(Uri, ContentValues),用来插入新的数据。

-- update(Uri, ContentValues, String,String[]),用来更新已有的数据。

-- delete(Uri, String, String[]),用来删除数据。

-- getType(Uri),用来返回数据的MIME类型。

4、manifest

<!--?xml version="1.0"encoding="utf-8"?-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="hb.android.contentProvider"android:versioncode="1" android:versionname="1.0">
    <uses-sdkandroid:minsdkversion="8">
     
         
            <intent-filter>
                 
                <categoryandroid:name="android.intent.category.LAUNCHER">
            </category></action></intent-filter>
        </activity>
<providerandroid:name=".TeacherContentProvider"android:authorities="hb.android.contentProvider"android:multiprocess="false">
    </provider></application>
</uses-sdk></manifest>
在配置Content Provider的时候,最重要的就是要指定它的authorities属性了,只有配置了这个属性,第三方应用程序才能通过它来找到这个Content Provider。这要需要注意的,这里配置的authorities属性的值是和我们前面在Articles.java文件中定义的AUTHORITY常量的值是一致的。另外一个属性multiprocess是一个布尔值,它表示这个Content Provider是否可以在每个客户进程中创建一个实例,这样做的目的是为了减少进程间通信的开销。这里我们为了减少不必要的内存开销,把属性multiprocess的值设置为false,使得系统只能有一个Content Provider实例存在,它运行在自己的进程中。在这个配置文件里面,我们还可以设置这个Content Provider的访问权限,这里我们为了简单起见,就不设置权限了。

6、布局文件

<!--?xml version="1.0"encoding="utf-8"?-->
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="fill_parent"android:layout_height="fill_parent">
<buttonandroid:id="@+id/insert" android:text="@string/insert"android:layout_width="fill_parent"android:layout_height="wrap_content">
</button><buttonandroid:id="@+id/query" android:text="@string/query" android:layout_width="fill_parent"android:layout_height="wrap_content">
</button><buttonandroid:id="@+id/querys" android:text="@string/querys"android:layout_width="fill_parent"android:layout_height="wrap_content">
</button><buttonandroid:id="@+id/update" android:text="@string/update"android:layout_width="fill_parent"android:layout_height="wrap_content">
</button><buttonandroid:id="@+id/delete" android:text="@string/delete"android:layout_width="fill_parent"android:layout_height="wrap_content">
</button></linearlayout>

7、activity

package hb.android.contentProvider;
import java.util.Date;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/**
 * 这个类用来测试ContentProvider是否可用。通过 给定的uri访问,数据库;
 *
 * @author HB
 *
 */
public class TeacherActivity extendsActivity {
    Button insert;
    Button query;
    Button update;
    Button delete;
    Button querys;
    Uri uri =Uri.parse("content://hb.android.contentProvider/teacher");
 
    /** Called when theactivity is first created. */
    @Override
    public voidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        insert= (Button) findViewById(R.id.insert);
        query= (Button) findViewById(R.id.query);
        update= (Button) findViewById(R.id.update);
        delete= (Button) findViewById(R.id.delete);
        querys= (Button) findViewById(R.id.querys);
        //绑定监听器的两种方法一;
        insert.setOnClickListener(newInsertListener());
        query.setOnClickListener(newQueryListener());
        //方法二
        update.setOnClickListener(newOnClickListener() {
            publicvoid onClick(View v) {
                //TODO Auto-generated method stub
                ContentResolvercr = getContentResolver();
                ContentValuescv = new ContentValues();
                cv.put("name","huangbiao");
                cv.put("date_added",(new Date()).toString());
                inturi2 = cr.update(uri, cv, "_ID=?", new String[]{"3"});
System.out.println("updated"+":"+uri2);
            }
        });
 
        delete.setOnClickListener(newOnClickListener() {
             
            publicvoid onClick(View v) {
                ContentResolvercr = getContentResolver();
                cr.delete(uri,"_ID=?", new String[]{"2"});
            }
        });
 
        querys.setOnClickListener(newOnClickListener() {
 
            publicvoid onClick(View v) {
                //TODO Auto-generated method stub
                ContentResolvercr = getContentResolver();
                //查找id为1的数据
                Cursorc = cr.query(uri, null, null,null, null);
                System.out.println(c.getCount());
                c.close();
            }
        });
    }
 
    classInsertListener implements OnClickListener {
 
        publicvoid onClick(View v) {
            //TODO Auto-generated method stub
            ContentResolvercr = getContentResolver();
 
            ContentValuescv = new ContentValues();
            cv.put("title","jiaoshou");
            cv.put("name","jiaoshi");
            cv.put("sex",true);
            Uriuri2 = cr.insert(uri, cv);
            System.out.println(uri2.toString());
        }
 
    }
 
    class QueryListenerimplements OnClickListener {
 
        publicvoid onClick(View v) {
            //TODO Auto-generated method stub
            ContentResolvercr = getContentResolver();
            //查找id为1的数据
            Cursorc = cr.query(uri, null, "_ID=?", new String[] { "1" },null);
            //这里必须要调用c.moveToFirst将游标移动到第一条数据,不然会出现index -1 requested , with a size of 1错误;cr.query返回的是一个结果集。
            if(c.moveToFirst() == false) {
                //为空的Cursor
                return;
            }
            intname = c.getColumnIndex("name");
            System.out.println(c.getString(name));
            c.close();
        }
    }
}

组件Content Provider中的数据更新通知机制和Android系统中的广播(Broadcast)通知机制的实现思路是相似的。

在Android的广播机制中,首先是接收者对自己感兴趣的广播进行注册,接着当发送者发出这些广播时,接收者就会得到通知了。

然而,Content Provider中的数据监控机制与Android系统中的广播机制又有三个主要的区别,

一:是前者是通过URI来把通知的发送者和接收者关联在一起的,而后者是通过Intent来关联的,

二:是前者的通知注册中心是由ContentService服务来扮演的,而后者是由ActivityManagerService服务来扮演的,

三:是前者负责接收数据更新通知的类必须要继承ContentObserver类,而后者要继承BroadcastReceiver类。

之所以会有这些区别,是由于Content Proivder组件的数据共享功能本身就是建立在URI的基础之上的,因此专门针对URI来设计另外一套通知机制会更实用和方便,而Android系统的广播机制是一种更加通用的事件通知机制,它的适用范围会更广泛一些。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值