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绑定起来。
注意这里也是要区分api版本的。最后用本活动的setListAdapter将adapter跟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
➤ 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
➤ 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);
更新操作如下:
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