- 浏览: 33271 次
- 性别:
- 来自: 南京
最新评论
-
chunjuan0126:
你说打电话是OutgoingCallBroadcaster.j ...
android打电话实现分析 -
fancyup:
学习了!!!
android打电话实现分析
我们平时在做android开发的时候,一定经常会接触到数据库操作,android使用sqlite作为它的本地数据库,并提供了一种叫做Content Provider的数据访问机制,简单来说,它就像一个web服务,有自己的URI,我们也是通过URI的形式来访问它的数据,通过这种形式的接口,使得我们的数据不仅在我们自己的应用中可以访问,甚至还可以被系统中的其他应用所调用。 一个典型的例子就是我们手机中的通讯录,android给我们暴露了一个接口,我们只要申请到相应的权限,通过访问这个接口,就可以得到通讯录的信息了。 说了这么多,现在言归正传,这篇文章主要是和大家分享一下Content Provider的实现方式,通过一些更加标准的代码架构,可以使我们的项目的效率更高,并且提高可维护性。
例如,我们有一个记事本程序,需要记录每一条记事,那么我们就需要这样一个数据表:
note
_id integer
content varchar(2000)
pubDate integer
为了说明问题,我们只位这个表设置了三个字段,分别使记录的id,记事内容,和编辑时间, 大家需要注意个是id字段需要在前面加一个下划线,否则在和ListView绑定的时候会出问题。
现在有了表接口,那么我们就可以将这张表抽象程一个数据结构,代码如下:
- public class NoteMetaData {
-
- public static final String AUTHORITY = "org.spring.provider.NoteProvider";
-
- public static final String DATABASE_NAME = "note.db";
-
- public static final int DATABASE_VERSION = 1;
-
-
- private NoteMetaData() {}
-
- public static final class NoteTableMetaData implements BaseColumns{
-
- private NoteTableMetaData() {};
-
- public static final String TABLE_NAME = "note";
-
- public static final String DEFAULT_SORT_ORDER = "_id desc";
-
- public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/notes");
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.spring.demo";
-
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.spring.demo";
-
- //integer
- public static final String NOTE_ID = "_id";
-
- //string
- public static final String NOTE_CONTENT = "content";
-
- //integer
- public static final String NOTE_PUB_DATE = "pubDate";
-
- }
-
- }
如上代码,我们定义好了我们需要的元数据,这个类首先定义了和ContentProvider相关的信息,AUTHORITY表示ContentProvider的URI,DATABASE_NAME和DATABASE_VERSION分别表示我们的数据库名和数据库版本。静态内部类NoteTableMetaData,表示我们刚才定义个表,首先TABLE_NAME表示表名,DEFAULT_SORT_ORDER 作为默认的排序规则,当然如果我们在查询中自定义了排序规则的话,这个就会被覆盖,CONTENT_URI定义了Provider对外访问的协议,CONTENT_TYPE,CONTENT_ITEM_TYPE用于表示两种uri模式的实体类型,这个其实很像我们http协议中的MIME类型。接下来的数据就是我们数据库字段的映射了,可以用注释标出他们的数据类型,这样,我们的元数据就完工拉,这个工作虽然有些枯燥,但是对于我们项目的可维护性还是很有帮助的,例如如果我们需要改动某个字段的名字,我们只需要修改元数据类就完成了所有的工作。
有了这些源数据,我们就可以开始来写ContentProvider了,首先,我们要定义一个默认的映射表,代码如下:
- public class NoteProvider extends ContentProvider{
- private static HashMap<String, String> noteProjectionMap;
-
- static {
-
- noteProjectionMap = new HashMap<String, String>();
-
- noteProjectionMap.put(NoteTableMetaData.NOTE_ID, NoteTableMetaData.NOTE_ID);
- noteProjectionMap.put(NoteTableMetaData.NOTE_CONTENT, NoteTableMetaData.NOTE_CONTENT);
- noteProjectionMap.put(NoteTableMetaData.NOTE_PUB_DATE, NoteTableMetaData.NOTE_PUB_DATE);
-
- }
- }
这个HashMap其实就相当于我们select语句中的别名,在后面的sqlite操作中会用到这个数据,一般情况下,我们不需要更改别名,所以在map中将键和值都设置为同样的就可以了。
由于我们的NoteProvider要处理两种形式的URI,所以我们需要一个机制来区分不同的URI,这就要用到UriMatcher,代码如下:
- private static UriMatcher matcher;
-
- private static final int QUERY_LIST = 1;
- private static final int QUERY_ITEM = 2;
-
- static {
-
- matcher = new UriMatcher(UriMatcher.NO_MATCH);
- matcher.addURI(NoteMetaData.AUTHORITY, "notes", QUERY_LIST);
- matcher.addURI(NoteMetaData.AUTHORITY, "notes/#", QUERY_ITEM);
-
- }
UriMatcher用来区分不同的URI,首先我们将它定义为一个静态属性,然后在静态初始化块中,使用它的addURI方法,为它添加了两个URI规则,并为每个规则设置了一个表示常量,这里有QUERY_LIST和QUERY_ITEM,而这个方法的前两个参数就是用来构造这个URI规则的,例如第一条规则中,第一个参数我们用到了元数据中的 AUTHORITY和一个notes字符串, 这样,这条规则最终就会成为这种形式org.spring.provider.NoteProvider/notes 而另外一条规则就是这样org.spring.provider.NoteProvider/notes/# ,注意到第二条规则中的井号,它是一个占位符,在实际的场景中,这个位置会用一个数字来代替。说了这么多,我们为什么要用两个URI来为这个Provider来提供接口呢,相信只要做过web开发的朋友就会知道,假如我们要做一个CRUD功能,我们首先需要一个页面来显示数据,这个页面是不接受ID参数的。但我们还需要有一个编辑功能的页面,而这个页面就需要接受一个ID来区分要编辑哪一条记录。这样就不难理解我们为什么要用两种URI了,这里的第一条URI规则,就相当于那个显示数据的页面,而第二个URI 中的井号的位置就相当于编辑页面中的ID。有了这个matcher后,我们就可以根据不同的URI开执行各自所需的操作。
现在Provider的基本信息已经基本完成了,因为我们的Provider需要和数据库进行交互,所以我们还需要一个中间层,可以使用SQLiteOpenHelper。
- private DatabaseHelper dbHelper;
-
- class DatabaseHelper extends SQLiteOpenHelper{
- DatabaseHelper(Context context) {
-
- super(context, NoteMetaData.DATABASE_NAME, null, NoteMetaData.DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
-
- db.execSQL(
-
- "create table " + NoteTableMetaData.TABLE_NAME + " ( "
- + NoteTableMetaData.NOTE_ID + " integer primary key, "
- + NoteTableMetaData.NOTE_CONTENT + " varchar(2000), "
- + NoteTableMetaData.NOTE_PUB_DATE + " integer"
- + ");"
-
- );
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-
- db.execSQL("drop table if exists " + NoteTableMetaData.TABLE_NAME);
- onCreate(db);
-
- }
-
-
- }
如上代码,我们扩展了自己的Helper,并将它定义为Provider 的私有属性,这个类的实现中我们还是使用元数据来进行操作,例如创建数据库和更新数据库的操作,字段名和表名使用的是元数据中的属性。
现在有了这些基础架构后,我们就可以实现相应的数据库操作方法了,先从query说起:
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
-
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-
- switch (matcher.match(uri)) {
-
- case QUERY_LIST:{
-
- qb.setTables(NoteTableMetaData.TABLE_NAME);
- qb.setProjectionMap(noteProjectionMap);
-
- }break;
-
- case QUERY_ITEM:{
-
- qb.setTables(NoteTableMetaData.TABLE_NAME);
- qb.setProjectionMap(noteProjectionMap);
- qb.appendWhere(NoteTableMetaData.NOTE_ID + "=" + uri.getPathSegments().get(1));
-
- }break;
-
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- String orderBy;
- if(TextUtils.isEmpty(sortOrder)) {
-
- orderBy = NoteTableMetaData.DEFAULT_SORT_ORDER;
-
- }else {
-
- orderBy = sortOrder;
- }
-
- SQLiteDatabase db = dbHelper.getWritableDatabase();
-
- Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
-
- c.setNotificationUri(getContext().getContentResolver(), uri);
- return c;
-
- }
这里使用了SQLiteQueryBuilder来构建数据库查询,这样可使我们从更抽象的层次来处理数据库交互,在这里我们之前定义的matcher就派上用场拉,我们判断了一下URI的类型,如果使QUERY_LIST,就查询所有的数据,而如果是QUERY_ITEM类型,就只查询特定ID下的记录。这一点在我们的代码中应该很明显,当然如果给出的URI不符合任意一条规则,那么就直接抛出异常。 接下来我判断了一下这次查询是否明确指定了排序规则,如果没指定就使用我们之前定一个默认规则来对数据进行排序,随后就是获取数据库引用,并执行插叙操作了。 这里要注意一下c.setNotificationUri方法,这个方法为当的URI注册了一下通知,简单来说就是这样,如果有其他的调用改变了底层数据库并发送了更改消息,那么这些注册了通知的URI会自动更新他们的数据集,而不用我们手动的进行刷新,这样可以省去很多繁琐的编码工作。最典型的例子就是为ListView绑定数据时,如果使用了这种机制,我们在修改数据库中的数据后,ListView 的显示也会自动的刷新。
接下来介绍一下insert方法:
- public Uri insert(Uri uri, ContentValues values) {
-
- if(matcher.match(uri) != QUERY_LIST) {
-
- throw new IllegalArgumentException("Unknown URI " + uri);
-
- }
-
-
-
- if(values.containsKey(NoteTableMetaData.NOTE_PUB_DATE) == false) {
-
- Long now = Long.valueOf(System.currentTimeMillis());
- values.put(NoteTableMetaData.NOTE_PUB_DATE, now);
-
- }
-
- SQLiteDatabase db = dbHelper.getWritableDatabase();
-
- long rowID = db.insert(NoteTableMetaData.TABLE_NAME, NoteTableMetaData.NOTE_CONTENT, values);
-
- if(rowID > 0) {
-
- Uri insertedUri = ContentUris.withAppendedId(NoteTableMetaData.CONTENT_URI, rowID);
- getContext().getContentResolver().notifyChange(insertedUri, null);
- return insertedUri;
-
- }
-
- throw new android.database.SQLException("Failed to insert row into " + uri);
- }
先说一下这个方法传进来的参数,第一个参数时调用的URI,接下来的values就相当于insert语句中的列名和值的一对组合,由于这个插入方法只接受不带ID的URI所以我们在一开始的时候进行了一下判断,然后我们检测了一下是否指定了日期,如果没有就以当前时间作为默认值,随后就是数据库调用了。 我们通过返回rowID来判断该条记录是否插入成功,如果成功就返回带着这条记录ID的URI,否则就抛出异常。注意到这里的notifyChange方法,这个正好和前面的注册通知相对应,它会通知所有注册的URI,数据库已经改变。
下面再来介绍一下update方法:
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
-
- SQLiteDatabase db = dbHelper.getWritableDatabase();
- int count;
-
- switch (matcher.match(uri)) {
-
- case QUERY_LIST:{
-
- count = db.update(NoteTableMetaData.TABLE_NAME, values,selection, selectionArgs);
-
- }break;
-
- case QUERY_ITEM:{
-
- String rowID = uri.getPathSegments().get(1);
- count = db.update(NoteTableMetaData.TABLE_NAME, values,
- NoteTableMetaData.NOTE_ID + "=" + rowID
- + (!TextUtils.isEmpty(selection) ? ("and ( " + selection + " ) ") : ""), selectionArgs);
-
- }break;
- default:
-
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- getContext().getContentResolver().notifyChange(uri, null);
- return count;
- }
这里个方法里的内容和前面很类似,唯一的区别就是通过URI来确定更新的方式。这一点在代码中写的也比较明白,所以就不赘述了。最后再介绍一下delete 方法:
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
-
- SQLiteDatabase db = dbHelper.getWritableDatabase();
- int count;
-
- switch (matcher.match(uri)) {
-
- case QUERY_LIST:{
-
- count = db.delete(NoteTableMetaData.TABLE_NAME, selection, selectionArgs);
-
- }break;
-
- case QUERY_ITEM:{
-
- String rowID = uri.getPathSegments().get(1);
- count = db.delete(NoteTableMetaData.TABLE_NAME,
- NoteTableMetaData.NOTE_ID + "=" + rowID +
- (!TextUtils.isEmpty(selection) ? (" and ( " + selection + " ) ") : "" ), selectionArgs);
-
- }break;
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- getContext().getContentResolver().notifyChange(uri, null);
- return count;
-
- }
这个方法也是判断两种不同的URI,如果时QUERY_LIST类型的URI,那么它就会删除所有的记录(当然,如果我们不需要这种操作,也可以忽略这种URI),另一种就是根据ID来删除相应的记录。这个方法应该也不难理解。 大家注意到,我们在这些方法中,大量的用到了我们元数据类中的信息,这样做的好处前面也说过了,隔离了底层的表结构后,让数据库结构的修改变得非常容易。
当然,我们还需要实现getType方法,来返回不同URI对应的MIME类型,这个信息,在我们的元数据中已有定义:
- @Override
- public String getType(Uri uri) {
-
- switch (matcher.match(uri)) {
-
- case QUERY_LIST:{
- return NoteTableMetaData.CONTENT_TYPE;
- }
-
- case QUERY_ITEM:{
- return NoteTableMetaData.CONTENT_ITEM_TYPE;
- }
- default:{
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- }
-
- }
好啦,到现在位置我们的Provider就已经实现好了,最后不要忘了在manifest中注册这个Provider:
- <provider android:name=".NoteProvider" android:authorities="org.spring.provider.NoteProvider" />
到此为止,我们的数据访问接口就实现完成了,我们可以用下面的方式很容易的进行数据访问:
- //插入数据
- ContentValues values = new ContentValues();
- values.put(NoteTableMetaData.NOTE_CONTENT, "test");
- Uri insertedUri = getContentResolver().insert(NoteTableMetaData.CONTENT_URI, values);
-
- //更新数据
- values.put(NoteTableMetaData.NOTE_CONTENT, "test updated");
- getContentResolver().update(Uri.withAppendedPath(NoteTableMetaData.CONTENT_URI, "/" + insertedUri.getPathSegments().get(1)), values, null, null);
-
- //查询数据
- Cursor c = getContentResolver().query(NoteTableMetaData.CONTENT_URI, null, null, null, null);
-
- //绑定ListView
- SimpleCursorAdapter adapter =
- new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, c,
- new String[] {"content" }, new int[] {android.R.id.text1});
-
- list.setAdapter(adapter);
-
- //删除数据
- getContentResolver().delete(insertedUri, null, null);
这篇文章主要是给大家提供了一种ContentProvider的架构方法,通过一些良好的代码组织,可以让我们的开发工作变得更加轻松,并且有效的增强应用的建壮性,对我们日常的开发还是很有帮助的。当然这种方法并不是唯一的,更不敢说这个是最好的。更希望它能够起到一种抛砖引玉的作用,大家可以在这个基础之上进一步的探索,找出更加适合自己的架构方式~
发表评论
-
获取Android SDK 源代码并在Eclipse中关联查看的方法--转
2012-04-16 08:57 1223在Google搜索“Android Eclipse 源代码”得 ... -
android 禁用屏蔽系统状态栏(statusbar) 转
2012-03-29 11:03 4511Object service = getSystemServi ... -
如何使Android应用程序获取系统权限(转)
2012-03-29 11:03 1273在 android 的API中有提供 ... -
LockScreen
2012-03-22 11:28 14001. 创建Service,注册Broadcast ... -
配置android jni开发环境
2012-03-22 11:27 1971以下是配置android jni开发环境的步骤。供参考。相关软 ... -
Android.mk写法
2012-03-22 11:26 918android编译系统的makefile文件Android.m ... -
windows 下 Android NDK开发配置(window + cygwin + NDK )转
2012-03-18 13:01 1268时间: 2011年6月11日 00:01 (星期六) ... -
Android中JNI编程的那些事儿
2011-10-12 17:19 852Android系统不允许一个纯粹使用C/C++的程序出现,它要 ... -
MMI Code Messages on Android
2011-08-30 11:12 1142MMI Code Messages on Android ... -
Android widget 之RemoteView
2011-08-25 17:23 9931.RemoteView概述 到目前为止,我发现Remote ... -
startManagingCursor
2011-08-25 16:10 1625总结一下Activity.startManagingCur ... -
Android上dip、dp、px、sp等单位说明
2011-08-24 16:11 630[ dip: device independent pixel ... -
Android Activity的四种LaunchMode
2011-08-18 12:39 998转自: http://dev.10086.cn/cmdn/wi ... -
MMS 源码目录结构
2011-08-17 15:55 757转自:http://gnibre.iteye.co ... -
Intents和Intent Filters(理论部分)
2011-08-17 15:51 752<转>http://www.cnblogs.com ... -
android打电话实现分析
2011-08-17 15:48 66481 OutgoingCallBroadcaster.java ... -
Android UI 界面绘制原理分析
2011-08-17 14:43 2797View类包含Surface(变量 ...
相关推荐
6. **内容提供者(Content Provider)**: 虽然不是框架的必需部分,但内容提供者是Android中用于在不同应用间共享数据的标准接口。如果框架集成内容提供者,可以使得数据更容易地被其他应用访问。 7. **LiveData和...
在Android开发中,Content Provider是一种核心组件,它允许应用程序之间共享数据。本项目"notes-app-content-provider"展示了如何使用Content Provider结合SQLite数据库来构建一个笔记应用。以下是关于这个项目的...
在Android程序开发中,源码高仿是一种常见的学习方法,通过分析系统自带应用的源代码,开发者可以深入了解Android系统的运行机制以及如何实现特定功能。本资料包“Android程序研发源码高仿基于android2.2(Froyo)的...
5. **数据存储**:Android提供了多种数据存储方式,包括Shared Preferences(轻量级键值对存储)、SQLite数据库(结构化数据存储)、Internal/External Storage(文件系统存储)和Content Provider(跨应用数据共享...
源代码可能会涉及其中一种或多种,展示如何读写数据。 4. **服务(Service)**:服务在后台运行,不与用户交互。源码可能会包含自定义服务的实现,用于执行长时间任务,如音乐播放或后台数据同步。 5. **...
Content Provider是Android系统提供的一种机制,它允许不同的应用程序之间进行数据交换,打破了应用程序之间的数据隔离。通过Content Provider,一个应用可以将自己的数据暴露给其他应用,同时也可以访问其他应用...
它是Android四大组件之一,提供了一种标准的方式来访问和操作数据。开发者可以通过实现ContentProvider类,定义URI和CRUD操作,使其他应用能够安全地访问备忘录数据。 4. **通知与提醒**:为了让用户不会错过重要...
Android是一种开源的操作系统,主要用于移动设备,如智能手机和平板电脑。它的开发主要基于Java和Kotlin语言,使用Android Studio作为集成开发环境(IDE)。Android应用程序由多个组件构成,包括活动(Activity)、...
5. **Intent**:Intent是Android中的一种消息传递机制,用于启动组件间的通信,如启动活动或服务。 6. **生命周期**:活动和其他组件有各自的生命周期,开发者需要理解这些生命周期方法,如onCreate()、onStart()、...
5. **数据绑定**:如果需要显示从Content Provider获取的日历事件,你可以结合使用Android Data Binding或MVVM架构来绑定数据到日历视图上,使其动态更新。 通过以上内容,你应该对如何在Android应用中实现日历功能...
MVP(Model-View-Presenter)是一种设计模式,常用于Android应用开发,以提高代码的可测试性和可维护性。在MVP中,模型(Model)负责数据处理和业务逻辑,视图(View)处理用户界面交互,而 presenter则作为模型和视图...
在开发基于Android的通讯录应用时,主要涉及的是应用程序框架层,其中包括Content Provider,它是Android提供的一种机制,用于在不同应用程序之间共享数据。 二、Content Provider Content Provider是Android四大...
Content Provider是Android系统中用于数据共享的一种机制,它可以让你的应用访问其他应用的数据,或者让其他应用访问你的应用数据。在Android的短信应用中,短信数据存储在一个特殊的Content Provider中,即`SMS_...
4. **Content Provider**:在Android中,Content Provider是数据共享的机制,使得不同应用可以访问SQLite数据库。在记事本应用中,Content Provider封装了数据库操作,提供统一的数据访问接口。 5. **Intent**:...
1. 数据库管理:Android支持SQLite数据库,这是一种轻量级的关系型数据库,适用于移动设备。源代码中可能会包含SQLiteOpenHelper类的使用,用于创建、升级数据库以及执行SQL查询。 2. Content Provider:作为...
以Binder驱动为例,它是一种基于Linux的IPC(进程间通信)机制,是Android系统中用于进程通信的关键组件之一。Binder驱动使得不同进程之间可以通过代理和远程过程调用(RPC)实现高效通信。Binder机制不仅提供了高效...
3. **Broadcast Receiver**:广播接收者是Android系统中的一种组件,用于接收系统或应用发送的广播消息。通过Broadcast Receiver,应用可以在后台监听特定事件,如网络状态改变、系统启动等,并作出相应反应。 4. *...
Android内置了SQLite数据库,这是一种轻量级的关系型数据库,适合移动设备上的本地数据存储。我们需要创建数据库表,定义字段,如收支金额、日期、类别等,并实现数据的增删改查操作。此外,为了提高用户体验,可以...
源代码中可能包含了SQL语句的编写及数据库操作的封装,掌握SQLite基本操作和内容提供者(Content Provider)的概念是必要的。 6. **缓存策略**:为了提高用户体验,ECShop可能会实现缓存策略,如内存缓存和磁盘缓存,...