一、Content Provider 简介
我们说Android应用程序的四个核心组件是:Activity、Service、Broadcast Receiver 和 Content Provider。在Android中,应用程序彼此之间相互独立的,它们都运行在自己独立的虚拟机中。Content Provider 提供了程序之间共享数据的方法,一个程序可以使用Content Provider 定义一个URI,提供统一的操作接口,其他程序可以通过此URI访问指定的数据,进行数据的增、删、改、查。
Content Provider作为应用程序之间唯一的共享数据的途径,Content Provider 主要的功能就是存储并检索数据以及向其他应用程序提供访问数据的接口。
Android 系统为一些常见的数据类型(如音乐、视频、图像、手机通信录联系人信息等)内置了一系列的 Content Provider, 这些都位于android.provider包下。持有特定的许可,可以在自己开发的应用程序中访问这些Content Provider。
共享的数据类型包括 audio、 video、 images 、personal contact information 等等 详见android.provider
如果想让自己的数据共享,有两种方法:
1、创造自己的content provider
2、将数据添加到已存在的provider中 (要有适当的权限)
让自己的数据和其他应用程序共享有两种方式:创建自己的Content Provier(即继承自ContentProvider的子类) 或者是将自己的数据添加到已有的Content Provider中去,后者需要保证现有的Content Provider和自己的数据类型相同且具有该 Content Provider的写入权限。对于Content Provider,最重要的就是数据模型(data model) 和 URI。
1.数据模型
Content Provider 将其存储的数据以数据表的形式提供给访问者,在数据表中每一行为一条记录,每一列为具有特定类型和意义的数据。每一条数据记录都包括一个 "_ID" 数值字段,改字段唯一标识一条数据。
2.URI
URI,每一个Content Provider 都对外提供一个能够唯一标识自己数据集(data set)的公开URI, 如果一个Content Provider管理多个数据集,其将会为每个数据集分配一个独立的URI。所有的Content Provider 的URI 都以"content://" 开头,其中"content:"是用来标识数据是由Content Provider管理的 schema。
在几乎所有的Content Provider 的操作中都会用到URI,因此一般来讲,如果是自己开发的Content Provider,最好将URI定义为常量,这样在简化开发的同时也提高了代码的可维护性。
首先来介绍如何访问Content Provider中的数据,访问 Content Provider中的数据主要通过ContentResolver对象,ContentResolver类提供了成员方法可以用来对Content Provider 中的数据进行查询、插入、修改和删除等操作。 以查询为例,查询一个 Content Provider 需要掌握如下的信息。
唯一标识Content Provider 的URI
需要访问的数据字段名称。
该数据字段的数据类型
提示: 如果需要访问特定的某条数据记录,只需该记录的ID 即可。
查询Content Provider的方法有两个:ContentResolver的query() 和 Activity 对象的 managedQuery(),二者接收的参数均相同,返回的都是Cursor 对象,唯一不同的是 使用managedQuery 方法可以让Activity 来管理 Cursor 的生命周期。
被管理的Cursor 会在 Activity进入暂停状态的时候调用自己的 deactivate 方法自行卸载,而在Activity回到运行状态时会调用自己的requery 方法重新查询生成的Cursor对象。如果一个未被管理的Cursor对象想被Activity管理,可以调用Activity的 startManagingCursor方法来实现。
下面讲解一下
如何查询一个content provider ,
如何修改provider里边的数据,
如何创造自己的content provider.
Android系统会实例化所有的content provider,可以通过
ContentResolver cr = getContentResolver();直接获取对象实例
_ID |
NUMBER |
NUMBER_KEY |
LABEL |
NAME |
TYPE |
13 |
(425) 555 6677 |
425 555 6677 |
Kirkland office |
Bully Pulpit |
TYPE_WORK |
44 |
(212) 555-1234 |
212 555 1234 |
NY apartment |
Alan Vain |
TYPE_HOME |
45 |
(212) 555-6657 |
212 555 6657 |
Downtown office |
Alan Vain |
TYPE_MOBILE |
53 |
201.555.4433 |
201 555 4433 |
Love Nest |
Rex Cars |
TYPE_HOME |
一个查询返回一个Cursor 对象
URIs
每一个contents provider都回暴露一个公共的URI用来标识数据集
以“content://”开头
如:
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI
每一个ContentResolver 方法都会以URI为第一个参数来指明使用哪一个数据集
1、查询一个Content Provider
需要知道3样东西:
1)URI
2)想查询的字段名称
3)数据类型
如果想查询特定的记录,还需要该记录的ID
可使用的方法:
ContentResolver.query()
Activity.managedQuery()
两者都返回Cursor对象
参数列表: CONTENT_URI,也可以加上ID
例如:content://.../23
有一些静态方法可以直接加进ID后返回一个URI
如 ContentUris,withAppendedId()和
Uri.withAppendedPath()
例如,如果想查询一个Id为23的联系人,可以这样构建查询
//Use the ContentUtils method to produce the base URI for the contact wiht _ID == 23
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
//Alternativaly ,ues the Uri method to produce the bade URI
//It takes a string rathat than an integer
Uri myPerson2 = Uri.withAppendedPath(People.CONTENT_URI, "23");
//Then query for this specific record
Cursor cur = managedQuery(myPerson,null,null,null,null);
其余参数含义:
1.URI
2.想要返回的字段名称,null就返回所有的字段
3.一个条件选择器或说拦截器,相当于SQL中的where
4.一个排序的条件,相当于SQL中的ORDER BY
下面是一个 查询通讯录列表返回名字和相应号码的例子
//Form an array specifying which columns to return
String [] projection = new String[]{
People._ID,
People._COUNT,
People.NAME,
People.NUMBER
};
//Get the base URI for the People table in the Contacts content provider
Uri contacts = People.CONTENT_URI;
//Make the query
Cursor managedCursor = managedQuery(contacts,
projection,//Which columns to return
null, //Which rows to return (all rows)
null, //Selection arguments(none)
People.NAME+"ASC" //Put the results in ascending order by name
);
该查询返回的是一个Cursor对象,内容如下:
_ID |
_COUNT |
NAME |
NUMBER |
44 |
3 |
Alan Vain |
212 555 1234 |
13 |
3 |
Bully Pulpit |
425 555 6677 |
53 |
3 |
Rex Cars |
201 555 4433 |
这个Cursor只能用作读取数据,如果想增加,修改或者删除的话
必须用ContentResolver 对象
利用Cursor读取数据时必须知道字段的类型,通过
getString()
getInt()
getFloat()
如果直接用getString()读,Cursor将会把全部数据转为String类型返回
另:也可以通过index下标访问字段
下面例子读取名字和电话号码
private void getColumnData(Cursor cur){
//默认情况下游标位置不指向任何记录,所以通过判断移向第一个记录是否为空可以知道该Cursor是否为空
if(cur.moveToFirst()){
String name;
String phoneNumber;
int nameColumn = cur.getColumnIndex(People.NAME);
int phoneColumn = cur.getColumnIndex(People.NUMBER);
String imagePath;
do{
//Get the field values
name = cur.getString(nameColumn);
phoneNumber = cur.getString(phoneColumn);
//do something with the values
}while(cur.moveToNext());
}
如果一个查询要返回一个binary data(二进制文件)如图片或声音文件,那文件必须是可以直接插进表或者表中只保持可以访问到该文件的路径 (content:URI)
一般来说,小型数据(20k到50k)可以直接存入表中,可以通过Cursor.getBlod()获得,返回一个byte数组
如果是保存文件路径如content:URI 就不可以直接访问或打开该路径,(因为有权限访问控制),不过,可以通过ContentResolver,openInputStream()去获得一个InputStream,然后就可以读取数据;
修改数据
增加新记录
增加新数据到原有记录
更新原有记录
删除记录
所有的modification 必须通过使用ContentResolver方法 有一些content Provider 对于写操作需要一些permission
如果没有申请该权限,就会失败
Adding records
1)把要增加的值保存在一个ContentValues中
2)通过ContentResolver.insert() 进行增加 参数为provider的 URI 以及要新增的ContentValue Map值
返回一个URI,可以通过该URI获取一个Cursor的对象进一步做修改
ContentValues values = new ContentValues();
//Add bellsong to contacts and make him a favourite
values.put(People.NAME, "bellsong");
//1=the new contact is added to favourite
//0=the new contact is not added to favourite
values.put(People.STARRED, 1);
Uri uri=getContentResolver().insert(People.CONTENT_URI, values);
Adding new values
Uri phoneUri = null;
Uri emailUri = null;
//Add a phone number for bellsong
//Begin with the URI for the new record just returned by insert();
//it ends with the _ID of the new record,so we don't have to add the ID ourselves
//Then append the designation for the phone table to this URI,
//and use the resulting URI to insert the phone number
phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
values.clear();
values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);
values.put(People.Phones.NUMBER, "1234567");
getContentResolver().insert(phoneUri, values);
//Now add an email address in the same way
emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);
values.clear();
//ContactMethods.KIND id uesd to distinguish different kinds of contact methods,such as email,etc
values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(People.ContactMethods.DATA, "bellsong@foxmail.com");
values.put(People.ContactMethods.TYPE,People.ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
You can place small amounts of binary data into a table by calling the version of ContentValues.put() that takes a byte array. That would work for a small icon-like image or a short audio clip, for example. However, if you have a large amount of binary data to add, such as a photograph or a complete song, put a content: URI for the data in the table and callContentResolver.openOutputStream() with the file's URI. (That causes the content provider to store the data in a file and record the file path in a hidden field of the record.)
In this regard, the MediaStore content provider, the main provider that dispenses image, audio, and video data, employs a special convention: The same URI that is used with query() ormanagedQuery() to get meta-information about the binary data (such as, the caption of a photograph or the date it was taken) is used with openInputStream() to get the data itself. Similarly, the same URI that is used with insert() to put meta-information into a MediaStore record is used with openOutputStream() to place the binary data there. The following code snippet illustrates this convention:
import android.provider.MediaStore.Images.Media;import android.content.ContentValues;import java.io.OutputStream;
// Save the name and description of an image in a ContentValues map. ContentValues values = new ContentValues(3);values.put(Media.DISPLAY_NAME, "road_trip_1");values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");values.put(Media.MIME_TYPE, "image/jpeg");
// Add a new record without the bitmap, but with the values just set.// insert() returns the URI of the new record.Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
// Now get a handle to the file for that record, and save the data into it.// Here, sourceBitmap is a Bitmap object representing the file to save to the database.try { OutputStream outStream = getContentResolver().openOutputStream(uri); sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream); outStream.close();} catch (Exception e) { Log.e(TAG, "exception while writing image", e);}
编写一个ContentProvider ,必须先扩展android.content.ContentProvider 并实现以下方法
query
insert
update
delete
getType
但是,要使这些方法生效,在实现它们之前必须进行大量的设置,以下介绍需要采取的各种步骤:
1)计划数据库、URI、列名称、创建元数据类来定义所有这些元素据的常量
2)扩展抽象类ContentProvider
3)实现方法 query、insert、update、delete、getType
4)在描述文件中注册提供程序
1)计划数据库
这里借助一个包含一些图书的数据库,这个图书数据库仅包含一个books表,该表包括name、isbn、author
BookPrividerMetaData类:
package com.kingsoft.NotePadList;
import android.net.Uri;
import android.provider.BaseColumns;
public class BookPrividerMetaData {
public static final String AUTHORITY = "com.androidbook.privider.BookProvider";
public static final String DATABASE_NAME = "book.db";
public static final int DATABASE_VERSION = 1;
public static final String BOOKS_TABLE_NAME = "books";
private BookPrividerMetaData(){}
//inner class describing BookTable
public static final class BookTableMetaData implements BaseColumns{
private BookTableMetaData(){}
public static final String TABLE_NAME = "books";
//uri and MIME type defintions
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/books");
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.androidbook.book";
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.androidbook.book";
public static final String DEFAULT_SORT_ORDER = "modified DESC";
//Addition Columns start here
//String type
public static final String BOOK_NAME = "name";
//string type
public static final String BOOK_ISBN = "isbn";
public static final String BOOK_AUTHOR = "author";
public static final String CREATE_DATE = "created";
public static final String MODIFIED_DATE = "modified";
}
}
BookProviderMetaData 类首先将其授权为com.androidbook.provider.BookProvider 我们将此字符串在Android描述文件中注册该提供程序。此字符串形成了URI的前面部分
然后将它的一个表(books)定义为内部BookTableMetaData类。BooktTableMetaData类然后定义一个URI来标识一个图书集合。有了上一段的授权,图书集合的URI将类似于:
content://com.androidbook.provider.BookProvider/books
此URI由以下常量标识
BookProviderMetaData.BookTableMetaData.Content_URI
元数据类BookTableMetaData也继承了BaseColumn 类,后者提供了标准的_id字段,该字段表示行ID。有了这些元数据的定义,我们就准备好了实现提供程序了。
2、扩展ContentProvider
package com.kingsoft.NotePadList;
import java.util.HashMap;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import com.kingsoft.NotePadList.BookPrividerMetaData.BookTableMetaData;
public class BookProvider extends ContentProvider {
//Create a Projection Map for Columns
//Projection maps are similiar to "as" construct in an sql
//statement whereby you can rename the columns
private static HashMap<String ,String> sBooksProjectionMap;
static{
sBooksProjectionMap = new HashMap<String ,String>();
sBooksProjectionMap.put(BookTableMetaData._ID, BookTableMetaData._ID);
//name isbn author
sBooksProjectionMap.put(BookTableMetaData.BOOK_NAME, BookTableMetaData.BOOK_NAME);
sBooksProjectionMap.put(BookTableMetaData.BOOK_ISBN, BookTableMetaData.BOOK_ISBN);
sBooksProjectionMap.put(BookTableMetaData.BOOK_AUTHOR, BookTableMetaData.BOOK_AUTHOR);
//created data ,modified date
sBooksProjectionMap.put(BookTableMetaData.CREATE_DATE, BookTableMetaData.CREATE_DATE);
sBooksProjectionMap.put(BookTableMetaData.MODIFIED_DATE, BookTableMetaData.MODIFIED_DATE);
}
//Provide a mechanism to identify all the incoming uri patterns
private static UriMatcher sUriMatcher;
private static final int INCOMING_BOOK_COLLECTION_URI_INDICATOR = 1;
private static final int INCOMING_SINGLE_BOOK_URI_INDICATOR = 2;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(BookPrividerMetaData.AUTHORITY, "books", INCOMING_BOOK_COLLECTION_URI_INDICATOR);
sUriMatcher.addURI(BookPrividerMetaData.AUTHORITY, "books/#", INCOMING_SINGLE_BOOK_URI_INDICATOR);
}
//Deal with OnCreate call back
private DatabaseHelper mOpenHelper;
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
mOpenHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
private static class DatabaseHelper extends SQLiteOpenHelper{
public DatabaseHelper(Context context) {
super(context, BookPrividerMetaData.DATABASE_NAME, null, BookPrividerMetaData.DATABASE_VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL("CREATE TABLE " + BookTableMetaData.TABLE_NAME + " ("
+ BookPrividerMetaData.BookTableMetaData._ID
+ "INTEGER PRIMARY KEY,"
+BookTableMetaData.BOOK_NAME + " TEXT"
+BookTableMetaData.BOOK_ISBN + " TEXT"
+BookTableMetaData.BOOK_AUTHOR+" TEXT"
+BookTableMetaData.CREATE_DATE + " INTEGER"
+BookTableMetaData.MODIFIED_DATE + " INTEGER"
+ ");"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
Log.w("Tag","Upgratin databse from version " + oldVersion + " to" + newVersion + " ,which will desroty all old data");
db.execSQL("DROP TABLE IF EXITS " + BookTableMetaData.TABLE_NAME);
onCreate(db);
}
}
}
3、履行MIME类型契约
必须实现getType()方法,以便返回URI的MIME类型
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch(sUriMatcher.match(uri)){
case INCOMING_BOOK_COLLECTION_URI_INDICATOR:
return BookTableMetaData.CONTENT_TYPE;
case INCOMING_SINGLE_BOOK_URI_INDICATOR:
return BookTableMetaData.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknow URI "+ uri);
}
}
4、实现query()
5、实现insert()
6、实现update()
7、实现delete()
8、使用UriMatcher来解析URI
9、实现投影映射
10、提供注册程序
<provider android:name="BooksProvider"
android:authorities = "com.androidbook.provider.BookProvider"/>
实战一:
使用现成的Contant Provider ,遍历联系人列表中的联系人和号码
package com.kingsoft.contentprovider;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.provider.Contacts.People;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class ContentProviderActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b1 = (Button)findViewById(R.id.Button1);
b1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
ContentResolver contentResolver = getContentResolver();
//获得所有联系人
Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,null, null, null,null);
//循环遍历
if(cursor.moveToFirst()){
int idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID);
int displayNameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
do{
//获得联系人id
String contactsId = cursor.getString(idColumn);
//获得联系人姓名
String disdisPlayName = cursor.getString(displayNameColumn);
Toast.makeText(ContentProviderActivity.this, "联系人姓名:"+disdisPlayName, Toast.LENGTH_LONG).show();
//查看联系人有多少个电话号码,如果没有返回 0
int phoneCount =cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
if(phoneCount > 0){
//获得联系人的电话号码列表
Cursor phonesCursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone._ID+" = "+contactsId, null, null);
if(phonesCursor.moveToFirst()){
do{
//遍历所有电话号码
String phoneNumber = phonesCursor.getString(phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Toast.makeText(ContentProviderActivity.this, "联系人电话:"+phoneNumber,Toast.LENGTH_LONG ).show();
}while(phonesCursor.moveToNext());
}
}
}while(cursor.moveToNext());
}
}
});
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="读取联系人信息 "/>
<Button
android:id="@+id/Button1"
android:layout_width="80dip"
android:layout_height="wrap_content"
android:text="确认"
/>
</LinearLayout>
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS" />
实战二:另外一种方法获得联系人
package cn.mycontent;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.widget.TextView;
public class Activity01 extends Activity {
//查询Content Provider时希望返回的列
String [] columns = {
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts._ID,
//People._ID,
//People.NAME
};
Uri contactUri = ContactsContract.Contacts.CONTENT_URI;
TextView tv;
//Uri contaUri = Contacts.People.CONTENT_URI;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView)findViewById(R.id.tv);
String result = getQueryData();
tv.setTextColor(Color.GREEN);
tv.setTextSize(20.0f);
tv.setText("ID\t名字\n" + result);
}
//获取联系人列表的信息,返回 String对象
public String getQueryData(){
String result = "";
//获取ContentResolver对象
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(contactUri, columns, null, null, null);
//获得_ID字段的索引
int idIndex = cursor.getColumnIndex(BaseColumns._ID);
//获得Name字段的索引
int nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
//遍历Cursor提取数据
for (cursor.moveToFirst();(!cursor.isAfterLast());cursor.moveToNext()) {
result = result + cursor.getString(idIndex) + "\t";
result = result + cursor.getString(nameIndex)+ "\t\n";
}
cursor.close();
return result;
}
}
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS" />