ContentProvider
ContentProvider分为系统的和自定义的,系统的也就是例如联系人,图片等数据。内容提供者将一些特定的应用程序数据供给其它应用程序使用。数据可以存储于文件系统、
SQLite数据库或其它方式。它继承于ContentProvider基类,为其它应用程序取用和存储它管理的数据实现了一套标准方法。
应用程序并不直接调用这些方法,而是使用一个 ContentResolver对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关
交互通讯进行管理。
ContentProvider基础
Android提供一些主要数据类型的ContentProvider,比如音频、视频、图片和私人通讯录等,可在android.provider包下找到一些Android提供的ContentProvider。通过获
得这些ContentProvider可以查询它们包含的数据,当然前提是已获得适当的读取权限。
方法 | 描述 |
---|---|
public boolean onCreate() | 在创建ContentProvider调用 |
public Cursor query(Uri, String[], String, String[], String) | 用于查询指定Uri的ContentProvider,返回一个Cursor |
public Uri insert(Uri, ContentValues) | 用于添加数据到指定Uri的ContentProvider中 |
public int update(Uri, ContentValues, String, String[]) | 用于更新指定Uri的ContentProvider中的数据 |
public int delete(Uri, String, String[]) | 用于从指定Uri的ContentProvider中删除数据 |
public String getType(Uri) | 用于返回指定的Uri中的数据的MIME类型 |
1、如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头。
要得到所有person记录的Uri为content://contacts/person,那么返回的MIME类型字符串为"vnd.android.cursor.dir/person"。
2、如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头。
要得到id为10的person记录的Uri为content://contacts/person/10,那么返回的MIME类型字符串应为"vnd.android.cursor.item/person"。
ContentResolver
当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用
Context提供的getContentResolver()方法。
ContentResolver contentResolver = getContentResolver();
ContentResolver提供的方法和ContentProvider提供的方法对应的有以下几个方法:
方法 | 描述 |
---|---|
public Uri insert(Uri, ContentValues) | 用于添加数据到指定Uri的ContentProvider中。 |
public int delete(Uri, String, String[]) | 用于从指定Uri的ContentProvider中删除数据。 |
public int update(Uri , ContentValues, String, String[]) | 用于更新指定Uri的ContentProvider中的数据。 |
public Cursor query(Uri , String[] , String , String[] , String ) | 用于查询指定Uri的ContentProvider。 |
URI
外部程序只需知道内容提供者的Uri路径信息,通过ContentResolver即可调用内容提供者。
字段 | 描述 |
---|---|
schema | 用来说明一个ContentProvider控制这些数据。"content://" |
主机名或授权Authority | 它定义了是哪个ContentProvider提供这些数据。 |
path | 路径,URI下的某一个Item。 |
ID | 通常定义Uri时使用”#”号占位符代替, 使用时替换成对应的数字。 |
content://com.itheima.provider/person/# | #表示数据id(#代表任意数字) |
content://com.itheima.provider/person/* | *来匹配任意文本 |
ContentProvider实例
操作短信
ContentProvider操作短信示例如下:
public class SmsUtils {
/** 声明一个接口 , 包含一些回调函数 */
public interface BackupSmsCallBack {
/**
* 短信备份之前调用的方法
* @param max 短信的总条目个数
*/
public void beforeSmsBackup(int max);
/**
* 当短信备份过程中调用的方法
*
* @param process 当前备份的进度
*/
public void onSmsBackup(int process);
}
/**
* 备份用户的短信
* @param context 上下文
* @param callback 短信备份的接口
* @param filename 备份后的文件名称
* @return 是否备份成功
*/
public static boolean backupSms(Context context,
BackupSmsCallBack callback, String filename) {
try {
ContentResolver resolver = context.getContentResolver();
Uri uri = Uri.parse("content://sms/");
File file = new File(Environment.getExternalStorageDirectory(),
filename);
FileOutputStream fos = new FileOutputStream(file);
XmlSerializer serializer = Xml.newSerializer();
serializer.setOutput(fos, "utf-8");
serializer.startDocument("utf-8", true);
serializer.startTag(null, "info");
Cursor cursor = resolver.query(uri, new String[] { "address",
"date", "body", "type" }, null, null, null);
int max = cursor.getCount();
serializer.attribute(null, "total", String.valueOf(max));
callback.beforeSmsBackup(max);
int process = 0;
while (cursor.moveToNext()) {
serializer.startTag(null, "sms");
serializer.startTag(null, "address");
String address = cursor.getString(0);
serializer.text(address);
serializer.endTag(null, "address");
serializer.startTag(null, "date");
String date = cursor.getString(1);
serializer.text(date);
serializer.endTag(null, "date");
serializer.startTag(null, "body");
String body = cursor.getString(2);
serializer.text(body);
serializer.endTag(null, "body");
serializer.startTag(null, "type");
String type = cursor.getString(3);
serializer.text(type);
serializer.endTag(null, "type");
serializer.endTag(null, "sms");
Thread.sleep(2000);
process++;
// pb.setProgress(process);
// pd.setProgress(process);
callback.onSmsBackup(process);
}
cursor.close();
serializer.endTag(null, "info");
serializer.endDocument();
fos.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
操作联系人
准备工作
a)通过DDMS,查看Android模拟器下的com.android.providers.contacts包下的数据库,查看其contact2.db数据库的内容。
b)查看数据库,其中raw_contacts表存放的是联系人条数信息,data表中存放的是raw_contacts中的每一条id对应的具体信息,不同类型的信息由mimetype_id来标识。
raw_contacts表:
data表:
mimetypes表:
c)打开Android源码,查看packagesproviders路径下的文件,其中ContactsProvider就是联系人的内容提供者。
打开清单文件,寻找联系人的内容提供者对应的是哪个java文件。
打开ContactsProvider2.java文件,查看此内容提供者的uri路径信息
操作raw_contacts表的Uri:
content://com.adroid.contacts/raw_contacts
操作data表的Uri:
content://com.adroid.contacts/data
操作数据库表时注意contacts2.db数据库使用了视图,所以操作数据库表时表结构有所改变,注意操作时要操纵的列的列名已经改变。
比如:data表在查询的时候没有mimetype_id,取代的是mimetype
操作实例
public static List<ContactInfo> getContactInfos(Context context) {
// 内容提供者的解析器
ContentResolver resolver = context.getContentResolver();
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri datauri = Uri.parse("content://com.android.contacts/data");
Cursor cursor = resolver.query(uri, new String[] { "contact_id" },null, null, null);
List<ContactInfo> contactInfos = new ArrayList<ContactInfoUtils.ContactInfo>();
while (cursor.moveToNext()) {
String id = cursor.getString(0);
System.out.println("id=" + id);
if (id != null) {
ContactInfo info = new ContactInfoUtils().new ContactInfo();
Cursor datacursor = resolver.query(datauri, new String[] {
"data1", "mimetype" }, "raw_contact_id=?",
new String[] { id }, null);
while (datacursor.moveToNext()) {
String data1 = datacursor.getString(0);
String mimetype = datacursor.getString(1);
if ("vnd.android.cursor.item/name".equals(mimetype)) {
info.name = data1;// 姓名
} else if ("vnd.android.cursor.item/phone_v2".equals(mimetype)) {
info.phone = data1;// 电话
} else if ("vnd.android.cursor.item/email_v2".equals(mimetype)) {
info.email = data1;// 邮箱
}
}
contactInfos.add(info);
}
}
return contactInfos;
}
public class ContactInfo {
public String name;
public String email;
public String phone;
}
自定义ContentProvider
a)新建一个类继承ContentProvider来创建内容提供器,并实现六个抽象方法。
public class MyProvider extends ContentProvider {
// 初始化内容提供器调用。通常完成数据库的创建和升级操作。
@Override
public boolean onCreate() {
return false;
}
// 从内容提供器中查询数据,使用uri参数确定查询哪张表。
@Override
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
return null;
}
// 向内容提供器中添加一条数据使用uri参数确定添加哪张表。
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
// 从内容提供器中删除数据,使用uri来确定删除哪张表。
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
// 更新内容提供器中已有的数据,使用uri参数来确定更新哪张表。
@Override
public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {
return 0;
}
// 根据传入的内容URI来返回相应的MIME类型。
@Override
public String getType(Uri uri) {
return null;
}
}
b) uri的格式上面已经有叙述,接下来我们通过UriMatcher类实现匹配内容URI的功能
public class MyProvider extends ContentProvider {
// 表示访问table1和table2中的所有数据或单条数据
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher mUriMatcher;
static{
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI("cn.legend.provider", "table1", TABLE1_DIR);
mUriMatcher.addURI("cn.legend.provider", "table1/#", TABLE1_ITEM);
mUriMatcher.addURI("cn.legend.provider", "table2", TABLE2_DIR);
mUriMatcher.addURI("cn.legend.provider", "table2/#", TABLE2_ITEM);
}
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
switch (mUriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询table1表中的所有数据
break;
case TABLE1_ITEM:
// 查询table1表中的单条数据
break;
case TABLE2_DIR:
// 查询table2表中的所有数据
break;
case TABLE2_ITEM:
// 查询table2表中的单条数据
break;
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
switch (mUriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.cn.legend.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.dir/vnd.cn.legend.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.cn.legend.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.dir/vnd.cn.legend.provider.table2";
}
return null;
}
}
c)上述只是以查询为例,增删改查都大同小异,只有getType()方法比较特殊,它是获取uri对象所对应的MIME类型,MIME字符串由三部分组成:
- 必须以vnd开头。
- 如果URI以路径结尾,则后接 android.cursor.dir/,如果URI以id结尾则后接 android.cursor.item/。
- 最后接上 vnd.<authority>.<path>。
对于content://cn.legend.provide/table1这个URI所对应MIME:
vnd.android.cursor.dir/vnd.cn.legend.provider.table1
对于content://cn.legend.provider/table1/1这个URI所对应MIME:
vnd.android.cursor.item/vnd.cn.legend.provider.table1
ContentObserver
ContentObserver的使用类似与设计模式中的观察者模式,ContentObserver是观察者,被观察的ContentProvider是被观察者。
当被观察者ContentProvider的数据发生了增删改的变化,就会及时的通知给ContentProvider,ContentObsserver做出相应的处理。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注册观察者Observser
this.getContentResolver().registerContentObserver(Uri.parse("content://sms"),true,new SMSObserver(new Handler()));
}
private final class SMSObserver extends ContentObserver {
public SMSObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
Cursor cursor = MainActivity.this.getContentResolver().query(
Uri.parse("content://sms/inbox"), null, null, null, null);
while (cursor.moveToNext()) {
StringBuilder sb = new StringBuilder();
sb.append("address=").append(
cursor.getString(cursor.getColumnIndex("address")));
sb.append(";subject=").append(
cursor.getString(cursor.getColumnIndex("subject")));
sb.append(";body=").append(
cursor.getString(cursor.getColumnIndex("body")));
sb.append(";time=").append(
cursor.getLong(cursor.getColumnIndex("date")));
System.out.println("--------has Receivered SMS::" + sb.toString());
}
}
}
}
这些知识contentObserver的基本使用,更细节的使用方式待后续补充。