一、ContentProvider(内容提供者)介绍
ContentProvider在android中的作用是对外共享数据, 也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据 进行添删改查。关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE或 Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。那么,这里为何要使用ContentProvider对外共享数据 呢?是这样的,如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式而不同,导致数据的访问方式无法统一,如:采用xml文件对外共享数 据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据。使用ContentProvider对外共享数据的好处是统一了数据的访问方式。
当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:
public class PersonProvider extends ContentProvider{
public boolean onCreate()
public Uri insert(Uri uri, ContentValues values)
public int delete(Uri uri, String selection, String[] selectionArgs)
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
public String getType(Uri uri)
}
第二步需要在AndroidManifest.xml使用对该ContentProvider进行配置,为了能让 其他应用找到该ContentProvider ,ContentProvider采用了authorities(主机名/域名)对它进行唯一标识,你可以把ContentProvider看作是一个网 站(想想,网站也是提供数据者),authorities 就是他的域名:
<manifest.... >
<application android:icon="@drawable/icon" android:label="@string/app_name">
<provider android:name=".provider.PersonProvider"
android:authorities="com.example.contentprovidertest.provider"/>
</application>
</manifest>
二、预备知识
从Uri谈起
Uri是指通用资源标志符
A:前缀表明数据受控于一个内容提供者。它从不修改,也就是schema
B:是指在AndroidMainfest.xml中我们注册的provider中的android:authorities属性所对应的
C:具体操作于哪个条目
D:具体指定到哪个条目下的哪条记录
再看它的类结构和常用方法:
Uri
在这个里它是没有构造方法的,它通常通过下面的这个方法来返回一个Uri对象
方法名称 | 描述 |
public static Uri parse (String uriString) | 通过一个传入的字符串来构造一个Uri对象 |
熟悉完Uri类再看与之相关的另外两个类
UriMatcher类
因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。
先看下它比较常用的几个方法:
方法名称 | 描述 |
public void addURI (String authority, String path, int code) | 往UriMatcher类里添加一个拼凑的Uri,在此我们可以理解为UriMatcher为一个Uri的容器,为个容器里面包含着我们即将可能要操作的Uri,它用于我们业务逻辑的处理,特别是第三个参数code,如果通过下面的match()方法匹配成功就返回这个code值 |
public int match (Uri uri) | 与传入的Uri匹配,它会首先与找我们之前通过addURI方法添加进来的Uri匹配,如果匹配成功就返回之前我们设置的code值,否则返回一个UriMatcher.NO_MATCH常量值为-1 |
熟悉完上面的方法,那么我们再来看它如何使用:
UriMatcher类用于匹配Uri,它的用法如下:
第一步把你需要匹配Uri路径全部给注册上,如下:
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "student" , STUDENTS);
uriMatcher.addURI(AUTHORITY, "student/#", STUDENT_ITEM);
uriMatcher.addURI(AUTHORITY, "course" , COURSES);
uriMatcher.addURI(AUTHORITY, "course/#", COURSE_ITEM);
}
int match = uriMatcher.match(uri);
switch (match) {
case STUDENTS:
return database.query(DatabaseStruct.STUDENT.TABLE_STUDENT , projection, selection, selectionArgs, null, null, sortOrder);
case COURSES:
return database.query(DatabaseStruct.COURSE.TABLE_COURSE , projection, selection, selectionArgs, null, null, sortOrder);
default:
LogUtil.e(TAG, "Invalid query Uri "+uri);
break;
}
再看另外一个工具类
ContentUris类
它用于在Uri后面追加一个ID或者解析出传入的Uri所带上的ID值,常用的两个方法如下:
方法名称 | 描述 |
public static Uri withAppendedId (Uri contentUri, long id) | 用于为路径加上ID部分 |
public static long parseId (Uri contentUri) | 从路径中获取ID部分 |
熟悉完上面所提及的相关的类,接下来我们再看这个ContentProvider核心类
ContentProvider
常用方法
方法名称 | 描述 |
public abstract boolean onCreate () | 在ContentProvider创建后被调用。 |
public abstract Uri insert (Uri uri, ContentValues values) | 根据Uri插入values对就的数据 |
public abstract int delete (Uri uri, String selection, String[] selectionArgs) | 根据Uri删除selection指定的条件所匹配的全部记录 |
public abstract int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) | 根据Uri修改selection指定的条件所匹配的全部记录 |
public abstract Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) | 根据Uri查询出selection指定的条件所匹配的全部记录,并且可以指定查询哪些列(projection),以什么方式(sortOrder)排序 |
public abstract String getType (Uri uri) | 返回当前Uri所数据的MIME类型,如果该Uri对应的数据可能包括多条记录,那么MIME类型字符串就是以vnd.android.cursor.dir/开头,如果Uri对应的数据只包含一条记录,那么MIME类型字符串就是以vnd.android.cursor.item/开头 |
ContentProvider暴露了5个抽象的方法,我们需要继承ContentProvider并分别实现这5个方法即可。
既然我们知道了ContentProvider类是向外提供数据的一种机制,那么在之前我们也说过要想来操作这个对外提供的数据,我们就用到了另外一个类:
ContentResolver
在这个类里面也定义了一系列的增、删、改、查方法,与 ContentProvider定义的方法想对应。
可能大家在这里还是有点理不清这些类的一些关系,特别是ContentResolver与ContentProvider与Uri类的关系,那么我上张图吧,或许对大家有所帮助:
三、动手实践
下面通过一个demo来展示ContentProvider使用方法
1、实现ContentProvider
package com.example.contentprovidertest.provider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import com.example.contentprovidertest.util.LogUtil;
public class YPProvider extends ContentProvider {
private static final String TAG = YPProvider.class.getSimpleName();
public static final String AUTHORITY = "com.example.contentprovidertest.provider";
public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY);
public static final Uri CONTENT_URI_STUDENT = Uri.parse("content://"+ AUTHORITY + "/"+DatabaseStruct.STUDENT.TABLE_STUDENT);
public static final Uri CONTENT_URI_COURSE = Uri.parse("content://"+ AUTHORITY + "/"+DatabaseStruct.COURSE.TABLE_COURSE);
private static final int STUDENTS = 1;
private static final int STUDENT_ITEM = 2;
private static final int COURSES = 10;
private static final int COURSE_ITEM = 11;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "student" , STUDENTS);
uriMatcher.addURI(AUTHORITY, "student/#", STUDENT_ITEM);
uriMatcher.addURI(AUTHORITY, "course" , COURSES);
uriMatcher.addURI(AUTHORITY, "course/#", COURSE_ITEM);
}
private MySqliteOpenHelper mHelper;
@Override
public boolean onCreate() {
mHelper = new MySqliteOpenHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
LogUtil.i(TAG, "query uri="+uri);
SQLiteDatabase database = mHelper.getReadableDatabase();
int match = uriMatcher.match(uri);
switch (match) {
case STUDENTS:
return database.query(DatabaseStruct.STUDENT.TABLE_STUDENT , projection, selection, selectionArgs, null, null, sortOrder);
case COURSES:
return database.query(DatabaseStruct.COURSE.TABLE_COURSE , projection, selection, selectionArgs, null, null, sortOrder);
default:
LogUtil.e(TAG, "Invalid query Uri "+uri);
break;
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
LogUtil.i(TAG, "insert uri="+uri);
SQLiteDatabase database = mHelper.getWritableDatabase();
long rowId = -1;
Uri noteUri = null;
int match = uriMatcher.match(uri);
switch (match) {
case STUDENT_ITEM:
rowId = database.insert(DatabaseStruct.STUDENT.TABLE_STUDENT , null, values);
if (rowId == -1) {
LogUtil.d(TAG, "couldn't insert into downloads database");
return null;
}
noteUri=ContentUris.withAppendedId(CONTENT_URI_STUDENT, rowId);
//通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(noteUri, null);
break;
case COURSE_ITEM:
rowId = database.insert(DatabaseStruct.COURSE.TABLE_COURSE , null, values);
if (rowId == -1) {
LogUtil.d(TAG, "couldn't insert into downloads database");
return null;
}
noteUri=ContentUris.withAppendedId(CONTENT_URI_COURSE, rowId);
//通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(noteUri, null);
break;
default:
LogUtil.e(TAG, "Invalid insert Uri "+uri);
break;
}
return noteUri;
}
//批量插入
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
LogUtil.e(TAG, "bulkInsert Uri "+uri);
SQLiteDatabase database = mHelper.getWritableDatabase();
int numInserted = -1;
database.beginTransaction();
int match = uriMatcher.match(uri);
switch (match) {
case STUDENT_ITEM:
try {
for (ContentValues cv : values) {
long newID = database.insertOrThrow(DatabaseStruct.STUDENT.TABLE_STUDENT, null, cv);
if (newID <= 0) {
throw new SQLException("Failed to insert row into " + uri);
}
}
database.setTransactionSuccessful();
//通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(CONTENT_URI_STUDENT, null);
numInserted = values.length;
}finally{
database.endTransaction(); //提交事务
}
break;
case COURSE_ITEM:
try {
for (ContentValues cv : values) {
long newID = database.insertOrThrow(DatabaseStruct.COURSE.TABLE_COURSE, null, cv);
if (newID <= 0) {
throw new SQLException("Failed to insert row into " + uri);
}
}
database.setTransactionSuccessful();
//通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(CONTENT_URI_COURSE, null);
numInserted = values.length;
}finally{
database.endTransaction(); //提交事务
}
break;
default:
LogUtil.e(TAG, "Invalid bulkInsert Uri "+uri);
break;
}
return numInserted;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
LogUtil.i(TAG, "delete uri="+uri);
SQLiteDatabase database = mHelper.getWritableDatabase();
int rowId = -1;
Uri noteUri = null;
int match = uriMatcher.match(uri);
switch (match) {
case STUDENT_ITEM:
rowId = database.delete(DatabaseStruct.STUDENT.TABLE_STUDENT , selection, selectionArgs);
if (rowId == -1) {
LogUtil.d(TAG, "couldn't delete "+uri);
return -1;
}
noteUri=ContentUris.withAppendedId(CONTENT_URI_STUDENT, rowId);
//通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(noteUri, null);
break;
case COURSE_ITEM:
rowId = database.delete(DatabaseStruct.COURSE.TABLE_COURSE , selection, selectionArgs);
if (rowId == -1) {
LogUtil.d(TAG, "couldn't delete "+uri);
return -1;
}
noteUri=ContentUris.withAppendedId(CONTENT_URI_COURSE, rowId);
//通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(noteUri, null);
break;
default:
LogUtil.e(TAG, "Invalid delete Uri "+uri);
break;
}
return rowId;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
LogUtil.i(TAG, "update uri="+uri);
SQLiteDatabase database = mHelper.getWritableDatabase();
int rowId = -1;
Uri noteUri= null;
int match = uriMatcher.match(uri);
switch (match) {
case STUDENT_ITEM:
rowId = database.update(DatabaseStruct.STUDENT.TABLE_STUDENT , values, selection, selectionArgs);
if (rowId == -1) {
LogUtil.d(TAG, "couldn't update "+uri);
}
noteUri=ContentUris.withAppendedId(CONTENT_URI_STUDENT, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
break;
case COURSE_ITEM:
rowId = database.update(DatabaseStruct.COURSE.TABLE_COURSE , values, selection, selectionArgs);
if (rowId == -1) {
LogUtil.d(TAG, "couldn't update "+uri);
}
noteUri=ContentUris.withAppendedId(CONTENT_URI_COURSE, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
break;
default:
LogUtil.e(TAG, "Invalid update Uri "+uri);
break;
}
return rowId;
}
@Override
public String getType(Uri uri) {
LogUtil.i(TAG, "getType uri="+uri);
int match = uriMatcher.match(uri);
String type = null;
switch (match) {
case STUDENTS:
type = "marks";
break;
case COURSES:
type = "downloads";
break;
default:
break;
}
return type;
}
}
其中 bulkInsert 方法 为批量插入方法,它是非抽象的,不需要这个功能的可以不用实现。
2、Android Manifest 文件中 注册ContentProvider
<provider
android:name=".provider.YPProvider"
android:authorities="com.example.contentprovidertest.provider" android:exported="false">
</provider>
3、使用ContentResolver 操作数据库
package com.example.contentprovidertest;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.ContentValues;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import com.example.contentprovidertest.entity.Course;
import com.example.contentprovidertest.entity.Student;
import com.example.contentprovidertest.provider.DatabaseStruct;
import com.example.contentprovidertest.provider.YPProvider;
import com.example.contentprovidertest.util.LogUtil;
public class MainActivity extends Activity implements OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private EditText et_inputA;
private EditText et_inputB;
private ListView studentListView;
private ListView courseListView;
private SimpleDataAdapter courseAdapter;
private SimpleDataAdapter stAdapter;
private MyContentObserver mObserver;
private List<Student> stuList = new ArrayList<Student>();
private List<Course> courseList = new ArrayList<Course>();
private Handler mHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
stAdapter.notifyDataSetChanged();
break;
case 2:
courseAdapter.notifyDataSetChanged();
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
}
private void findView() {
Button bt_add_student = (Button) findViewById(R.id.bt_add_student);
Button bt_delete_student = (Button) findViewById(R.id.bt_delete_student);
Button bt_add_course = (Button) findViewById(R.id.bt_add_course);
Button bt_delete_course = (Button) findViewById(R.id.bt_delete_course);
et_inputA = (EditText) findViewById(R.id.et_inputA);
et_inputB = (EditText) findViewById(R.id.et_inputB);
studentListView = (ListView) findViewById(R.id.student_List);
courseListView = (ListView) findViewById(R.id.course_List);
courseAdapter = new SimpleDataAdapter(this, courseList);
courseListView.setAdapter(courseAdapter);
stAdapter = new SimpleDataAdapter(MainActivity.this, stuList);
studentListView.setAdapter(stAdapter);
bt_add_student.setOnClickListener(this);
bt_delete_student.setOnClickListener(this);
bt_add_course.setOnClickListener(this);
bt_delete_course.setOnClickListener(this);
mObserver = new MyContentObserver();
getContentResolver().registerContentObserver(YPProvider.CONTENT_URI, true, mObserver);
queryTableCourse();
queryTableStudent();
initListView(true);
}
@Override
protected void onStop() {
getContentResolver().unregisterContentObserver(mObserver);
super.onStop();
}
private class MyContentObserver extends ContentObserver{
public MyContentObserver() {
super(new Handler());
}
@Override
public void onChange(boolean selfChange) {
LogUtil.e(TAG, "database changed");
queryTableCourse();
queryTableStudent();
super.onChange(selfChange);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_add_student:
addTable(true);
break;
case R.id.bt_delete_student:
deleteTable(true);
break;
case R.id.bt_add_course:
addTable(false);
break;
case R.id.bt_delete_course:
deleteTable(false);
break;
default:
break;
}
et_inputA.setText("");
et_inputB.setText("");
}
/**
* 查询 Student表
*/
private void queryTableStudent() {
Cursor cursor = getContentResolver().query(
YPProvider.CONTENT_URI_STUDENT,
new String[] { DatabaseStruct.STUDENT._ID,
DatabaseStruct.STUDENT.NAME,
DatabaseStruct.STUDENT.GRADE }, null, null, null);
List<Student> list = new ArrayList<Student>();
while(cursor!=null && cursor.moveToNext()){
int id = cursor.getInt(0);
String name = cursor.getString(1);
int grade = cursor.getInt(2);
// LogUtil.i(TAG, "id="+id+",name="+name+",grade="+grade);
Student s = new Student();
s.setId(id);
s.setName(name);
s.setGrade(grade);
list.add(s);
}
if(cursor!=null){
cursor.close(); //关闭cursor
}
stuList.clear();
stuList.addAll(list);
list = null;
mHandler.sendEmptyMessage(1);
}
/**
* 查询 Course表
*/
private void queryTableCourse() {
Cursor cursor = getContentResolver().query(
YPProvider.CONTENT_URI_COURSE,
new String[] { DatabaseStruct.COURSE._ID,
DatabaseStruct.COURSE.NAME,
DatabaseStruct.COURSE.TEACHER}, null, null, null);
List<Course> list = new ArrayList<Course>();
while(cursor!=null && cursor.moveToNext()){
int id = cursor.getInt(0);
String name = cursor.getString(1);
String teacher = cursor.getString(2);
// LogUtil.i(TAG, "id="+id+",name="+name+",teacher="+teacher);
Course c = new Course();
c.setId(id);
c.setName(name);
c.setTeacher(teacher);
list.add(c);
}
if(cursor!=null){
cursor.close(); //关闭cursor
}
courseList.clear();
courseList.addAll(list);
list = null;
mHandler.sendEmptyMessage(2);
}
/**
* 根据 id 删除 Student/Course表中的某一条数据
* @param isStudent
*/
private void deleteTable(boolean isStudent) {
String sa = et_inputA.getText().toString();
if(sa==null || "".equals(sa)){
return ;
}
try {
int id = Integer.parseInt(sa);
} catch (NumberFormatException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "ID格式非法,请输入要删除数据的ID", Toast.LENGTH_SHORT).show();
return;
}
if (isStudent) {
Uri uri = Uri.parse(YPProvider.CONTENT_URI_STUDENT + "/3");
getContentResolver().delete(uri, DatabaseStruct.STUDENT._ID+" = ?",
new String[] { sa });
} else {
Uri uri = Uri.parse(YPProvider.CONTENT_URI + "/3");
getContentResolver().delete(uri, DatabaseStruct.COURSE._ID+" = ?",
new String[] { sa });
}
initListView(isStudent);
}
/**
* 向 Student/Course表中 插入一条数据
* @param isStudent
*/
private void addTable(boolean isStudent) {
String sa = et_inputA.getText().toString();
String sb = et_inputB.getText().toString();
if(sa==null || sb==null || "".equals(sa) || "".equals(sb)){
Toast.makeText(getApplicationContext(), "请输入内容", Toast.LENGTH_LONG).show();
return ;
}
if (isStudent) {
int grade = 0;
try {
grade = Integer.parseInt(sb);
} catch (NumberFormatException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "成绩格式非法,请输入整数", Toast.LENGTH_SHORT).show();
return;
}
ContentValues values = new ContentValues();
values.put(DatabaseStruct.STUDENT.NAME, sa);
values.put(DatabaseStruct.STUDENT.GRADE, grade);
Uri uri = Uri.parse(YPProvider.CONTENT_URI_STUDENT + "/1");
getContentResolver().insert(uri, values);
} else {
ContentValues values = new ContentValues();
values.put(DatabaseStruct.COURSE.NAME, sa);
values.put(DatabaseStruct.COURSE.TEACHER,sb);
Uri uri = Uri.parse(YPProvider.CONTENT_URI_COURSE + "/1");
getContentResolver().insert(uri, values);
}
initListView(isStudent);
}
private void initListView(boolean isStudent) {
if(isStudent){
studentListView.setVisibility(View.VISIBLE);
courseListView.setVisibility(View.GONE);
}else{
studentListView.setVisibility(View.GONE);
courseListView.setVisibility(View.VISIBLE);
}
}
}
工程代码下载:http://download.csdn.net/detail/fx_sky/6414595