一、ContentProvider
1.1、简介
ContentProvider(内容提供者)是Android中的四大组件之一。
ContentProvider分为系统的和自定义的,系统的也就是例如联系人,图片等数据。
内容提供者将一些特定的应用程序数据供给其它应用程序使用。数据可以存储于文件系统、SQLite数据库或其它方式。
内容提供者继承于ContentProvider基类,为其它应用程序取用和存储它管理的数据实现了一套标准方法。
应用程序并不直接调用这些方法,而是使用一个 ContentResolver对象,调用它的方法作为替代。
ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。
1.2、ContentProvider
Android提供一些主要数据类型的ContentProvider,比如音频、视频、图片和私人通讯录等,
可在android.provider包下找到一些Android提供的ContentProvider。
通过获得这些ContentProvider可以查询它们包含的数据,当然前提是已获得适当的读取权限。
1.主要方法
*如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头。
例:要得到所有person记录的Uri为content://contacts/person,那么返回的MIME类型字符串为"vnd.android.cursor.dir/person"。
*如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头。
例:要得到id为10的person记录的Uri为content://contacts/person/10,那么返回的MIME类型字符串应为"vnd.android.cursor.item/person"。
1.3、ContentResolver
当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,
要获取ContentResolver对象,可以使用Context提供的getContentResolver()方法。
ContentResolver contentResolver = getContentResolver();
ContentResolver提供的方法和ContentProvider提供的方法对应的有以下几个方法:
1.4、URI
外部程序只需知道内容提供者的Uri路径信息,通过ContentResolver即可调用内容提供者。
1.5、操作短信
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; } } }
1.6、操作联系人
1.准备工作
a)通过DDMS,查看Android模拟器下的com.android.providers.contacts包下的数据库,查看其contact2.db数据库的内容。
b)查看数据库,其中raw_contacts表存放的是联系人条数信息,data表中存放的是raw_contacts中的每一条id对应的具体信息,不同类型的信息由mimetype_id来标识。
raw_contacts表:
data表:
mimetypes表:
c)打开Android源码,查看packages\providers\路径下的文件,其中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
2.操作实例
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; }
1.7、自定义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字符串由三部分组成:
1. 必须以vnd开头。
2. 如果URI以路径结尾,则后接 android.cursor.dir/,如果URI以id结尾则后接 android.cursor.item/。
3. 最后接上 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
1.8、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的基本使用,更细节的使用方式待后续补充。
1.9、观察者模式
原生的方式一般不推荐使用:有两个弊端
- 继承Observer的话,观察者只能作为它的子类。
- update中的data数据类型不明确。
自定义观察者模式:
a)在被观察者中将定义MyObserver接口,并将被观察者对象和数据传递进接口的方法update()。
b)在被观察者中造一个集合,来表示观察者的集合。
c)在被观察者中暴漏添加观察者的方法和删除观察者的方法。
d)在被观察者中定义notifyXXX(Data data)方法,并将被观察者中的数据传递进去。
并对观察者集合进行迭代,迭代过程中一一调用MyObserver中的方法,并将当前对象和数据传递进去。
e) 那么当被观察者中数据改变时,我们就可以调用notifyXXX(Data data)方法传入数据了。
f) 给观察者实现MyObserver接口,并重写其中的update()方法。
g)将被观察者定义为单例模式,在观察者中得到被观察者的实例后,去添加和销毁监听器即可。