ContentProvider
ContentProvider是Android四大组件之一,是不同应用程序之间进行数据交换的标准API,ContentProvider以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其它应用程序使用ContentResolver根据Uri访问操作指定数据。
Uri代表了要操作的数据,Uri主要包含了两部分信息:1.需要操作的ContentProvider,2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。
2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
•要操作contact表中id为10的记录,可以构建这样的路径:/contact/10
• 要操作contact表中id为10的记录的name字段, contact/10/name
•要操作contact表中的所有记录,可以构建这样的路径:/contact
要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:
要操作xml文件中contact节点下的name节点,可以构建这样的路径:/contact/name
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri =Uri.parse("content://com.changcheng.provider.contactprovider/contact")
因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris。掌握它们的使用,会便于我们的开发工作。
UriMatcher有两个方法:
Public Methods |
|
||||||||||
void |
addURI(String authority, String path, int code) Add a URI to match, and the code to return when this URI is matched. |
||||||||||
int |
match(Uri uri) Try to match against the path in a url. |
UriMatcher类用于匹配Uri,它的用法如下:
首先第一步把你需要匹配Uri路径全部给注册上,如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.ljq.provider.personprovider/person路径,返回匹配码为1
sMatcher.addURI("com.ljq.provider.personprovider","person", 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配content://com.ljq.provider.personprovider/person/230路径,返回匹配码为2
sMatcher.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部分,它有两个比较实用的方法:
static long |
parseId(Uri contentUri) Converts the last path segment to a long. |
static Uri |
withAppendedId(Uri contentUri, long id) Appends the given ID to the end of the path. |
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
ContentResolver:介绍
当外部应用需要对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的记录。
要创建我们自己的Content Provider的话,我们需要遵循以下几步:
a. 创建一个继承了ContentProvider父类的类
b. 定义一个名为CONTENT_URI,并且是public static final的Uri类型的类变量,你必须为其指定一个唯一的字符串值,最好的方案是以类的全名称,如:
public static final Uri CONTENT_URI = Uri.parse(“content://com.google.android.MyContentProvider”);
c. 定义你要返回给客户端的数据列名。如果你正在使用Android数据库,必须为其定义一个叫_id的列,它用来表示每条记录的唯一性。
d. 创建你的数据存储系统。大多数Content Provider使用Android文件系统或SQLite数据库来保持数据,但是你也可以以任何你想要的方式来存储。
e. 如果你要存储字节型数据,比如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,通过它来读取对应的文件数据。处理这种数据类型的Content Provider需要实现一个名为_data的字段,_data字段列出了该文件在Android文件系统上的精确路径。这个字段不仅是供客户端使用,而且也可以供ContentResolver使用。客户端可以调用ContentResolver.openOutputStream()方法来处理该URI指向的文件资源;如果是ContentResolver本身的话,由于其持有的权限比客户端要高,所以它能直接访问该数据文件。
f. 声明public staticString型的变量,用于指定要从游标处返回的数据列。
g. 查询返回一个Cursor类型的对象。所有执行写操作的方法如insert(), update()以及delete()都将被监听。我们可以通过使用ContentResover().notifyChange()方法来通知监听器关于数据更新的信息。
h. 在AndroidMenifest.xml中使用<provider>标签来设置Content Provider。
i. 如果你要处理的数据类型是一种比较新的类型,你就必须先定义一个新的MIME类型,以供ContentProvider.geType(url)来返回。MIME类型有两种形式:一种是为指定的单个记录的,还有一种是为多条记录的。这里给出一种常用的格式:
vnd.android.cursor.item/vnd.yourcompanyname.contenttype(单个记录的MIME类型)
比如, 一个请求列车信息的URI如content://com.example.transportationprovider/trains/122可能就会返回typevnd.android.cursor.item/vnd.example.rail这样一个MIME类型。
vnd.android.cursor.dir/vnd.yourcompanyname.contenttype(多个记录的MIME类型)
比如, 一个请求所有列车信息的URI如content://com.example.transportationprovider/trains可能就会返回vnd.android.cursor.dir/vnd.example.rail这样一个MIME类型。
1.常量类,声明需要使用的常量(Dicts类)
package com.jph.dictdemo; import android.net.Uri; import android.provider.BaseColumns; /** *Describe:</br> *常量类,声明要使用的常量</br> *内部类Dict实现了BaseColumns接口,该接口也是一个常量接口</br> *该常量接口中已经定义了_ID、WORD、DETAIL常量,分别用来表示记录的id、单词、单词解释</br> *@author jph</br> *Date:2014.07.13 */ public class Dicts { //定义ContentProvider的Authority public static final String AUTHORITY="com.jph.dictdemo.dictprovider"; //定义一个静态内部类,定义该ContentProvider所包含数据列的列明 public static final class Dict implements BaseColumns{ //定义Content所允许操作的三个数据列名 public static final String _ID="_id"; public static final String WORD="word"; //单词 public static final String DETAIL="detail";//单词解释 public static final Uri DICT_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/words"); public static final Uri WORD_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/word"); public static final String CONTENT_TYPE="vnd.android.cursor.dir/com.jph.dictdemo.dict"; public static final String CONTENT_ITEM_TYPE="vnd.android.cursor.item/com.jph.dictdemo.dict"; public static final String DB_NAME="dict.db3";//数据库名 public static final String T_NAME="tb_dict";//表名 } }
package com.jph.dictdemo; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; /** * Describe:</br> * 在创建表的时候除了主键外其它的字段没有指定数据类型,因为SQLiteQLite </br> * 在解析CREATE TABLE 语句时,会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息 </br> * @author jph</br> * Date:2014.07.13 * */ public class DbHelper extends SQLiteOpenHelper { final String CREATE_TABLE_SQL="create table tb_dict("+ Dicts.Dict._ID+" integer primary key autoincrement,"+ Dicts.Dict.WORD+","+Dicts.Dict.DETAIL+")"; public DbHelper(Context context, String name, int version) { super(context, name, null, version); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub //第一次使用数据库是自动创建表 db.execSQL(CREATE_TABLE_SQL); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub System.out.println("--------------onUpgrade Called----------------"+ "oldVersion:"+oldVersion+" newVersion"+newVersion); } }
package com.jph.dictdemo; 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; /** * Describe:</br> * Dict程序的为了向外界提供数据,创建了继承ContentProvider的子类</br> * DictProvider并实现其所有的抽象方法,如:insert、delete、query、</br> * delete、update等方法。</br> * @author jph</br> * Date:2014.07.13 * */ public class DictProvider extends ContentProvider { //定义数据库帮助类对象 private DbHelper dbOpenHelper; private static final int WORDS=1; private static final int WORD=2; private static UriMatcher matcher=new UriMatcher(UriMatcher.NO_MATCH); static { //添加需要匹配的Uri,匹配则返回相应的匹配码 matcher.addURI(Dicts.AUTHORITY, "words", 1); matcher.addURI(Dicts.AUTHORITY, "word/#", 2); } public DictProvider() { // TODO Auto-generated constructor stub } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub SQLiteDatabase db=dbOpenHelper.getWritableDatabase(); //纪录所删除的纪录数 int count=0; switch (matcher.match(uri)) { // 如果Uri参数代表操作全部数据项 case WORDS: count=db.delete(Dicts.Dict.T_NAME, selection, selectionArgs); break; // 如果Uri参数代表指定数据项 case WORD: //解析出所要删除单词的ID long id=ContentUris.parseId(uri); String whereClause=Dicts.Dict._ID+"="+id; //如果原来的where子句存在,拼接where子句 if (selection!="") { whereClause=whereClause+"and"+selection; } count=db.delete(Dicts.Dict.T_NAME, whereClause, selectionArgs); break; default: throw new IllegalArgumentException("未知Uri:"+uri); } //通知数据已经改变 getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub switch (matcher.match(uri)) { //如果操作的是多项纪录 case WORDS: return Dicts.Dict.CONTENT_TYPE; // 如果操作的数据是单项记录 case WORD: return Dicts.Dict.CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("未知Uri:" + uri); } } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub SQLiteDatabase db=dbOpenHelper.getReadableDatabase(); switch (matcher.match(uri)) { // 如果Uri参数代表操作全部数据项 case WORDS: long rowId=db.insert(Dicts.Dict.T_NAME, Dicts.Dict._ID, values); //如果插入成功则返回Uri if (rowId>0) { //在已有的Uri后面追加ID Uri urinew=ContentUris.withAppendedId(uri, rowId); //通知数据已经改变 getContext().getContentResolver().notifyChange(uri, null); return urinew; } break; default: throw new IllegalArgumentException("未知Uri:"+uri); } return null; } //初始化 @Override public boolean onCreate() { // TODO Auto-generated method stub //创建db对象 dbOpenHelper=new DbHelper(this.getContext(),Dicts.Dict.DB_NAME, 1); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stubSQLiteDatabase db=dbOpenHelper.getReadableDatabase(); SQLiteDatabase db=dbOpenHelper.getReadableDatabase(); switch (matcher.match(uri)) { // 如果Uri参数代表操作全部数据项 case WORDS: return db.query(Dicts.Dict.T_NAME, projection, selection, selectionArgs, null, null, null); // 如果Uri参数代表指定数据项 case WORD: //解析出所要查询单词的ID long id=ContentUris.parseId(uri); String whereClause=Dicts.Dict._ID+"="+id; //如果原来的where子句存在,拼接where子句 if (selection!="") { whereClause=whereClause+"and"+selection; } return db.query(Dicts.Dict.T_NAME, projection, whereClause, selectionArgs, null, null, null); default: throw new IllegalArgumentException("未知Uri:"+uri); } } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stubSQLiteDatabase SQLiteDatabase db=dbOpenHelper.getReadableDatabase(); //纪录所更新的纪录数 int count=0; switch (matcher.match(uri)) { // 如果Uri参数代表操作全部数据项 case WORDS: count=db.update(Dicts.Dict.T_NAME, values, selection, selectionArgs); break; // 如果Uri参数代表指定数据项 case WORD: //解析出所要更新单词的ID long id=ContentUris.parseId(uri); String whereClause=Dicts.Dict._ID+"="+id; //如果原来的where子句存在,拼接where子句 if (selection!="") { whereClause=whereClause+"and"+selection; } count=db.update(Dicts.Dict.T_NAME, values, whereClause, selectionArgs); break; default: throw new IllegalArgumentException("未知Uri:"+uri); } //通知数据已经改变 getContext().getContentResolver().notifyChange(uri, null); return count; } }
4.创建一个应用来测试,查询、删减、插入、更新数据(DictResolver)
该应用主要有两个Activity,一个用于操作ContentProvider,一个用于显示操作结果
DictResolver类:
package com.jph.dictresolver; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import android.os.Bundle; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; /** * Describe:</br> * 本实例通过获取系统的ContentResolver对象,对DictDemo实例的</br> * ContentProvider中的数据进行GRUD操作</br> * @author jph * Date:<2014.07.13> * */ public class DictResolver extends Activity { ContentResolver resolver; Button btnInsert,btnSelect; EditText edtWord,edtDetail,edtSelect; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //获取系统的ContentResolver对象 resolver=getContentResolver(); edtDetail=(EditText)findViewById(R.id.edtDetail); edtWord=(EditText)findViewById(R.id.edtWord); edtDetail=(EditText)findViewById(R.id.edtDetail); edtSelect=(EditText)findViewById(R.id.edtSelect); btnInsert=(Button)findViewById(R.id.btnInsert); btnSelect=(Button)findViewById(R.id.btnSelect); btnInsert.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String detail=edtDetail.getText().toString(); String word=edtWord.getText().toString(); //定义ContentValues用于存储单词纪录的名值对 ContentValues values=new ContentValues(); values.put(Dicts.Dict.WORD, word); values.put(Dicts.Dict.DETAIL, detail); resolver.insert(Dicts.Dict.DICT_CONTENT_URI, values); edtWord.setText(""); edtDetail.setText(""); } }); btnSelect.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String key=edtSelect.getText().toString(); //执行查询 Cursor cursor=resolver.query(Dicts.Dict.DICT_CONTENT_URI, null, "word like ? or detail like ?", new String[]{"%"+key+"%","%"+key+"%"}, null); //创建一个Bundle对象 Bundle bundle=new Bundle(); bundle.putSerializable("list", convertCursorToList(cursor)); Intent intent=new Intent(DictResolver.this,ResultShow.class); intent.putExtras(bundle); startActivity(intent); } }); } //将Cursor类型的查询结果转换成序列化的List protected ArrayList<Map<String, String>> convertCursorToList(Cursor cursor) { // TODO Auto-generated method stub ArrayList<Map<String, String>>list=new ArrayList<Map<String,String>>(); //表里cursor结果集 while (cursor.moveToNext()) { //将结果集中的数据放入ArrayList中 Map<String, String> listItem=new HashMap<String, String>(); listItem.put("word",cursor.getString(1)); listItem.put("detail", cursor.getString(2)); list.add(listItem); } return list; } }
ResultShow类:
package com.jph.dictresolver; import java.util.List; import java.util.Map; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; public class ResultShow extends Activity { ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.result); listView=(ListView)findViewById(R.id.list); Intent intent=getIntent(); //获取Intent所携带的数据 Bundle bundle=intent.getExtras(); //从Bundle包中取出数据 @SuppressWarnings("unchecked") List<Map<String, String>>list=(List<Map<String, String>>) bundle.getSerializable("list"); //将List封装成Adapter SimpleAdapter adapter=new SimpleAdapter(this, list, R.layout.line, new String[]{"word","detail"}, new int[]{R.id.txtWord,R.id.txtDetail}); //显示数据 listView.setAdapter(adapter); } }