Android -- (13),Content Provider

2012/2/6



一,使用Content Provider

使用content provider可以在安卓应用程序之间共享数据,content provider 如何存储数据不用关系,只需要知道如何往用其访问其他应用程序的数据即可。
content provider跟数据库很相似,可以查询修改其内容。但是content provider如何存储数据是不确定的,content provider作为一层接口提供给用户以查询。

Android里面有很多设置好的content provider: Browser(提供bookmark,history等data),Calllog(提供missed call ,call detail等),Contacts(提供contact detail),MediaStore(存储类似audio,video,images等)Setting(devices setting)。

当然。也可以自己创建content provider。

1.1查询格式

--用contentprovider来查询数据,以URl来查询,格式如下:<standard_prefix>://<authority>/<data_path>/<id> 
其中:
standard_prefix:一般用content:// 即可
authority:指定了content provider的名字。如果用系统自带的content provider ,如contacts,直接填写contacts即可,如果用第三方的content provider ,就要用完整的qualified name 如:com.wrox.provider
data_path:指定了查询的数据类型,例如:如果要从contacts中获得联系人的信息,则authority是contacts,而data_path则是people。查询URL语句则是:content://contacts/people.
id:指定了需要哪条记录,如果要contacts的第二条记录,则应该是:content://contacts/people/2.

以下几种是常用的查询格式:
格式                                                                丨                         描述
content://media/internal/images                                   Returns a list of all the internal images on the device
content://media/external/images                                  Returns a list of all the images stored on the external storage (e.g., SD card) on the device
content://call_log/calls                                                 Returns a list of all calls registered in the Call Log
content://browser/bookmarks                                      Returns a list of bookmarks stored in the browser

1.2使用content provider

--介绍如何使用content provider。

首先,要创建一个URI对象来指定查询语句:
Uri allContacts = Uri.parse(“content://contacts/people”);
然后用这个语句查询:(注意不同的API版本用不同的接口)
Cursor c; 
if(android.os.Build.VERSION.SDK_INT<11) {
//---before Honeycomb---
c = managedQuery(allContacts, null, null, null, null);
} else{
//---Honeycomb and later---
CursorLoader cursorLoader = newCursorLoader(
this, 
allContacts, 
null, 
null,
null, 
null);
c = cursorLoader.loadInBackground(); 
}

查询结果返回一个cursor对象,如果是HoneyComb之前的接口,就用managedQuery方法来查询,如果是HoneyComb之后的接口,就创建一个cursorLoader对象来查询。

当查询好了之后,我们用simpleCursorAdapter来将查询结果和xml文件中的ListView绑定起来。
String[] columns = newString[] {
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts._ID};
int[] views = new int[] {R.id.contactName, R.id.contactID};
SimpleCursorAdapter adapter;
if(android.os.Build.VERSION.SDK_INT<11) {
//---before Honeycomb---
adapter = newSimpleCursorAdapter(
this, R.layout.main, c, columns, views);
} else{
//---Honeycomb and later---
adapter = newSimpleCursorAdapter(
this, R.layout.main, c, columns, views,
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); 
}
this.setListAdapter(adapter);


注意这里也是要区分api版本的。最后用本活动的setListAdapter将adapter跟listView结合起来。

最后当然还需要向活动里添加许可:
<uses-permission android:name="android.permission.READ_CONTACTS"/>

1.3使用预定义的content provider

除了可以自己定义URI来使用content provider之外,还可以用安卓系统中定义好的content provider。
例如Uri allContacts = Uri.parse(“content://contacts/people”);已经被定义成了:Uri allContacts = ContactsContract.Contacts.CONTENT_URI;

其他的还有:

➤ Browser.BOOKMARKS_URI
➤ Browser.SEARCHES_URI
➤ CallLog.CONTENT_URI
➤ MediaStore.Images.Media.INTERNAL_CONTENT_URI
➤ MediaStore.Images.Media.EXTERNAL_CONTENT_URI
➤ Settings.CONTENT_URI

当你需要查询第一个联系人的时候,可以用:
Uri allContacts = Uri.parse(“content://contacts/people/1”);
也可以用预定义的格式:

importandroid.content.ContentUris;
...
Uri allContacts = ContentUris.withAppendedId(
ContactsContract.Contacts.CONTENT_URI, 1);
除了跟ListView等控件绑定之外,还可以直接用cursor来显示信息:
Uri allContacts = ContactsContract.Contacts.CONTENT_URI;
假设我们已经查询好了的结构在cursor c中:

private void PrintContacts(Cursor c)
{
if(c.moveToFirst()) {
do{
String contactID = c.getString(c.getColumnIndex(
ContactsContract.Contacts._ID));
String contactDisplayName = 
c.getString(c.getColumnIndex(
ContactsContract.Contacts.DISPLAY_NAME));
Log.v(“Content Providers”, contactID + “, “+
contactDisplayName);
} while(c.moveToNext());
}
}
可以将cursor提供给PrintContacts来输出,这个输出会在控制台上面显示出来。在这种情况下(使用android设置好的URI路径),用的是 ContactsContract.Contacts._ID来获得ID项的列号(columnIndex)然后再用getString来获得该个triple的ID的内容。(简单来说就是该行的ID列的内容)。ContactsContract.Contacts.DISPLAY_NAME同理。

联系人中除了id和name之外,phone也很重要,于是如果你想继续查询联系人的电话号码的话:

if(c.moveToFirst()) {
do{
String contactID = c.getString(c.getColumnIndex(
ContactsContract.Contacts._ID));
String contactDisplayName = 
c.getString(c.getColumnIndex(
ContactsContract.Contacts.DISPLAY_NAME));
Log.v(“Content Providers”, contactID + “, “+
contactDisplayName);
//---get phone number---
int hasPhone =
c.getInt(c.getColumnIndex(
ContactsContract.Contacts.HAS_PHONE_NUMBER));
if(hasPhone == 1) {
Cursor phoneCursor = 
getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID+ “ = “+
contactID, null, null);
while(phoneCursor.moveToNext()) {
Log.v(“Content Providers”,
phoneCursor.getString(
phoneCursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER)));
}
phoneCursor.close();
}
} while(c.moveToNext());
}

注意新增加的查询语句,由于phone跟contacts是存储在不同的table里面,于是先在contacts表里面判断是否有phone--> if(hasphone==1),则进入第二重查找,注意getContentResolver()获得一个ContentResolver对象然后对提供的content provider 的URI(ContactsContract.CommonDataKinds.Phone.CONTENT_URI)进行查找,查找的条件为 :ContactsContract.CommonDataKinds.Phone.CONTENT_ID = contactID。查找后即返回一个遍历属于该contact的电话的cursor。

1.4 查询操作

在对content provider的使用中,还可以根据查询的操作来获得对应的结果。

1.4.1 Project操作(投影)

---managedQuery()方法的第二个参数(CursorLoader的第三个参数)控制着有多少个column是返回的,一开始我们设置null表示全部column都返回。我们也可以指定某些column来返回:
String[] projection = new String[]
{ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER};
Cursor c; 
if(android.os.Build.VERSION.SDK_INT<11) {
//---before Honeycomb---
c = managedQuery(allContacts, projection, null, null, null);
} else{
//---Honeycomb and later---
CursorLoader cursorLoader = newCursorLoader(
this, 
allContacts, 
projection, 
null,
null, 
null);
c = cursorLoader.loadInBackground(); 
}
注意不同方法提供projection数组的参数位置不同。以上就指定了只有  _ID, DISPLAY_NAME, 和 HAS_PHONE_NUMBER 这三个column在结果中返回。

1.4.2 Filtering(过滤)

--这个可以过滤掉不想要的结果。managedQuery()的第三第四个参数(CursorLoader类的第四第五个参数),例子如下:
Cursor c;
if(android.os.Build.VERSION.SDK_INT<11) {
//---before Honeycomb---
c = managedQuery(allContacts, projection, 
ContactsContract.Contacts.DISPLAY_NAME+ “ LIKE ‘%Lee’”, null, null);
} else{
//---Honeycomb and later---
CursorLoader cursorLoader = newCursorLoader(
this, 
allContacts, 
projection, 
ContactsContract.Contacts.DISPLAY_NAME+ “ LIKE ‘%Lee’”,
null, 
null);
c = cursorLoader.loadInBackground(); 
}
注意上面managedQuery方法的第三个参数,这个参数指定了只有名字中带有“lee”的人才能被选中。当然也可以自己提供匹配字串,当做第四个参数提供给managedQuery即可(CursorLoader类的第五个参数):
Cursor c; 
if(android.os.Build.VERSION.SDK_INT<11) {
//---before Honeycomb---
c = managedQuery(allContacts, projection, 
ContactsContract.Contacts.DISPLAY_NAME+ “ LIKE ?”,
new String[] {“%Lee”}, null);
} else{
//---Honeycomb and later---
CursorLoader cursorLoader = newCursorLoader(
this, 
allContacts, 
projection, 
ContactsContract.Contacts.DISPLAY_NAME+ “ LIKE ?”,
newString[] {“%Lee”}, 
null);
c = cursorLoader.loadInBackground(); 
}
注意managedQuery的第四个参数new String[] {“%Lee”},这里提供匹配的字串即可。

1.4.3 排序(Sorting)

--最后一个参数(managedQuery方法和CursorLoader类)允许用户指定某属性已升序或降序排列结果:
Cursor c; 
if(android.os.Build.VERSION.SDK_INT<11) {
//---before Honeycomb---
c = managedQuery(allContacts, projection, 
ContactsContract.Contacts.DISPLAY_NAME+ “ LIKE ?”,
newString[] {“%Lee”}, 
ContactsContract.Contacts.DISPLAY_NAME+ “ ASC”);
} else{
//---Honeycomb and later---
CursorLoader cursorLoader = newCursorLoader(
this, 
allContacts, 
projection, 
ContactsContract.Contacts.DISPLAY_NAME+ “ LIKE ?”,
newString[] {“%Lee”}, 
ContactsContract.Contacts.DISPLAY_NAME+ “ ASC”);
c = cursorLoader.loadInBackground(); 
}

这里的参数是ContactsContract.Contacts.DISPLAY_NAME+ “ ASC” 表示以ContactsContract.Contacts.DISPLAY_NAME升序排列查询结果。


二,创建content provider

--除了用系统自带的content provider之外,还可以自己创建。只需要将一个类继承于ContentProvider类并且将抽象方法实现即可。

首先用一个类来继承ContentProvider类,然后要重写一下几个方法:

➤ getType()— Returns the MIME type of the data at the given URI 
➤ onCreate()— Called when the provider is started
➤ query()— Receives a request from a client. The result is returned as a Cursorobject.
➤ insert()— Inserts a new record into the content provider
➤ delete()— Deletes an existing record from the content provider
➤ update()— Updates an existing record from the content provider

在自己定义的ContentProvider内,可以自由选择用什么方式来存储数据,这里提供一个实现:
package com.example.contentprovider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class BooksProvider  extends ContentProvider{
	static final String PROVIDER_NAME="com.example.provider.Books";
	static final Uri CONTENT_URI=Uri.parse("content://"+PROVIDER_NAME+"/books");
	static final String _ID="_id";
	static final String TITLE="title";
	static final String ISBN="isbn";
	static final int BOOKS=1;
	static final int BOOK_ID=2;
	private static final UriMatcher uriMatcher;
	static{
		uriMatcher= new UriMatcher(UriMatcher.NO_MATCH);
		uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
		uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
		}
	SQLiteDatabase booksDB;
	static final String DATABASE_NAME="Books";
	static final String DATABASE_TABLE="titles";
	static final int DATABASE_VERSION=1;
	static final String DATABASE_CREATE="create table "+DATABASE_TABLE+" (_id integer primary key autoincrement, "
			+ "title text not null, isbn text not null);";
	private static class DatabaseHelper extends SQLiteOpenHelper
	{
		DatabaseHelper(Context context)
		{
			super(context,DATABASE_NAME,null,DATABASE_VERSION);
		}
		public void onCreate(SQLiteDatabase db)
		{
			db.execSQL(DATABASE_CREATE);
		}
		public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)
		{
			Log.w("content provider database", "from "+oldVersion+" to "+newVersion+"\n");
			db.execSQL("DROP TABLE IF EXISTS titles");
			onCreate(db);
		}
		
	}
	public int delete(Uri arg0,String arg1,String [] arg2)
	{
		int count=0;
		switch (uriMatcher.match(arg0))
		{
		case BOOKS:
			count=booksDB.delete(DATABASE_TABLE,arg1,arg2);break;
		case BOOK_ID:
			String id=arg0.getPathSegments().get(1);
			count=booksDB.delete(DATABASE_TABLE, _ID+ " = "+ id +
					(!TextUtils.isEmpty(arg1) ? " AND ("+
							arg1 + ')': ""), arg2);break;
		default :throw new IllegalArgumentException("Unknown URI "+ arg0);
		}
		getContext().getContentResolver().notifyChange(arg0, null);
		return count;
	}
	public String  getType(Uri uri)
	{
		switch(uriMatcher.match(uri))
		{
		case BOOKS:return "vnd.android.cursor.dir/vnd.learn2develop.books";
		case BOOK_ID: return "vnd.android.cursor.item/vnd.learn2develop.books";
		default :throw new IllegalArgumentException("Unsupported URI: "+ uri);
		}
	}
	public Uri insert(Uri uri ,ContentValues values)
	{
		long rowID=booksDB.insert(DATABASE_TABLE, "", values);
		if(rowID>0)
		{
			Uri _uri=ContentUris.withAppendedId(CONTENT_URI, rowID);
			getContext().getContentResolver().notifyChange(_uri, null);
			return _uri;
		}
		throw new SQLException("Failed to insert row into "+uri);
		
	}
	public boolean onCreate()
	{
		Context	context=getContext();
		DatabaseHelper dbHelper=new DatabaseHelper(context);
		booksDB=dbHelper.getWritableDatabase();
		return (booksDB==null)? false:true;
	}
	public Cursor query(Uri uri,String [] projection,String selection,String[] selectionArgs,String sortOrder)
	{
		SQLiteQueryBuilder sqlBuilder=new SQLiteQueryBuilder();
		sqlBuilder.setTables(DATABASE_TABLE);
		if(uriMatcher.match(uri)==BOOK_ID)
		{
			sqlBuilder.appendWhere(_ID+"="+uri.getPathSegments().get(1));
		}
		if(sortOrder==null|| sortOrder=="") sortOrder=TITLE;
		Cursor c=sqlBuilder.query(booksDB, projection, selection, selectionArgs, null, null, sortOrder);
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}
	public int update(Uri uri,ContentValues values,String selection,
			String [] selectionArgs)
	{
		int count=0;
		switch(uriMatcher.match(uri))
		{
		case BOOKS:
			count=booksDB.update(DATABASE_TABLE,values,selection,selectionArgs);
			break;
		case BOOK_ID:
			count=booksDB.update(DATABASE_TABLE, values, _ID+ " = "+ uri.getPathSegments().get(1) +
					(!TextUtils.isEmpty(selection) ? " AND ("+
							selection + ')': ""), selectionArgs);
			break;
			default: throw new IllegalArgumentException("Unknown URI "+ uri);
			
		
		}
		getContext().getContentResolver().notifyChange(uri, null);
		return count;
	}

}
在这个例子中,用了SQLite Database方法来实现数据的存储(参见上一篇文章)。然后用了一个UriMatcher来提供ContentProvider的调用方法:
private static finalUriMatcher uriMatcher;
static{
uriMatcher= new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, “books”, BOOKS);
uriMatcher.addURI(PROVIDER_NAME, “books/#”, BOOK_ID);
}
这里说明了要用content://com.example.provider.Books/books ,或者用  content://net.learn2develop.provider.Books/books/编号 来提取ContentProvider里面的data。
然后,我们用getType方法来唯一的描述你的ContentProvider的数据类型,如果是第一种(返回全部书本数据),则返回vnd.android.cursor.dir/vnd.learn2develop.books类型,如果是选中一本,则返回vnd.android.cursor.item/vnd.learn2develop.books类型。
下一步,重写了onCreate方法,当ContentProvider被使用的时候打开一个数据库连接来查询数据:
@Override
public booleanonCreate() {
Context context = getContext();
DatabaseHelper dbHelper = newDatabaseHelper(context);
booksDB= dbHelper.getWritableDatabase();
return(booksDB== null)? false:true;
}
最后重写了query方法来根据用户需求查询书本:

publicCursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
sqlBuilder.setTables(DATABASE_TABLE);
if(uriMatcher.match(uri) == BOOK_ID)
//---if getting a particular book---
sqlBuilder.appendWhere(
_ID+ “ = “+ uri.getPathSegments().get(1));
if(sortOrder==null|| sortOrder==””)
sortOrder = TITLE;
Cursor c = sqlBuilder.query(
booksDB,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
//---register to watch a content URI for changes---c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
注意上面判断是否选取其中一本书,然后用添加where语句来到查询语句中。允许向ContentProvider中插入数据,重写了insert方法:
publicUri insert(Uri uri, ContentValues values) {
//---add a new book---
longrowID = booksDB.insert(
DATABASE_TABLE,
“”,
values);
//---if added successfully---
if(rowID>0)
{
Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(_uri, null);
return _uri;
}
throw newSQLException(“Failed to insert row into “+ uri);
}
当插入成功的时候,函数调用notifyChange来提醒用户插入成功,当删除书本的时候,用delete方法:

public intdelete(Uri arg0, String arg1, String[] arg2) {
// arg0 = uri 
// arg1 = selection
// arg2 = selectionArgs
intcount=0;
switch(uriMatcher.match(arg0)){
case BOOKS:
count = booksDB.delete(
DATABASE_TABLE,
arg1,
arg2);
break;
case BOOK_ID:
String id = arg0.getPathSegments().get(1);
count = booksDB.delete(
DATABASE_TABLE,
_ID+ “ = “+ id +
(!TextUtils.isEmpty(arg1) ? “ AND (“+
arg1 + ‘)’: “”),
arg2);
break;
default: throw newIllegalArgumentException(“Unknown URI “+ arg0);
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}
里面用switch语句来分别是删除一本书还是全部书本。同样,删除成功之后也用notifyChange来通知用户。最后重写了update方法:

public intupdate(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
intcount = 0;
switch(uriMatcher.match(uri)){
case BOOKS:
count = booksDB.update(
DATABASE_TABLE,
values,
selection,
selectionArgs);
break;
case BOOK_ID:
count = booksDB.update(
DATABASE_TABLE,
values,
_ID+ “ = “+ uri.getPathSegments().get(1) +
(!TextUtils.isEmpty(selection) ? “ AND (“+
selection + ‘)’: “”),
selectionArgs);
break;
default: throw newIllegalArgumentException(“Unknown URI “+ uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
当更新数据成功的时候,也是用notifyChange来通知用户。

当我们插入数据的时候,首先要创建一个contentValue对象:

//---add a book---                                                                                                                                  ContentValues values = new ContentValues();
values.put(BooksProvider.TITLE, ((EditText)
findViewById(R.id.txtTitle)).getText().toString());
values.put(BooksProvider.ISBN, ((EditText)
findViewById(R.id.txtISBN)).getText().toString());
Uri uri = getContentResolver().insert(
BooksProvider.CONTENT_URI, values);
因为ContentProvider(Booksprovider)和使用其的类在同一个Package中,就可以用BooksProvider.TITLE和BooksProvider.ISBN来指定插入的行。如果在不同的Package,应该改成如下:

ContentValues values = new ContentValues();
values.put(“title”, ((EditText)
findViewById(R.id.txtTitle)).getText().toString());
values.put(“isbn”, ((EditText)
findViewById(R.id.txtISBN)).getText().toString());
Uri uri = getContentResolver().insert(
Uri.parse(
“content://net.learn2develop.provider.Books/books”),
values);

然后用insert方法来插入。
更新操作如下:

ContentValues editedValues = new ContentValues();
editedValues.put(BooksProvider.TITLE, “Android Tips and Tricks”);
getContentResolver().update(
Uri.parse(
“content://net.learn2develop.provider.Books/books/2”),
editedValues,
null,
null);

删除操作如下(一本书):
getContentResolver().delete(
Uri.parse(“content://net.learn2develop.provider.Books/books/2”),
null, null);

删除全部书:
//---delete all titles---getContentResolver().delete(
Uri.parse(“content://net.learn2develop.provider.Books/books”),
null, null);

2013/2/11







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值