Android开发之Content Provider与Service

Content provider简介

Android中的Content provider机制可支持在多个应用中存储和读取数据。这也是跨应用共享数据的唯一方式。在android系统中,没有一个公共的内存区域,供多个应用共享存储数据。

Android提供了一些主要数据类型的Content provider,比如音频、视频、图片和私人通讯录等。可在android.provider包下面找到一些android提供的Content provider。可以获得这些Content provider,查询它们包含的数据,当然前提是已获得适当的读取权限。

如果想公开自己的数据,那么可有两种办法:

  1. 创建自己的Content provider,需要继承ContentProvider类;
  2. 如果你的数据和已存在的Content provider数据结构一致,可以将数据写到已存在的Content provider中,当然前提是获取写该Content provider的权限。比如把OA中的成员通讯信息加入到系统的联系人Content provider中。

    Content provider基础

    所有Content provider都需要实现相同的接口用于查询Content provider并返回数据,也包括增加、修改和删除数据。

    首先需要获得一个ContentResolver的实例,可通过Activity的成员方法getContentResovler()方法:

    ContentResolver cr = getContentResolver();

    ContentResolver实例带的方法可实现找到指定的Content provider并获取到Content provider的数据。

    ContentResolver的查询过程开始,Android系统将确定查询所需的具体Content provider,确认它是否启动并运行它。android系统负责初始化所有的Content provider,不需要用户自己去创建。实际上,content provider的用户都不可能直接访问到content provider实例,只能通过ContentResolver在中间代理。

    数据模型

    Content provider展示数据类似一个单个数据库表。其中:

    每行有个带唯一值的数字字段,名为_ID,可用于对表中指定记录的定位;

    Content provider返回的数据结构,是类似JDBCResultSet,在android中,是Cursor对象。

    URI

    每个content provider定义一个唯一的公开的URI,用于指定到它的数据集。一个content provider可以包含多个数据集(可以看作多张表),这样,就需要有多个URI与每个数据集对应。这些URI要以这样的格式开头:

    content//

    表示这个uri指定一个content provider

    如果你想创建自己的content provider,最好把自定义的URI设置为类的常量,这样简化别人的调用,并且以后如果更新URI也很容易。android定义了CONTENT_URI常量用于URI,比如:

    android.provider.Contacts.Phones.CONTENT_URI
    android.provider.Contacts.Photos.CONTENT_URI

    要注意的是上面例子中的Contacts,已经在android 2.0及以上版本不赞成使用。

    查询Content provider

    要想使用一个content provider,需要以下信息:

    定义这个content providerURI

    返回结果的字段名称

    这些字段的数据类型

    如果需要查询content provider数据集的特定记录(行),还需要知道该记录的ID的值。

    构建查询

    查询就是输入URI等参数,其中URI是必须的,其他是可选的,如果系统能找到URI对应的content provider将返回一个Cursor对象。

    可以通过ContentResolver.query()或者Activity.managedQuery()方法。两者的方法参数完全一样,查询 过程和返回值也是相同的。区别是,通过Activity.managedQuery()方法,不但获取到Cursor对象,而且能够管理Cursor对象 的生命周期,比如当Activity暂停(pause)的时候,卸载该Cursor对象,当Activity restart的时候重新查询。另外,也可以对一个没有处于Activity管理的Cursor对象做成被Activity管理的,通过调用 Activity.startManaginCursor()方法。

    类似这样:

    Cursor cur = managedQuery(myPerson, null, null, null, null);

    其中第一个参数myPersonUri类型实例。

    如果需要查询的是指定行的记录,需要用_ID值,比如ID值为23URI将是类似:

    content://. . . ./23

    android提供了方便的方法,让开发者不需要自己拼接上面这样的URI,比如类似:

    Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);

    或者:

    Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23″);

    二者的区别是一个接收整数类型的ID值,一个接收字符串类型。

    其他几个参数:

    names,可以为null,表示取数据集的全部列,或者声明一个String数组,数组中存放列名称,比如:People._ID。一般列名都在该Content provider中有常量对应;

    针对返回结果的过滤器,格式类似于SQL中的WHERE子句,区别是不带WHERE关键字,如果返回null表示不过滤,比如name=?

    前面过滤器的参数,是String数组,是针对前面条件中?占位符的值;

    排序参数,类似SQLORDER BY字句,不过不需要写ORDER BY部分,比如name desc,如果不排序,可输入null

    返回值是Cursor对象,游标位置在第一条记录之前。不同的content provider会有不同的列和名称,但是会有两个相同的列,上面提到过的一个是_ID,用于唯一标识记录,还有一个_COUNT,用于记录整个结果集的大小,可以看到上面图中的_COUNT的值是相同的。
    读取返回的数据

    如果在查询的时候使用到ID,那么返回的数据只有一条记录。在其他情况下,一般会有多条记录。和JDBCResultSet类似,需要操作游标遍历结果集,在每行,再通过列名获取到列的值,可以通过getString()getInt()getFloat()等方法获取值。

    JDBC中不同,没有直接通过列名获取列值的方法,只能先列名获取到列的整型索引值,然后再通过该索引值定位获取列的值。
    编辑数据

    可以通过content provider实现以下编辑功能:

    * 增加新的记录;
    *
    在已经存在的记录中增加新的值;
    *
    批量更新已经存在的多个记录;
    *
    删除记录。

    所有的编辑功能都是通过ContentResolver的方法实现。一些Content provider对权限要求更严格一些,需要写的权限,如果没有会报错。
    增加记录

    要想增加记录到content provider,首先,要在ContentValues对象中设置类似map的键值对,在这里,键的值对应content provider中的列的名字,键值对的值,是对应列希望的类型。然后,调用ContentResolver.insert()方法,传入这个 ContentValues对象,和对应Content providerURI即可。返回值是这个新记录的URI对象。这样你可以通过这个URI获得包含这条记录的Cursor对象

    在原有记录上增加值

    如果记录已经存在,可在记录上增加新的值,或者编辑已经存在的值。

    首先要过去到原来的值对象,然后要清除原有的值,然后像上面增加记录一样即可:

    批量更新值

    批量更新一组记录的值,比如NY改名为Eew York。可调用ContenResolver.update()方法。
    删除记录

    如果是删除单个记录,调用ContentResolver.delete()方法,URI参数,指定到具体行即可。

    如果是删除多个记录,调用ContentResolver.delete()方法,URI参数指定Content provider即可,并带一个类似SQLWHERE子句条件。这里和上面类似,不带WHERE关键字。
    创建自己的Content provider

    创建content provider,需要:

    * 设置存储系统。大多数content provider使用文件或者SQLite数据库,不过你可以用任何方式存储数据。android提供SQLiteoOpenHelper帮助开发者创建和管理SQLiteDatabase
    *
    继承ContentProvider,提供对数据的访问。
    *
    manifest文件中声明content provider

    继承ContentProvider

    必须定义ContentProvider类的子类,需要实现如下方法:

    query()insert()update()delete()getType()onCreate()

    query()方法,返回值是Cursor实例,用于迭代请求的数据。Cursor是一个接口。android为该接口提供了一些只读的(和 JDBCResultSet不一样,后者还提供可写入的可选特性)Cursor实现。比如SQLiteCursor,可迭代SQLite数据库中的数据。可以通过SQLiteDatabase类的query()方法获取到该Cursor实例。还有其他的Cursor实现,比如 MatrixCursor,用于数据不是存储在数据库的情况下。

    因为Content provider可能被多个ContentResolver对象在不同的进程和线程中调用,因此实现Content provider必须考虑线程安全问题。

    作为良好的习惯,在实现编辑数据的代码中,要调用ContentResolver.notifyChange()方法,通知那些监听数据变化的监听器。

    在实现子类的时候,还有一些步骤可以简化Content provider客户端的使用:

    定义public static final Uri常量,名称为CONTENT_URI

    public static final Uri CONTENT_URI =

    Uri.parse("content://com.example.codelab.transportationprovider");

    如果有多个表,它们也是使用相同的CONTENT_URI,只是它们的路径部分不同。

    也就是说红色框部分是一致的。

    定义返回的列名,public static final,列名的值,比如使用SQLite数据库作为存储,对应表的列名。

    在文档中要写出各个列的数据类型,便于使用者读取。

    如果需要处理新的MIME数据类型,比如通过Intent的方式,并且带datamimeType(参见总结一下Intent概念),那么需要在ContentProvider.getType()方法中进行处理,参见编写完整的Content provider示例编写一个getType方法部分。

    如果处理数据库表中超大的数据,比如很大的位图文件,一般存在文件系统中,可以参照content provider中使用大型二进制文件,这样第三方的content provider使用者,可以访问不属于它权限的文件,通过content provider做代理。
    声明Content Provider

    创建Content Provider后,需要在manifest文件中声明,android系统才能知道它,当其他应用需要调用该Content Provider时才能创建或者调用它。

    导入已有的外部数据库

    我们平时见到的android数据库操作一般都是在程序开始时创建一个空的数据库,然后再进行相关操作。如果我们需要使用一个已有数据的数据库怎么办呢?
    我们都知道android系统下数据库应该存放在 /data/data/com.*.*(package name)/ 目录下,所以我们需要做的是把已有的数据库传入那个目录下。操作方法是用FileInputStream读取原数据库,再用FileOutputStream把读取到的东西写入到那个目录。
    把原数据库包括在项目源码的 res/raw 目录下,然后建立一个DBManager类,

    然后在程序的首个Activity中示例化一个DBManager对象,然后对其执行openDatabase方法就可以完成导入了,可以把一些要对数据库进行的操作写在DBManager类里,然后通过DBManager类的对象调用;也可以在完成导入之后通过一个SQliteDatabase类的对象打开数据库,并执行操作。

    我的做法是 在程序的首个Activity中导入数据库:

    此时在DDMS中可以查看到,外部数据库已经成功导入

    Service概念及用途

    Android中的服务,它与Activity不同,它是不能与用户交互的,不能自己启动的,运行在后台的程序,如果我们退出应用时,Service进程并没有结束,它仍然在后台运行,那 我们什么时候会用到Service呢?比如我们播放音乐的时候,有可能想边听音乐边干些其他事情,当我们退出播放音乐的应用,如果不用Service,我 们就听不到歌了,所以这时候就得用到Service了,又比如当我们一个应用的数据是通过网络获取的,不同时间(一段时间)的数据是不同的这时候我们可以 Service在后台定时更新,而不用每打开应用的时候在去获取。

    Service生命周期 

    Android Service的生命周期并不像Activity那么复杂,它只继承了onCreate(),onStart(),onDestroy()三个方法,当我们第一次启动Service时,先后调用了onCreate(),onStart()这两个方法,当停止Service时,则执行onDestroy()方法,这里需要注意的是,如果Service已经启动了,当我们再次启动Service时,不会在执行onCreate()方法,而是直接执行onStart()方法,具体的可以看下面的实例。

    ServiceActivity通信

    Service后端的数据最终还是要呈现在前端Activity之上的,因为启动Service时,系统会重新开启一个新的进程,这就涉及到不同进程间通信的问题了(AIDL)这一节我不作过多描述,当我们想获取启动的Service实例时,我们可以用到bindServiceonBindService方法,它们分别执行了ServiceIBinder()onUnbind()方法。

转载于:https://www.cnblogs.com/wshark/archive/2011/05/12/2044191.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值