android基础总结篇之八:创建及调用自己的ContentProvider
今天我们来讲解一下如何创建及调用自己的contentprovider。
在前面两篇文章中我们分别讲了如何读写联系人和短消息,相信大家对于contentprovider的操作方法已经有了一定程度的了解。在有些场合,除了操作contentprovider之外,我们还有可能需要创建自己的contentprovider,来提供信息共享的服务,这就要求我们很好的掌握contentprovider的创建及使用技巧。下面我们就由表及里的逐步讲解每个步骤。
在正式开始实例演示之前,我们先来了解以下两个知识点:
授权:
在android中,每一个contentprovider都会用类似于域名的字符串来注册自己,我们成为授权(authority)。这个唯一标识的字符串是此contentprovider可提供的一组uri的基础,有了这个基础,才能够向外界提供信息的共享服务。
授权是在androidmanifest.xml中完成的,每一个contentprovider必须在此声明并授权,方式如下:
<provider android:name=".someprovider" android:authorities="com.your-company.someprovider"/>
上面的<provider>元素指明了contentprovider的提供者是“someprovider”这个类,并为其授权,授权的基础uri为“com.your-company.someprovider”。有了这个授权信息,系统可以准确的定位到具体的contentprovider,从而使访问者能够获取到指定的信息。这和浏览web页面的方式很相似,“someprovider”就像一台具体的服务器,而“com.your-company.someprovider”就像注册的域名,相信大家对这个概念并不陌生,由此联想一下就可以了解contentprovider授权的作用了。(需要注意的是,除了android内置应用程序之外,第三方程序应尽量使用以上方式的完全限定的授权名。)
mime类型:
就像网站返回给定url的mime(multipurpose internet mail extensions,多用途internet邮件扩展)类型一样(这使浏览器能够用正确的程序来查看内容),contentprovider还负责返回给定uri的mime类型。根据mime类型规范,mime类型包含两部分:类型和子类型。例如:text/html,text/css,text/xml等等。
android也遵循类似的约定来定义mime类型。
对于单条记录,mime类型类似于:
vnd.android.cursor.item/vnd.your-company.content-type
而对于记录的集合,mime类型类似于:
vnd.android.cursor.dir/vnd.your-company.comtent-type
其中的vnd表示这些类型和子类型具有非标准的、供应商特定的形式;content-type可以根据contentprovider的功能来定,比如日记的contentprovider可以为note,日程安排的contentprovider可以为schedule,等等。
了解了以上两个知识点之后,我们就结合实例来演示一下具体的过程。
我们将会创建一个记录person信息的contentprovider,实现对person的crud操作,访问者可以通过下面路径操作我们的contentprovider:
访问者可以通过“[base_uri]/persons”来操作person集合,也可以通过“[base_uri]/persons/#”的形式操作单个person。
我们创建一个person的contentprovider需要两个步骤:
1.创建personprovider类:
我们需要继承contentprovider类,实现oncreate、query、insert、update、delete和gettype这几个方法。具体代码如下:
package com.scott.provider; import android.content.contentprovider; import android.content.contenturis; import android.content.contentvalues; import android.content.urimatcher; import android.database.cursor; import android.database.sqlite.sqlitedatabase; import android.net.uri; public class personprovider extends contentprovider { private static final urimatcher matcher; private dbhelper helper; private sqlitedatabase db; private static final string authority = "com.scott.provider.personprovider"; private static final int person_all = 0; private static final int person_one = 1; public static final string content_type = "vnd.android.cursor.dir/vnd.scott.person"; public static final string content_item_type = "vnd.android.cursor.item/vnd.scott.person"; //数据改变后立即重新查询 private static final uri notify_uri = uri.parse("content://" + authority + "/persons"); static { matcher = new urimatcher(urimatcher.no_match); matcher.adduri(authority, "persons", person_all); //匹配记录集合 matcher.adduri(authority, "persons/#", person_one); //匹配单条记录 } @override public boolean oncreate() { helper = new dbhelper(getcontext()); return true; } @override public string gettype(uri uri) { int match = matcher.match(uri); switch (match) { case person_all: return content_type; case person_one: return content_item_type; default: throw new illegalargumentexception("unknown uri: " + uri); } } @override public cursor query(uri uri, string[] projection, string selection, string[] selectionargs, string sortorder) { db = helper.getreadabledatabase(); int match = matcher.match(uri); switch (match) { case person_all: //doesn't need any code in my provider. break; case person_one: long _id = contenturis.parseid(uri); selection = "_id = ?"; selectionargs = new string[]{string.valueof(_id)}; break; default: throw new illegalargumentexception("unknown uri: " + uri); } return db.query("person", projection, selection, selectionargs, null, null, sortorder); } @override public uri insert(uri uri, contentvalues values) { int match = matcher.match(uri); if (match != person_all) { throw new illegalargumentexception("wrong uri: " + uri); } db = helper.getwritabledatabase(); if (values == null) { values = new contentvalues(); values.put("name", "no name"); values.put("age", "1"); values.put("info", "no info."); } long rowid = db.insert("person", null, values); if (rowid > 0) { notifydatachanged(); return contenturis.withappendedid(uri, rowid); } return null; } @override public int delete(uri uri, string selection, string[] selectionargs) { db = helper.getwritabledatabase(); int match = matcher.match(uri); switch (match) { case person_all: //doesn't need any code in my provider. break; case person_one: long _id = contenturis.parseid(uri); selection = "_id = ?"; selectionargs = new string[]{string.valueof(_id)}; } int count = db.delete("person", selection, selectionargs); if (count > 0) { notifydatachanged(); } return count; } @override public int update(uri uri, contentvalues values, string selection, string[] selectionargs) { db = helper.getwritabledatabase(); int match = matcher.match(uri); switch (match) { case person_all: //doesn't need any code in my provider. break; case person_one: long _id = contenturis.parseid(uri); selection = "_id = ?"; selectionargs = new string[]{string.valueof(_id)}; break; default: throw new illegalargumentexception("unknown uri: " + uri); } int count = db.update("person", values, selection, selectionargs); if (count > 0) { notifydatachanged(); } return count; } //通知指定uri数据已改变 private void notifydatachanged() { getcontext().getcontentresolver().notifychange(notify_uri, null); } }
在personprovider中,我们定义了授权地址为“com.scott.provider.personprovider”,相信大家在前面也有所了解了。基于这个授权,我们使用了一个urimatcher对其路径进行匹配,“[base_uri]/persons"和“[base_uri]/persons/#”这两种路径我们在上面也介绍过,分别对应记录集合和单个记录的操作。在query、insert、update和delete方法中我们根据urimatcher匹配结果来判断该uri是操作记录集合还是单条记录,从而采取不同的处理方法。在gettype方法中,我们会根据匹配的结果返回不同的mime类型,这一步是不能缺少的,比如我们在query方法中有可能是查询全部集合,有可能是查询单条记录,那么我们返回的cursor或是集合类型,或是单条记录,这个跟gettype返回的mime类型是一致的,就好像浏览网页一样,指定的url返回的信息是什么类型,那么浏览器就应该接收到对应的mime类型。另外,我们注意到,上面代码中,在insert、update、delete方法中都调用了notifydatachanged方法,这个方法中仅有的一步操作就是通知“[base_uri]/persons"的访问者,数据发生改变了,应该重新加载了。
在我们的personprovider中,我们用到了person、dbhelper类,代码如下:
package com.scott.provider; public class person { public int _id; public string name; public int age; public string info; public person() { } public person(string name, int age, string info) { this.name = name; this.age = age; this.info = info; } } [java] view plain copy package com.scott.provider; import android.content.context; import android.database.sqlite.sqlitedatabase; import android.database.sqlite.sqliteopenhelper; public class dbhelper extends sqliteopenhelper { private static final string database_name = "provider.db"; private static final int database_version = 1; public dbhelper(context context) { super(context, database_name, null, database_version); } @override public void oncreate(sqlitedatabase db) { string sql = "create table if not exists person" + "(_id integer primary key autoincrement, name varchar, age integer, info text)"; db.execsql(sql); } @override public void onupgrade(sqlitedatabase db, int oldversion, int newversion) { db.execsql("drop table if exists person"); oncreate(db); } }
最后,要想让这个contentprovider生效,我们需要在androidmanifest.xml中声明并为其授权,如下所示:
<provider android:name=".personprovider" android:authorities="com.scott.provider.personprovider" android:multiprocess="true"/>
其中,android:multiprocess代表是否允许多进程操作。另外我们也可以为其声明相应的权限,对应的属性是:android:permission。
2.调用personprovider类:
完成了person的contentprovider后,下面我们来看一下如何访问它。这一步我们在mainactivity中完成,看下面代码:
package com.scott.provider; import java.util.arraylist; import android.app.activity; import android.content.contentresolver; import android.content.contenturis; import android.content.contentvalues; import android.database.cursor; import android.database.cursorwrapper; import android.net.uri; import android.os.bundle; import android.os.handler; import android.os.message; import android.view.view; import android.widget.listview; import android.widget.simplecursoradapter; public class mainactivity extends activity { private contentresolver resolver; private listview listview; private static final string authority = "com.scott.provider.personprovider"; private static final uri person_all_uri = uri.parse("content://" + authority + "/persons"); private handler handler = new handler() { public void handlemessage(message msg) { //update records. requery(); }; }; @override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); resolver = getcontentresolver(); listview = (listview) findviewbyid(r.id.listview); //为person_all_uri注册变化通知 getcontentresolver().registercontentobserver(person_all_uri, true, new personobserver(handler)); } /** * 初始化 * @param view */ public void init(view view) { arraylist<person> persons = new arraylist<person>(); person person1 = new person("ella", 22, "lively girl"); person person2 = new person("jenny", 22, "beautiful girl"); person person3 = new person("jessica", 23, "sexy girl"); person person4 = new person("kelly", 23, "hot baby"); person person5 = new person("jane", 25, "pretty woman"); persons.add(person1); persons.add(person2); persons.add(person3); persons.add(person4); persons.add(person5); for (person person : persons) { contentvalues values = new contentvalues(); values.put("name", person.name); values.put("age", person.age); values.put("info", person.info); resolver.insert(person_all_uri, values); } } /** * 查询所有记录 * @param view */ public void query(view view) { // uri persononeuri = contenturis.withappendedid(person_all_uri, 1);查询_id为1的记录 cursor c = resolver.query(person_all_uri, null, null, null, null); cursorwrapper cursorwrapper = new cursorwrapper(c) { @override public string getstring(int columnindex) { //将简介前加上年龄 if (getcolumnname(columnindex).equals("info")) { int age = getint(getcolumnindex("age")); return age + " years old, " + super.getstring(columnindex); } return super.getstring(columnindex); } }; //cursor须含有"_id"字段 simplecursoradapter adapter = new simplecursoradapter(this, android.r.layout.simple_list_item_2, cursorwrapper, new string[]{"name", "info"}, new int[]{android.r.id.text1, android.r.id.text2}); listview.setadapter(adapter); startmanagingcursor(cursorwrapper); //管理cursor } /** * 插入一条记录 * @param view */ public void insert(view view) { person person = new person("alina", 26, "attractive lady"); contentvalues values = new contentvalues(); values.put("name", person.name); values.put("age", person.age); values.put("info", person.info); resolver.insert(person_all_uri, values); } /** * 更新一条记录 * @param view */ public void update(view view) { person person = new person(); person.name = "jane"; person.age = 30; //将指定name的记录age字段更新为30 contentvalues values = new contentvalues(); values.put("age", person.age); resolver.update(person_all_uri, values, "name = ?", new string[]{person.name}); //将_id为1的age更新为30 // uri updateuri = contenturis.withappendedid(person_all_uri, 1); // resolver.update(updateuri, values, null, null); } /** * 删除一条记录 * @param view */ public void delete(view view) { //删除_id为1的记录 uri deluri = contenturis.withappendedid(person_all_uri, 1); resolver.delete(deluri, null, null); //删除所有记录 // resolver.delete(person_all_uri, null, null); } /** * 重新查询 */ private void requery() { //实际操作中可以查询集合信息后adapter.notifydatasetchanged(); query(null); } }
我们看到,在上面的代码中,分别对应每一种情况进行测试,相对较为简单。我们主要讲一下registercontentobserver这一环节。
在前面的personprovider我们也提到,在数据更改后,会向指定的uri访问者发出通知,以便于更新查询记录。大家注意,仅仅是contentprovider出力还不够,我们还需要在访问者中注册一个contentobserver,才能够接收到这个通知。下面我们创建一个
personobserver: package com.scott.provider; import android.database.contentobserver; import android.os.handler; import android.os.message; import android.util.log; public class personobserver extends contentobserver { public static final string tag = "personobserver"; private handler handler; public personobserver(handler handler) { super(handler); this.handler = handler; } @override public void onchange(boolean selfchange) { super.onchange(selfchange); log.i(tag, "data changed, try to requery."); //向handler发送消息,更新查询记录 message msg = new message(); handler.sendmessage(msg); } }
这样一来,当contentprovider发来通知之后,我们就能立即接收到,从而向handler发送一条消息,重新查询记录,使我们能够看到最新的记录信息。
最后,我们要在androidmanifest.xml中为mainactivity添加mime类型过滤器,告诉系统mainactivity可以处理的信息类型:
<!-- mime类型 --> <intent-filter> <data android:mimetype="vnd.android.cursor.dir/vnd.scott.person"/> </intent-filter> <intent-filter> <data android:mimetype="vnd.android.cursor.item/vnd.scott.person"/> </intent-filter>
这样就完成了访问者的代码,我们来看一下效果:
鉴于操作类型太多,我在这里就不再展示了,大家可以自己试一试。
原文链接:http://blog.csdn.net/liuhe688/article/details/7050868
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: alt属性和title属性