24、Android应用-找一个朋友 (整合SQLite/Content Provider/Google Maps)

创建一个应用程序,你会在 Google 地图上使用 SQLite 数据库和
Google 地图 Overlays 来绘制数据记录。


  应用程序:找一个朋友


关键技能  &  概念
●  创建一个 SQLite 数据库
●  创建一个定制内容提供者
●  从数据库检索条目并且传递到一个 Google  Maps  Overlay146
这是你将创建应用程序的最后一章,但是会是本书介绍的最大的一个应用程序。
我将介绍一些到目前为止没有遇到过的话题,而且你会用到这些话题所谈到的技
能创建一个非常健全的应用程序。
在本章中,会学习如何在 Android 模拟器中创建 SQLite 数据库。我会向你展示
如何在定制的数据库中读取,写入并且输出数据。这个过程包括创建并使用你自
己的 Content  Provider 来和数据库一同工作。然后,你拿取存储在数据库中的
数据并写入到 Google  Maps  Overlay 中。当你在前一章用 Google 地图时,还没
有使用 Overlay。使用 Google  Maps  Overlays 在地图上绘制形状并且写文本,
得到一个有信息量的地图。在这个项目中,将创建一个两部分应用程序。应用程
序的第一部分将允许用户输入“friends”到移动数据库中。(一个 friend 由姓
名和地理坐标位置组成)。用户将能增加,修改并且删除 friends.
第二个部分将包括一个菜单项目。当用户选择这个菜单项目,应用程序将显示一
个 Google 地图。这个 Google 地图和第九章创建的 Google 地图不同之处就是,
这个地图会包含一个 Google  Maps  Overlay,它会允许你在 Google 地图的标题
处写入姓名,给定信息并且绘制项目。
要开始,在 Eclipse 内创建一个新的 Android 项目,命名为 FindFriend,使用
下图的设定(略)。
现在,你应该对创建一个 Android 应用程序非常的熟悉了,创建本项目会需要一
点小小的帮助。Google 在 Android  SDK 里有一个应用程序叫 NotePad,非常简单
但是允许你储存,修改并且删除数据库里的“notes”。你会去修改这个例子的一
些代码来创建 Friends 数据库。
如果你想要知道 Google  NotePad 如何工作,在继续之前,在 Eclipse 中装载项
目并且在模拟器中运行它。不久将会开始修改这个代码,但是首先,在下一节里 ,
将创建你的第一个 SQLite 数据库。
创建一个 SQLite 数据库
创建一个 SQLite       SQLite       SQLite       SQLite 数据库  第十一章(2)  (2)  (2)  (2)
Android 设备将发布时会有一个内部的 SQLite 数据库。这个数据库的目的是允
许用户和开发者一个可以在活动中储存信息的地方。
如果你用过 Microsoft  SQL 服务器或者 SQLite,使用 Andorid 的 SQLite 数据库
的结构和过程对你将不会陌生。不管你有多少的经验,这个部分将涵盖所有需要
创建和使用全功能 SQlite 数据库的技能。你将要在 Android 模拟器上创建一个
数据库。要实现这个,你需要进入 Android  SDK 命令行编辑器工具并使用 shell
命令来进入 Android 服务器。
提示
参考第三章来重拾你的记忆关于路径声明和使用命令行工具。
一旦你进入服务器,你需要导航到数据库的位置。所有的 Android  SQLite 数据
库的位置是在 data/data/<package>/147
databases  目录。使用 cd 命令来从当前的目录改变到 data 目录,并且再到
<package>目录。如果你不确定<package>目录的名称,使用 ls 来列出文件和当
前目录。改变目录至<package>android_programmers_
guide.FindAFriend,如下所示(略)
警告
如果你没有 android_programmers_guide.FindAFriend 目录,按照前一部分描述
的方式创建你的应用程序并且运行“Hello  World!”默认的由项目创建的应用程
序,那样会确保你有个正确的目录。
找到 android_programmers_guide.FindAFriend 目录后,运行 Is 命令。这个命
令列出特定文件夹内所有的文件和目录。改命令应当返回空的内容。因为,此时
在该目录内没有文件和文件夹。
假定 SQLite 数据库必须在本目录下的一个数据库目录内,是时候来创建一个了。
mkdir 工具为你创建目录。因此,运行 mkdir  databases 命令。它将创建保留数
据库的目录。
警告
现在,你几乎是在服务器的根目录上。因此你刚刚创建的目录将被作为根目录进
入。当你运行活动时,可能会出问题,因为每一个活动有一个不同的用户。出于
开发的目的,要解决这个问题,运行 chmod  777  databases 来准许每个人都能进
入到数据库目录。将来,你必须对给予每个人的权力到一些敏感的 Android 条目
非常谨慎才行。只给予特定的用户需要使用特定条目的权力。
已经创建了数据库目录了,可以创建数据库了。使用 cd 命令导航到数据库目录。
在数据库目录后,使用 sqlite3  工具来创建数据库并命名它为 friends.db,如
下:
如果执行命令成功,你应当能看到一个 SQLite3 版本信息,本例是 3.5.0,和一
个  SQLite3  prompt—sqlite>。这说明数据库已被建立但是是空的。数据库没有
包含表格和数据。记住,下一步是为活动数据创建一个表格。
你  需  要  创  建  一  个  名  为   friends   的  表  格  。  这  个  表  将  保  留
id,name,location,created,  和  modified  字段。这些字段将为你的项目提供足
够的信息。
提示
如果你对 SQLite 不熟悉,一个 SQLite 命令必须以分号结束。如果你想要跨越一
个命令这个会有帮助。没有终止 SQLite 命令的情况下,按下 ENTER 键会继续给
你一个提示符,…>。你不能在提示符继续输入命令,除非你使用分号。一旦分
号被使用,SQLite 将把连续的命令作为一个完整的命令。
要在数据库内创建 friends 表格,在 sqlite>提示符输入下列命令:
#  sqlite  friends.db
CREATE  TABLE  friends  (_id  INTEGER  PRIMARY  KEY,  name  TEXT,  location  TEXT,
created  INTEGER,  modified  INTEGER);148
如果命令执行成功,将返回到 sqlite>提示符,如下图所示(略)。
数据库现在可以被使用了,你可以退出 SQLite 了。使用.exit 来退出。然后可
以退出 shell 部分返回到 Eclipse.
创建数据库是创建应用程序的第一步。现在数据库和相应的表格已经被创建,你
需要一个方法来存储数据。受雇 Android 数据的存储方式是一个 Content
Provider 。 下 面 的 部 分 带 你 走 进 如 何 为 新 数 据 库 创 建 一 个 定 制 Content
Provider 并存储数据。
创建一个定制的 Content  Provider
创建一个定制的 Content          Content         Content          Content Provider          Provider          Provider          Provider 第十一章(3)  (3)  (3)  (3)
Android 使用 Content  Provider 来存 储 数 据 。你 在第 九 章使 用 过 Content
Provider 存储并从一个 GPS 中读取坐标信息。同样的过程应用于数据库。有这
样一些 Content  Provider  Contact  Lists,IMs,和  Recent
Calls。总之,没有为你数据库准备的 Content  Provider。Android 是非常之灵
活并且允许你为定制的数据创建定制的 Content  Provider。在本节,将创建一
个和 Friends 数据工作的 Content  Provider。这是存取 friend 数据和最终在屏
幕上显示它们的关键所在。
下一节,让我们来编辑 string.xml 文件。这个文件保留全局遍及活动的字符串
内容。
编辑 string.xml 文件
首先,为项目编辑 string.xml 文件。string.xml 文件由每个项目创建但是你还
没有使用过它。这个文件保留可以在活动中使用的静态字符串。
通常,你不大可能在写这些字符串之前仔细查看所有的部分。那就是,当你构造
活动时,你通常增加到 string.xml 的入口。因为那样会打断本书的流程,所以
我预先给你所有 string.xml 文件的所有内容。编辑 string.xml 文件使它看上去
像这样:
<?xml  version="1.0"  encoding="utf-8"?>
<resources>
<string  name="app_name">FindAFriend</string>
<string  name="menu_delete">Delete</string>
<string  name="menu_insert">Add  Friend</string>
<string  name="find_friends">Find  Friends</string>
<string  name="menu_revert">Revert</string>
<string  name="menu_discard">Discard</string>
<string  name="resolve_edit">Edit  location</string>
<string  name="resolve_title">Edit  name</string>
<string  name="title_create">Create  Friend</string>
<string  name="title_edit">Edit  Friend</string>149
完成 string.xml 文件后,需要创建一个.java 文件来保留你的代码。应该命名
这个文件为 FriendsProvider.java。还要创建另外一个.java 文件来保留数据定
义。命名这个文件为 Friends.java,因为它会定义一个 Friends 数据结构像什
么样子并且允许你的 Content  Provider 正常进入。(因为 provider 将会是项目
中的一个类,没有必要来构建一个相应的.xml 布局文件)。
提示
技术上,对于应用程序,你定制的 Content  Provider 不需要定居在同一项目或
包装内作为代码的剩余部分。为了简单明了,我在 FindAFriend 项目里做了一个
类。假如你计划创建一个可能用户多重项目的 Content  Provider,在单独的包
装中创建它吧。这样,当你只想要使用 Content  Provider 时,会允许你呼叫一
个包装。
让我们开始 Friends.java 文件。你只要为相关的类需要输入两个包装:
BaseColumns 将被一个从主 Friends 类中的 subclass 执行。命名这个 subclass
为 Friend,因为它代表 Friends 数据集中的一个 friend。下面的代码显示如何
设置类的概要:
这个类将保留一下静态变量,它们定义 Friends 数据库中的每一列,Content
URI,和记录的默认排序。
提示
Content  URI 被用于识别将要处理的内容。这个数值必须唯一。
需要定义的字符串如下:
<string  name="title_notes_list">Friends</string>
<string  name="title_note">Location</string>
<string  name="title_edit_title">Friend  Name:</string>
<string  name="button_ok">OK</string>
<string  name="error_title">Error</string>
<string  name="error_message">Error  loading  note</string>
</resources>
import  android.net.Uri;
import  android.provider.BaseColumns;
public  final  class  Friends  {
public  static  final  class  Friend  implements  BaseColumns  {
}
}
public  static  final  Uri  CONTENT_URI
=
Uri.parse("content://android_programmers_guide.FindAFriend.Friends/friend");
public  static  final  String  DEFAULT_SORT_ORDER  =  "modified  DESC";
public  static  final  String  NAME  =  "name";150
有了变量的设定以后,Friends 类放在一起应该是这样的:
创建 Content  Provider
创建 Content          Content         Content          Content Provider          Provider          Provider          Provider 第十一章(4)  (4)  (4)  (4)
使用 Eclipse 打开将会成为项目 Content  Provider 的 FriendsProvider.java
文件。你将要在活动中使用这个定制的 Content  Provider 来从 Friends 数据库
中检索数据。
和往常一样。让我们从导入文件开始。你需要输入 Friends 类和一些其它的类:
如你所见,你输入类的大多数和 SQL 打交道。当你用这些包装的时候,我再向你
解释。
public  static  final  String  LOCATION  =  "location";
public  static  final  String  CREATED_DATE  =  "created";
public  static  final  String  MODIFIED_DATE  =  "modified";
package  android_programmers_guide.FindAFriend;
import  android.net.Uri;
import  android.provider.BaseColumns;
public  final  class  Friends  {
public  static  final  class  Friend  implements  BaseColumns  {
public  static  final  Uri  CONTENT_URI
=
Uri.parse("content://android_programmers_guide.FindAFriend.Friends/friend");
public  static  final  String  DEFAULT_SORT_ORDER  =  "modified  DESC";
public  static  final  String  NAME  =  "name";
public  static  final  String  LOCATION  =  "location";
public  static  final  String  CREATED_DATE  =  "created";
public  static  final  String  MODIFIED_DATE  =  "modified";
}
}
import  android_programmers_guide.FindAFriend.Friends;
import  android.content.*;
import  android.database.Cursor;
import  android.database.SQLException;
import  android.database.sqlite.SQLiteOpenHelper;
import  android.database.sqlite.SQLiteDatabase;
import  android.database.sqlite.SQLiteQueryBuilder;
import  android.net.Uri;
import  android.text.TextUtils;
import  android.util.Log;
import  java.util.HashMap;151
第一个将要使用的包装是 android.content。要成为一个 Content  Provider,需
要利用并优先要求的方法,你的 FriendsProvider 类需要扩展 ContentProvider。
看一下下面类的概要,它们包括一些将要使用在 Provider 的变量定义:
Content  Provider 包含一些需要优先的方法,包括 onCreate(  ),  query(  ),
insert(  ),  delete(  ),  和 update(  )。因为这些方法将被活动使用 Content
Provider 呼叫,你必须优先使用它们来进入 Friends  数据库。
你将优先 onCreate(  )  方法呼叫一个 SQLiteOpenHelper。因此,在能优先
ContentProvider  的  onCreate() 之 前 , 你 不 得 不 创 建 一 个 类 扩 展
SQLiteIpenHelper。
代码块跟从的是一个 Content  Provider 的子类。它扩展 SQLiteOpenHelper:
刚创建的 DatabaseHelper 类包含两个优先方法:onCreater()和 onUpgrade()。
onCreate()方法被用于当从代码中创建数据库,或者表格定义不存在的示例中。
注意
假 定 你 从 adb 壳 中 创 建 数 据 库 结 构 , 你 不 会 依 赖 于 DatabaseHelper 的
public  class  FriendsProvider  extends  ContentProvider  {
private  SQLiteDatabase  mDB;
private  static  final  String  TAG  =  "FriendsProvider";
private  static  final  String  DATABASE_NAME  =  "friends";
private  static  final  int  DATABASE_VERSION  =  2;
private  static  HashMap<String,  String>  FRIENDS_PROJECTION_MAP;
private  static  final  int  FRIENDS  =  1;
private  static  final  int  FRIENDS_ID  =  2;
private  static  final  UriMatcher  URL_MATCHER;}
private  static  class  DatabaseHelper  extends  SQLiteOpenHelper  {
@Override
public  void  onCreate(SQLiteDatabase  db)  {
db.execSQL("CREATE  TABLE  friends  (_id  INTEGER  PRIMARY  KEY,"
+  "name  TEXT,"  +  "location  TEXT,"  +  "created  INTEGER,"
+  "modified  INTEGER"  +  ");");
}
@Override
public  void  onUpgrade(SQLiteDatabase  db,  int  oldVersion,  int
newVersion)  {
Log.w(TAG,  "Upgrading  database  from  version  "  +  oldVersion  +  "to  "
+  newVersion  +  ",  which  will  destroy  all  old  data");
db.execSQL("DROP  TABLE  IF  EXISTS  friends");
onCreate(db);
}
}152
onCreate()方法来建立你的数据库。
DatabaseHelper 类创建后,你现在可以为 Content  Provider 优先 onCreate()
方法了:
这是个非常之简单的方法,结果就是返回一个布尔值代表你的数据库是否可以被
打开。你使用在兄弟类中创建的 SQLiteOpenHelper 来打开 Friends 数据库。注
意你传递数据库名称到 DatabaseHelper 类。如果数据库对象——mDB 返回的不
是 null,然后数据库就成功的被打开并可以查询了。
下一步,优先 ContentProvider 类的 query()方法。这将是 Content  Provider
的主要部分。query()方法是从活动通过 Content  Provider 被呼叫来从数据库
中获得记录。看下优先版本的 query()方法代码:
@Override
public  boolean  onCreate()  {
DatabaseHelper  dbHelper  =  new  DatabaseHelper();
mDB  =  dbHelper.openDatabase(getContext(),  DATABASE_NAME,  null,
DATABASE_VERSION);
return  (mDB  ==  null)  ?  false  :  true;
}
@Override
public  Cursor  query(Uri  url,  String[]  projection,  String  selection,
String[]  selectionArgs,  String  sort)  {
SQLiteQueryBuilder  qb  =  new  SQLiteQueryBuilder();
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:
qb.setTables("friends");
qb.setProjectionMap(FRIENDS_PROJECTION_MAP);
break;
case  FRIENDS_ID:
qb.setTables("friends");
qb.appendWhere("_id="  +  url.getPathSegments().get(1));
break;
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
String  orderBy;
if  (TextUtils.isEmpty(sort))  {
orderBy  =  Friends.Friend.DEFAULT_SORT_ORDER;
}  else  {
orderBy  =  sort;
}
Cursor  c  =  qb.query(mDB,  projection,  selection,  selectionArgs,  null,153
query()方法做了一点家务管理之类的事宜,通过检查传递到其中的数据库 URL
的有效性和定义一个查询分类序列达到的。URL 检查是为了确保你只是要进入
Friends 数据库。如果你试图从其它活动进入数据库,或者从其它的 Content
Provider,query()方法投递一个例外。
到方法的结尾,你使用 SQLiteQueryBuilder 来执行一个查询。通过下面的代码,
导致的数据集被赋值到一个光标:
注意
光标是一个设备允许你移动记录并从数据列中返回信息。
update(  ),delete(  ),  和 insert(  )  方法同样的简单。看一下这三个方法,
应当优先它们:
null,  orderBy);
c.setNotificationUri(getContext().getContentResolver(),  url);
return  c;
}
Cursor  c  =  qb.query(mDB,  projection,  selection,  selectionArgs,  null,
null,  orderBy);
@Override
public  Uri  insert(Uri  url,  ContentValues  initialValues)  {
long  rowID;
ContentValues  values;
if  (initialValues  !=  null)  {
values  =  new  ContentValues(initialValues);
}  else  {
values  =  new  ContentValues();
}
if  (URL_MATCHER.match(url)  !=  FRIENDS)  {
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
Long  now  =  Long.valueOf(System.currentTimeMillis());
Resources  r  =  Resources.getSystem();
if  (values.containsKey(Friends.Friend.CREATED_DATE  )  ==  false)  {
values.put(Friends.Friend.CREATED_DATE,  now);
}
if  (values.containsKey(Friends.Friend.MODIFIED_DATE)  ==  false)  {
values.put(Friends.Friend.MODIFIED_DATE,  now);
}
if  (values.containsKey(Friends.Friend.NAME)  ==  false)  {
values.put(Friends.Friend.NAME,
r.getString(android.R.string.untitled));154
}
if  (values.containsKey(Friends.Friend.LOCATION)  ==  false)  {
values.put(Friends.Friend.LOCATION  ,  "");
}
270  Android:  A  Programmer’s  Guide
rowID  =  mDB.insert("friends",  "friend",  values);
if  (rowID  >  0)  {
Uri  uri  =  ContentUris.withAppendedId(Friends.Friend.CONTENT_URI
,  rowID);
getContext().getContentResolver().notifyChange(uri,  null);
return  uri;
}
throw  new  SQLException("Failed  to  insert  row  into  "  +  url);
}
@Override
public  int  delete(Uri  url,  String  where,  String[]  whereArgs)  {
int  count;
long  rowId  =  0;
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:
count  =  mDB.delete("friends",  where,  whereArgs);
break;
case  FRIENDS_ID:
String  segment  =  url.getPathSegments().get(1);
rowId  =  Long.parseLong(segment);
count  =  mDB
.delete("friends",  "_id="
+  segment
+  (!TextUtils.isEmpty(where)  ?  "  AND  ("  +  where
+  ')'  :  ""),  whereArgs);
break;
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
getContext().getContentResolver().notifyChange(url,  null);
return  count;
}
@Override
public  int  update(Uri  url,  ContentValues  values,  String  where,  String[]
whereArgs)  {
int  count;
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:
count  =  mDB.update("friends",  values,  where,  whereArgs);155
这些方法的代码应当不需要再加以说明了。如果看过在每个方法的处理,代码的
核心就是发出一个数据库声明来执行要求的动作——更新,删除,或者插入。
最后的 Content  Provider 的部分就是一个 getType()方法,它返回 Friends 数
据类型。当创建自己的类型,应当跟从一下协议:
这样就完成了新的定制的 Content  Provider。看一下完成的 FriendsProvider
代码:
break;
case  FRIENDS_ID:
String  segment  =  url.getPathSegments().get(1);
count  =  mDB
.update("friends",  values,  "_id="  +  segment
Chapter  11:  Application:  Find  a  Friend  271
+  (!TextUtils.isEmpty(where)  ?  "  AND  ("  +  where  +  ')'  :  ""),  whereArgs);
break;
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
getContext().getContentResolver().notifyChange(url,  null);
return  count;
}
vnd.android.cursor.dir/vnd.<package>
Take  a  look  at  the  getType(  )  method:
@Override
public  String  getType(Uri  url)  {
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:
return
"vnd.android.cursor.dir/vnd.android_programmers_guide.friend";
case  FRIENDS_ID:
return
"vnd.android.cursor.item/vnd.android_programmers_guide.friend";
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
}
package  android_programmers_guide.FindAFriend;
import  android_programmers_guide.FindAFriend.Friends;
import  android.content.*;
import  android.database.Cursor;
import  android.database.SQLException;
import  android.database.sqlite.SQLiteOpenHelper;156
import  android.database.sqlite.SQLiteDatabase;
import  android.database.sqlite.SQLiteQueryBuilder;
import  android.net.Uri;
import  android.text.TextUtils;
import  android.util.Log;
import  java.util.HashMap;
public  class  FriendsProvider  extends  ContentProvider  {
private  SQLiteDatabase  mDB;
private  static  final  String  TAG  =  "FriendsProvider";
private  static  final  String  DATABASE_NAME  =  "friends";
private  static  final  int  DATABASE_VERSION  =  2;
private  static  HashMap<String,  String>  FRIENDS_PROJECTION_MAP;
private  static  final  int  FRIENDS  =  1;
private  static  final  int  FRIENDS_ID  =  2;
private  static  final  UriMatcher  URL_MATCHER;
private  static  class  DatabaseHelper  extends  SQLiteOpenHelper  {
@Override
public  void  onCreate(SQLiteDatabase  db)  {
db.execSQL("CREATE  TABLE  friends  (_id  INTEGER  PRIMARY  KEY,"
+  "name  TEXT,"  +  "location  TEXT,"  +  "created  INTEGER,"
+  "modified  INTEGER"  +  ");");
}
@Override
public  void  onUpgrade(SQLiteDatabase  db,  int  oldVersion,  int
newVersion)  {
Log.w(TAG,  "Upgrading  database  from  version  "  +  oldVersion  +  "to  "
+  newVersion  +  ",  which  will  destroy  all  old  data");
db.execSQL("DROP  TABLE  IF  EXISTS  friends");
onCreate(db);
}
}
@Override
public  boolean  onCreate()  {
DatabaseHelper  dbHelper  =  new  DatabaseHelper();
mDB  =  dbHelper.openDatabase(getContext(),  DATABASE_NAME,  null,
DATABASE_VERSION);
return  (mDB  ==  null)  ?  false  :  true;
}
@Override
public  Cursor  query(Uri  url,  String[]  projection,  String  selection,
String[]  selectionArgs,  String  sort)  {
SQLiteQueryBuilder  qb  =  new  SQLiteQueryBuilder();
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:157
qb.setTables("friends");
qb.setProjectionMap(FRIENDS_PROJECTION_MAP);
break;
case  FRIENDS_ID:
qb.setTables("friends");
qb.appendWhere("_id="  +  url.getPathSegments().get(1));
break;
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
String  orderBy;
if  (TextUtils.isEmpty(sort))  {
orderBy  =  Friends.Friend.DEFAULT_SORT_ORDER;
}  else  {
orderBy  =  sort;
}
Cursor  c  =  qb.query(mDB,  projection,  selection,  selectionArgs,  null,
null,  orderBy);
c.setNotificationUri(getContext().getContentResolver(),  url);
return  c;
}
@Override
public  String  getType(Uri  url)  {
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:
return
"vnd.android.cursor.dir/vnd.android_programmers_guide.friend";
case  FRIENDS_ID:
return
"vnd.android.cursor.item/vnd.android_programmers_guide.friend";
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
}
@Override
public  Uri  insert(Uri  url,  ContentValues  initialValues)  {
long  rowID;
ContentValues  values;
if  (initialValues  !=  null)  {
values  =  new  ContentValues(initialValues);
}  else  {
values  =  new  ContentValues();
}
if  (URL_MATCHER.match(url)  !=  FRIENDS)  {158
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
Long  now  =  Long.valueOf(System.currentTimeMillis());
Resources  r  =  Resources.getSystem();
if  (values.containsKey(Friends.Friend.CREATED_DATE  )  ==  false)  {
values.put(Friends.Friend.CREATED_DATE,  now);
}
if  (values.containsKey(Friends.Friend.MODIFIED_DATE)  ==  false)  {
values.put(Friends.Friend.MODIFIED_DATE,  now);
}
if  (values.containsKey(Friends.Friend.NAME)  ==  false)  {
values.put(Friends.Friend.NAME,
r.getString(android.R.string.untitled));
}
if  (values.containsKey(Friends.Friend.LOCATION)  ==  false)  {
values.put(Friends.Friend.LOCATION  ,  "");
}
rowID  =  mDB.insert("friends",  "friend",  values);
if  (rowID  >  0)  {
Uri  uri  =  ContentUris.withAppendedId(Friends.Friend.CONTENT_URI
,  rowID);
getContext().getContentResolver().notifyChange(uri,  null);
return  uri;
}
throw  new  SQLException("Failed  to  insert  row  into  "  +  url);
}
@Override
public  int  delete(Uri  url,  String  where,  String[]  whereArgs)  {
int  count;
long  rowId  =  0;
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:
Chapter  11:  Application:  Find  a  Friend  275
count  =  mDB.delete("friends",  where,  whereArgs);
break;
case  FRIENDS_ID:
String  segment  =  url.getPathSegments().get(1);
rowId  =  Long.parseLong(segment);
count  =  mDB
.delete("friends",  "_id="
+  segment
+  (!TextUtils.isEmpty(where)  ?  "  AND  ("  +  where
+  ')'  :  ""),  whereArgs);
break;159
有了最根本的数据素材(数据库,定义和 Content  Provider),你可以开始来建
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
getContext().getContentResolver().notifyChange(url,  null);
return  count;
}
@Override
public  int  update(Uri  url,  ContentValues  values,  String  where,  String[]
whereArgs)  {
int  count;
switch  (URL_MATCHER.match(url))  {
case  FRIENDS:
count  =  mDB.update("friends",  values,  where,  whereArgs);
break;
case  FRIENDS_ID:
String  segment  =  url.getPathSegments().get(1);
count  =  mDB
.update("friends",  values,  "_id="  +  segment
+  (!TextUtils.isEmpty(where)  ?  "  AND  ("  +  where  +  ')'  :  ""),  whereArgs);
break;
default:
throw  new  IllegalArgumentException("Unknown  URL  "  +  url);
}
getContext().getContentResolver().notifyChange(url,  null);
return  count;
}
static  {
URL_MATCHER  =  new  UriMatcher(UriMatcher.NO_MATCH);
URL_MATCHER.addURI("android_programmers_guide.FindAFriend.Friends",
"friend",  FRIENDS);
URL_MATCHER.addURI("android_programmers_guide.FindAFriend.Friends",
"friend/#",  FRIENDS_ID);
FRIENDS_PROJECTION_MAP  =  new  HashMap<String,  String>();
FRIENDS_PROJECTION_MAP.put(Friends.Friend._ID,  "_id");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.NAME,  "name");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.LOCATION,  "location");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.CREATED_DATE,  "created");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.MODIFIED_DATE,
"modified");
}
}160
造周围的活动。记住,活动将使用数据库内的数据,显示到列表,并允许用户启
动另一个活动来放置数据库条目到一个 Google  Maps  Overlay。在下一节,将构
建活动并完成 FindFriend 应用程序。
创建 FindAFriend 活动
创建 FindAFriend FindAFriend FindAFriend FindAFriend 活动  第十一章(5)  (5)  (5)  (5)
如果你花了些时间运行 Google 的 NotePad 示例,那么你就对活动的布局非常熟
悉 了 。 你 将 修 改 NotePad 的 接 口 来 使 用 Friends 数 据 库 和 Google 地 图 。
FindAFriend 活动将和一些小的活动交互:NameEditor,  LocationEditor,  和
FriendsMap。下面的章节中你将构建所有的这些活动。
注意
除了 NotePad,Google 提供了一些写的非常好的示例活动,展示了多编程状态的
基本技术。
如你所做的过去的几个活动,从文件 AndroidManifest.xml 开始。要想熟悉复杂
的应用程序,你需要多次更改 AndroidManifest.xml 文件。
编辑  AndroidManifest.xml
看看下面为 FindAFriend 项目准备的 AndroidManifest.xml 文件。需要为新活动
增加一些 Intent 过滤器,包括一个编辑 friend 的姓名,并且启动你的 Google
地图。
同样,需要仔细注意每个 Intent 过滤器的动作。这些动作会被传递到每个活动
并处理 Intent。最后,不要忘记增加 Access_Location  and  Access_GPS  许 可 ,
这样就可以增加你的当前闻之了。完整的 AndroidManifest.xml 文件应当像这样
显示:
<?xml  version="1.0"  encoding="utf-8"?>
<manifest  xmlns:android="http://schemas.android.com/apk/res/android"
package="android_programmers_guide.FindAFriend">
<application  android:icon="@drawable/icon">
<provider  android:name="FriendsProvider"
android:authorities="android_programmers_guide.FindAFriend.Friends"  />
<activity  android:name=".FindAFriend"
android:label="@string/app_name">
<intent-filter>
<action  android:name="android.intent.action.MAIN"  />
<category  android:name="android.intent.category.LAUNCHER"  />
</intent-filter>
<intent-filter>
<action  android:name="android.intent.action.VIEW"  />
<action  android:name="android.intent.action.EDIT"  />
<action  android:name="android.intent.action.PICK"  />161
<category  android:name="android.intent.category.DEFAULT"  />
<dataandroid:mimeType="vnd.android.cursor.dir/
vnd.android_programmers_guide.friend"  />
</intent-filter>
<intent-filter>
<action  android:name="android.intent.action.GET_CONTENT"  />
<category  android:name="android.intent.category.DEFAULT"  />
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend"  />
</intent-filter>
</activity>
<activity  android:name=".FriendsMap"  android:label="FriendsMap">
<intent-filter>
<action  android:name="android.intent.action.MAIN"  />
<category  android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity  android:name="LocationEditor"
android:label="@string/title_note">
<intent-filter  android:label="@string/resolve_edit">
<action  android:name="android.intent.action.VIEW"  />
<action  android:name="android.intent.action.EDIT"  />
<action
android:name="com.google.android.notepad.action.EDIT_LOCATION"  />
<category  android:name="android.intent.category.DEFAULT"  />
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend"  />
</intent-filter>
<intent-filter>
<action  android:name="android.intent.action.INSERT"  />
<category  android:name="android.intent.category.DEFAULT"  />
<dataandroid:mimeType="vnd.android.cursor.dir/
vnd.android_programmers_guide.friend"  />
</intent-filter>
</activity>
<activity  android:name="NameEditor"
android:label="@string/title_edit_title"
android:theme="@android:style/Theme.Dialog">
<intent-filter  android:label="@string/resolve_title">
<action  android:name="com.google.android.notepad.action.EDIT_NAME"
/>
<category  android:name="android.intent.category.DEFAULT"  />
<category  android:name="android.intent.category.ALTERNATIVE"  />162
在下一节中,将为 NameEditor 项目创建第一个活动。正如名称所示,当用户期
望编辑一个 friend 的名称时,这个活动将被启动。
创建 NameEditor 活动
创建 NameEditor NameEditor NameEditor NameEditor 活动  第十一章(6)  (6)  (6)  (6)
在本节中,你将为 FindAFriend 项目创建 NameEditor 活动。这个活动将被从主
活动 FindAFriend 菜单项目中启动(还没有创建)。NameEditor 活动的目的是
修改一个 Friend 记录的姓名字段。
增加一个 name_editor.xml 布局文件并且一个相应的 NameEditor.java 文件到应
用程序中。你将编辑这些文件来创建你的活动。
首先,编辑 name_editor.xml 文件来为活动创建布局文件。活动将有一个
EditText 和一个 Button。EditText 将允许你修改名称字段,Button 将写入结果
并退出。如果你一开始就跟从本书的话,你已经增加了不少的 View 布局到 XML
文件中。因此,我可以免去细节,玩战的 name_editor.xml 文件应当如下所示:
<category  android:name="android.intent.category.SELECTED_ALTERNATIVE"  />
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend"  />
</intent-filter>
</activity>
</application>
<uses-permission  android:name="android.permission.ACCESS_GPS">
</uses-permission><uses-permission
android:name="android.permission.ACCESS_LOCATION">
</uses-permission></manifest>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:paddingBottom="3dip">
<EditText  android:id="@+id/name"
android:maxLines="1"
android:layout_marginTop="2dip"
android:layout_width="wrap_content"
android:ems="25"
android:layout_height="wrap_content"
android:autoText="true"
android:capitalize="sentences"163
现在,编辑 NameEditor.java 并开始写代码。需要导入前一节的 Friends 类并且
导入 Cursor 包装来帮助你使用数据库记录:
应当建立活动来执行 View.OnClickListener()。这将允许你在活动中优先
OnClickListener()方法。这部分代码显示 NameEditor 类的概要和一些你需要的
变量定义:
下一步,需要优先一些方法,从 onCreate()开始。已经在其它章节里看过方法
被优先。通常,当活动被创建,它保留所有被执行的代码:
android:scrollHorizontally="true"  />
<Button  android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/button_ok"  />
</LinearLayout>
import  android.app.Activity;
import  android.database.Cursor;
import  android.net.Uri;
import  android.os.Bundle;
import  android.view.View;
import  android.widget.Button;
import  android.widget.EditText;
public  class  NameEditor  extends  Activity  implements  View.OnClickListener  {
public  static  final  String  EDIT_NAME_ACTION  =
"android_programmers_guide.FindAFriend.action.EDIT_NAME";
private  static  final  int  NAME_INDEX  =  1;
private  static  final  String[]  PROJECTION  =  new  String[]  {
Friends.Friend._ID,
Friends.Friend.NAME,
};
Cursor  mCursor;
EditText  mText;
}
public  void  onCreate(Bundle  icicle)  {
super.onCreate(icicle);
setContentView(R.layout.name_editor);
Uri  uri  =  getIntent().getData();
mCursor  =  managedQuery(uri,  PROJECTION,  null,  null);
mText  =  (EditText)  this.findViewById(R.id.name);
mText.setOnClickListener(this);
Button  b  =  (Button)  findViewById(R.id.ok);164
注意,在前面的代码示例中,你赋值布局到各自的 Views 并且初始化你的变量。
可是,你可能想知道为姓名字段准备的数据在哪里。那就是,你已经创建了一个
光标,但是从中还没有检索任何东西。为此,你将使用 onResume()方法。
下面两个优先的方法是,onResume()和 onPause(),作用是独自的读取和写入数
据库。在 Android 生命周期中,当活动被打开并且在焦点之上,onResume()被呼
叫。onPause()被呼叫的情况是当活动被打开,但是在焦点被传递到另一个活动
之前。
优先 onResume()方法来读取数据库并检索姓名字段:
在这种方式下,你移动光标到第一个记录,使用前面赋值的索引从中读取姓名字
段,然后设置 EditText 到姓名字段的内容。下一步,修改 onPause()方法来写
回 EditText 内容到数据库:
最后,从 onClick 句柄呼叫活动方法 finish()。它将清除并关闭活动。完成后
的 NameEditor.java 文件应当如下:
b.setOnClickListener(this);
}
protected  void  onResume()  {
super.onResume();
if  (mCursor  !=  null)  {
mCursor.first();
String  title  =  mCursor.getString(NAME_INDEX);
mText.setText(title);
}
}
protected  void  onPause()  {
super.onPause();
if  (mCursor  !=  null)  {
String  title  =  mText.getText().toString();
mCursor.updateString(NAME_INDEX,  title);
mCursor.commitUpdates();
}
}
package  android_programmers_guide.FindAFriend;
import  android_programmers_guide.FindAFriend.Friends;
import  android.app.Activity;
import  android.database.Cursor;
import  android.net.Uri;
import  android.os.Bundle;
import  android.view.View;
import  android.widget.Button;
import  android.widget.EditText;165
public  class  NameEditor  extends  Activity  implements  View.OnClickListener  {
public  static  final  String  EDIT_NAME_ACTION  =
"android_programmers_guide.FindAFriend.action.EDIT_NAME";
private  static  final  int  NAME_INDEX  =  1;
private  static  final  String[]  PROJECTION  =  new  String[]  {
Friends.Friend._ID,
Chapter  11:  Application:  Find  a  Friend  281
Friends.Friend.NAME,
};
Cursor  mCursor;
EditText  mText;
@Override
public  void  onCreate(Bundle  icicle)  {
super.onCreate(icicle);
setContentView(R.layout.name_editor);
Uri  uri  =  getIntent().getData();
mCursor  =  managedQuery(uri,  PROJECTION,  null,  null);
mText  =  (EditText)  this.findViewById(R.id.name);
mText.setOnClickListener(this);
Button  b  =  (Button)  findViewById(R.id.ok);
b.setOnClickListener(this);
}
@Override
protected  void  onResume()  {
super.onResume();
if  (mCursor  !=  null)  {
mCursor.first();
String  title  =  mCursor.getString(NAME_INDEX);
mText.setText(title);
}
}
@Override
protected  void  onPause()  {
super.onPause();
if  (mCursor  !=  null)  {
String  title  =  mText.getText().toString();
mCursor.updateString(NAME_INDEX,  title);
mCursor.commitUpdates();
}
}
public  void  onClick(View  v)  {
finish();
}
}166
这样,你可以在 Friends 数据库编辑姓名的值。总之,数据库中的两个字段重要 ,
姓名和位置。下一节,将为位置字段创建编辑器。
创建 LocationEditor 活动
创建 LocationEditor LocationEditor LocationEditor LocationEditor 活动  第十一章(7)  (7)  (7)  (7)
在本节中,你将为 Friends 数据库的“位置”字段创建一个编辑器。做这个活动 ,
你将会从 NameEditor 活动做一点小的修改。因此,代码和过程不同。
如果你浏览了 Google  NotePad 演示版,你应当注意到“notes”编辑器是一个白
色屏幕,带有动态自身重复划线。这是个使用定制 View 执行的结果。你会用这
个相同的 View 来制作 LocationEditor。
location_editor.xml
第一步是独自创建 location_editor.xml 和 LocationEditor.java 文件的布局和
代码。布局文件应当包含一个到定制 View 布局的呼叫。完整的布局文件如下:
LocationEditor 还会包含一个菜单系统,允许用户废弃,删除或者恢复他们做
的任何改变。这会是一个非常复杂的活动。因此,最好从头开始,那就是
LocationEditor.java 文件的输入部分。
LocationEditor.java
看看这个活动的输入,很多是处理屏幕上 View 的图样:
<?xml  version="1.0"  encoding="utf-8"?>
<view  xmlns:android="http://schemas.android.com/apk/res/android"
class="android_programmers_guide.FindAFriend.LocationEditor$MyEditText"
android:id="@+id/location"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:padding="10dip"
android:scrollbars="vertical"
android:fadingEdge="vertical"  />
import  android.app.Activity;
import  android.content.ComponentName;
import  android.content.Context;
import  android.content.Intent;
import  android.database.Cursor;
import  android.graphics.Canvas;
import  android.graphics.Paint;
import  android.graphics.Rect;
import  android.net.Uri;
import  android.os.Bundle;167
下一步,设置活动的主类概要。使用 LocationEditor 期间,这里有不少的变量
需要定义:
所有本章上一节执行的任务,变量的定义是无需加以说明的。
下一小块的代码展示需要创建的子类。这个子类将把 EditText 画在屏幕上。你
把它分开,这样可以从活动中按照需要呼叫它。记住,你会在屏幕上根据用户的
需要动态的绘制一个新的 EditText。特别注意需要优先的 onDraw 类:
import  android.util.AttributeSet;
import  android.view.Menu;
import  android.widget.EditText;
import  java.util.Map;
public  class  LocationEditor  extends  Activity  {
private  static  final  String  TAG  =  "Friends";
private  static  final  int  FRIEND_INDEX  =  1;
private  static  final  int  NAME_INDEX  =  2;
private  static  final  int  MODIFIED_INDEX  =  3;
private  static  final  String[]  PROJECTION  =  new  String[]  {
Friends.Friend._ID,  //  0
Friends.Friend.LOCATION,  //  1
Friends.Friend.NAME,  //  2
Friends.Friend.MODIFIED_DATE  //  3
};
private  static  final  String  ORIGINAL_CONTENT  =  "origContent";
private  static  final  int  REVERT_ID  =  Menu.FIRST;
private  static  final  int  DISCARD_ID  =  Menu.FIRST  +  1;
private  static  final  int  DELETE_ID  =  Menu.FIRST  +  2;
private  static  final  int  STATE_EDIT  =  0;
private  static  final  int  STATE_INSERT  =  1;
private  int  mState;
private  boolean  mNoteOnly  =  false;
private  Uri  mURI;
private  Cursor  mCursor;
Chapter  11:  Application:  Find  a  Friend  285
private  EditText  mText;
private  String  mOriginalContent;
}
public  static  class  MyEditText  extends  EditText  {
private  Rect  mRect;
private  Paint  mPaint;
public  MyEditText(Context  context,  AttributeSet  attrs,  Map  params)  {
super(context,  attrs,  params);
mRect  =  new  Rect();168
还有,看上去好像很多的代码,但是都不陌生。这个子类只是绘制一个所需的新
EditText 到屏幕上。
和 NameEditor 一样,你会使用 onResume()和 onPause()方法来使用数据库。看
看下面每个的代码:
mPaint  =  new  Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF0000FF);
}
@Override
protected  void  onDraw(Canvas  canvas)  {
int  count  =  getLineCount();
Rect  r  =  mRect;
Paint  paint  =  mPaint;
for  (int  i  =  0;  i  <  count;  i++)  {
int  baseline  =  getLineBounds(i,  r);
canvas.drawLine(r.left,  baseline  +  1,  r.right,  baseline  +  1,
paint);
}
super.onDraw(canvas);
}
}
protected  void  onResume()  {
super.onResume();
if  (mCursor  !=  null)  {
mCursor.first();
if  (mState  ==  STATE_EDIT)  {
setTitle(getText(R.string.title_edit));
}  else  if  (mState  ==  STATE_INSERT)  {
setTitle(getText(R.string.title_create));
}
String  note  =  mCursor.getString(FRIEND_INDEX);
mText.setTextKeepState(note);
if  (mOriginalContent  ==  null)  {
mOriginalContent  =  note;
}
}  else  {
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
protected  void  onPause()  {
super.onPause();169
和 NameEditor 很像,在 onResume()期间从数据库读取,在 onPause()期间写回
到数据库。在 LocationEditor 中增加的一个和 NameEditor 相反的特性是当你修
改时,还写完修改的数据。
最后,需要两个方法来取消和删除 friends。这些方法将被从菜单系统中呼叫:
if  (mCursor  !=  null)  {
String  text  =  mText.getText().toString();
int  length  =  text.length();
if  (isFinishing()  &&  (length  ==  0)  &&  !mNoteOnly)  {
setResult(RESULT_CANCELED);
deleteFriend();
}  else  {
if  (!mNoteOnly)  {
mCursor.updateLong(MODIFIED_INDEX,
System.currentTimeMillis());
if  (mState  ==  STATE_INSERT)  {
String  title  =  text.substring(0,  Math.min(30,
length));
Chapter  11:  Application:  Find  a  Friend  287
if  (length  >  30)  {
int  lastSpace  =  title.lastIndexOf('  ');
if  (lastSpace  >  0)  {
title  =  title.substring(0,  lastSpace);
}
}
mCursor.updateString(NAME_INDEX,  title);
}
}
mCursor.updateString(FRIEND_INDEX,  text);
managedCommitUpdates(mCursor);
}
}
}
private  final  void  cancelFriend()  {
if  (mCursor  !=  null)  {
if  (mState  ==  STATE_EDIT)  {
mCursor.updateString(FRIEND_INDEX,  mOriginalContent);
mCursor.commitUpdates();
mCursor.deactivate();
mCursor  =  null;
}  else  if  (mState  ==  STATE_INSERT)  {
deleteFriend();
}170
假 定 你 已 经 学 会 了 第 八 章 中 的 创 建 菜 单 系 统 , 简 单 的 检 查 一 下 完 整 的
LocationEditor.java 文件来所有的这些方法和子类如何一起工作:
}
setResult(RESULT_CANCELED);
finish();
}
private  final  void  deleteFriend()  {
if  (mCursor  !=  null)  {
mText.setText("");
mCursor.deleteRow();
mCursor.deactivate();
mCursor  =  null;
}
}
package  android_programmers_guide.FindAFriend;
import  android.app.Activity;
import  android.content.ComponentName;
import  android.content.Context;
import  android.content.Intent;
import  android.database.Cursor;
import  android.graphics.Canvas;
import  android.graphics.Paint;
import  android.graphics.Rect;
import  android.net.Uri;
import  android.os.Bundle;
import  android.util.AttributeSet;
import  android.view.Menu;
import  android.widget.EditText;
import  java.util.Map;
public  class  LocationEditor  extends  Activity  {
private  static  final  String  TAG  =  "Friends";
private  static  final  int  FRIEND_INDEX  =  1;
private  static  final  int  NAME_INDEX  =  2;
private  static  final  int  MODIFIED_INDEX  =  3;
private  static  final  String[]  PROJECTION  =  new  String[]  {
Friends.Friend._ID,  //  0
Friends.Friend.LOCATION,  //  1
Friends.Friend.NAME,  //  2
Friends.Friend.MODIFIED_DATE  //  3
};
private  static  final  String  ORIGINAL_CONTENT  =  "origContent";
private  static  final  int  REVERT_ID  =  Menu.FIRST;
private  static  final  int  DISCARD_ID  =  Menu.FIRST  +  1;171
private  static  final  int  DELETE_ID  =  Menu.FIRST  +  2;
private  static  final  int  STATE_EDIT  =  0;
private  static  final  int  STATE_INSERT  =  1;
private  int  mState;
288  Android:  A  Programmer’s  Guide
Chapter  11:  Application:  Find  a  Friend  289
private  boolean  mNoteOnly  =  false;
private  Uri  mURI;
private  Cursor  mCursor;
private  EditText  mText;
private  String  mOriginalContent;
public  static  class  MyEditText  extends  EditText  {
private  Rect  mRect;
private  Paint  mPaint;
public  MyEditText(Context  context,  AttributeSet  attrs,  Map  params)  {
super(context,  attrs,  params);
mRect  =  new  Rect();
mPaint  =  new  Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF0000FF);
}
@Override
protected  void  onDraw(Canvas  canvas)  {
int  count  =  getLineCount();
Rect  r  =  mRect;
Paint  paint  =  mPaint;
for  (int  i  =  0;  i  <  count;  i++)  {
int  baseline  =  getLineBounds(i,  r);
canvas.drawLine(r.left,  baseline  +  1,  r.right,  baseline  +  1,
paint);
}
super.onDraw(canvas);
}
}
@Override
protected  void  onCreate(Bundle  icicle)  {
super.onCreate(icicle);
final  Intent  intent  =  getIntent();
final  String  type  =  intent.resolveType(this);
final  String  action  =  intent.getAction();
if  (action.equals(Intent.EDIT_ACTION))  {
mState  =  STATE_EDIT;
mURI  =  intent.getData();
}  else  if  (action.equals(Intent.INSERT_ACTION))  {172
mState  =  STATE_INSERT;
290  Android:  A  Programmer’s  Guide
mURI  =  getContentResolver().insert(intent.getData(),  null);
if  (mURI  ==  null)  {
finish();
return;
}
setResult(RESULT_OK,  mURI.toString());
}  else  {
finish();
return;
}
setContentView(R.layout.location_editor);
mText  =  (EditText)  findViewById(R.id.location);
mCursor  =  managedQuery(mURI,  PROJECTION,  null,  null);
if  (icicle  !=  null)  {
mOriginalContent  =  icicle.getString(ORIGINAL_CONTENT);
}
}
@Override
protected  void  onResume()  {
super.onResume();
if  (mCursor  !=  null)  {
mCursor.first();
if  (mState  ==  STATE_EDIT)  {
setTitle(getText(R.string.title_edit));
}  else  if  (mState  ==  STATE_INSERT)  {
setTitle(getText(R.string.title_create));
}
String  note  =  mCursor.getString(FRIEND_INDEX);
mText.setTextKeepState(note);
if  (mOriginalContent  ==  null)  {
mOriginalContent  =  note;
}
}  else  {
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
Chapter  11:  Application:  Find  a  Friend  291
@Override
protected  void  onFreeze(Bundle  outState)  {
outState.putString(ORIGINAL_CONTENT,  mOriginalContent);
}173
@Override
protected  void  onPause()  {
super.onPause();
if  (mCursor  !=  null)  {
String  text  =  mText.getText().toString();
int  length  =  text.length();
if  (isFinishing()  &&  (length  ==  0)  &&  !mNoteOnly)  {
setResult(RESULT_CANCELED);
deleteFriend();
}  else  {
if  (!mNoteOnly)  {
mCursor.updateLong(MODIFIED_INDEX,
System.currentTimeMillis());
if  (mState  ==  STATE_INSERT)  {
String  title  =  text.substring(0,  Math.min(30,
length));
if  (length  >  30)  {
int  lastSpace  =  title.lastIndexOf('  ');
if  (lastSpace  >  0)  {
title  =  title.substring(0,  lastSpace);
}
}
mCursor.updateString(NAME_INDEX,  title);
}
}
mCursor.updateString(FRIEND_INDEX,  text);
managedCommitUpdates(mCursor);
}
}
}
@Override
public  boolean  onCreateOptionsMenu(Menu  menu)  {
super.onCreateOptionsMenu(menu);
if  (mState  ==  STATE_EDIT)  {
menu.add(0,  REVERT_ID,  R.string.menu_revert).setShortcut('0',
'r');
if  (!mNoteOnly)  {
menu.add(0,  DELETE_ID,
R.string.menu_delete).setShortcut('1',  'd');
}
}  else  {
menu.add(0,  DISCARD_ID,  R.string.menu_discard).setShortcut('0',
'd');
}174
if  (!mNoteOnly)  {
Intent  intent  =  new  Intent(null,  getIntent().getData());
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(
Menu.ALTERNATIVE,  0,
new  ComponentName(this,  LocationEditor.class),  null,
intent,  0,  null);
}
return  true;
}
@Override
public  boolean  onOptionsItemSelected(Menu.Item  item)  {
switch  (item.getId())  {
case  DELETE_ID:
deleteFriend();
finish();
break;
case  DISCARD_ID:
cancelFriend();
break;
case  REVERT_ID:
cancelFriend();
break;
}
return  super.onOptionsItemSelected(item);
}
private  final  void  cancelFriend()  {
if  (mCursor  !=  null)  {
if  (mState  ==  STATE_EDIT)  {
mCursor.updateString(FRIEND_INDEX,  mOriginalContent);
mCursor.commitUpdates();
mCursor.deactivate();
mCursor  =  null;
}  else  if  (mState  ==  STATE_INSERT)  {
deleteFriend();
}
}
setResult(RESULT_CANCELED);
finish();
}
private  final  void  deleteFriend()  {
if  (mCursor  !=  null)  {
mText.setText("");
292  Android:  A  Programmer’s  Guide175
在下一节,会创建绘制 Google  Maps  Overlay 的活动,FriendsMap 活动将从
Friends 数据库中读取完整的 friends 记录集并把每一个写入 Overlay。
创建 FriendsMap FriendsMap FriendsMap FriendsMap 活动  第十一章(8)  (8)  (8)  (8)
FriendsMap 是最后一个可以从主程序中呼叫的活动。本活动将从 Friends 数据库中呼叫数
据集并且在 Google Map 上为每一个 friend 画一个圆圈。该活动还将为你的当前位置画一个
圆圈。
从增加两个新文件来开始本项目,friendsmap.xml 和 FriendsMap.java 文件。因为你已经在
第九章内看过 friendsmap.xml 文件了,所以没有必要再解释一遍。使用的是一个 RelativeL
ayout 来把 4 个按钮放在一个 Google Map 上的。完整的 friendsmap.xml 文件应当看上去如
下:
mCursor.deleteRow();
mCursor.deactivate();
mCursor  =  null;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<view class="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button android:id="@+id/buttonZoomIn"
style="?android:attr/buttonStyleSmall"
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/buttonMapView"
style="?android:attr/buttonStyleSmall"
android:text="Map"
android:layout_alignRight="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/buttonSatView"
style="?android:attr/buttonStyleSmall"176
因为在第九章已经看过绝大多数的 FriendsMap.java 文件了,我不会再阐述每一个细节。但
是,有一个方法需要解释。
你会创建一个叫做 LoadFriends()的方法来存取数据库,读取记录,并且绘制 Overlay。看
一下 LoadFriends()的代码。注意,你打开数据库,匹配并分析位置字段,从位置字段的纬
度和经度创建点,并且绘制点到 Overlay 中。这个方法最后所做的事情就是从 GPS 上抓取
坐标并且在 Overlay 上绘制出来,用标签“ME”表示。
android:text="Sat"
android:layout_alignRight="@+id/myMap"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/buttonZoomOut"
style="?android:attr/buttonStyleSmall"
android:text="-"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
public void LoadFriends(MapView mv, MapController mc, Cursor c){
Point myLocation = null;
Double latPoint = null;
Double lngPoint = null;
c.first();
do{
if (c.getString(c.getColumnIndex("location")) != null) {
final String geoPattern = "(geo:[\\-]?[0-9]{1,3}\\.[0
9]{1,6}\\,[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\#)";
Pattern pattern = Pattern.compile(geoPattern);
CharSequence inputStr =
c.getString(c.getColumnIndex("location"));
Matcher matcher = Pattern.matcher(inputStr);
boolean matchFound = matcher.find();
if (matchFound) {
String groupStr = matcher.group(0);
latPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(":") + 1,
groupStr.indexOf(","))) ;
lngPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(",") + 1,
groupStr.indexOf("#"))) ;
Point friendLocation = new
Point(latPoint.intValue(),lngPoint.intValue());177
剩余的 FriendsMap.java 文件操控第十章介绍的缩放和棒性按钮:
294 Android: A Programmer’s Guide
drawFriendsOverlay.addNewFriend(c.getString(c.getColumnIndex("name")),
friendLocation);
}
}
}while(c.next());
LocationManager myManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
Double myLatPoint =
myManager.getCurrentLocation("gps").getLatitude()*1E6;
Double myLngPoint =
myManager.getCurrentLocation("gps").getLongitude()*1E6;
myLocation = new Point(myLatPoint.intValue(),myLngPoint.intValue());
drawFriendsOverlay.addNewFriend("Me", myLocation);
mc.centerMapTo(myLocation, false);
mc.zoomTo(9);
mv = null;
}
package android_programmers_guide.FindAFriend;
import android.os.Bundle;
import android.location.LocationManager;
import android.view.View;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.widget.Button;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.Paint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import com.google.android.maps.Point;
import com.google.android.maps.MapController;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayController;
public class FriendsMap extends MapActivity {
private static final String[] PROJECTION = new String[] {
Friends.Friend.NAME, Friends.Friend.LOCATION};
public Cursor mCursor;
DrawFriendsOverlay drawFriendsOverlay = new DrawFriendsOverlay();
@Override178
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.friendsmap);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(Friends.Friend.CONTENT_URI);
}
mCursor = managedQuery(getIntent().getData(), PROJECTION, null,null);
final MapView myMap = (MapView) findViewById(R.id.myMap);
final MapController myMapController = myMap.getController();
LoadFriends(myMap, myMapController, mCursor);
OverlayController myOverlayController =
myMap.createOverlayController();
myOverlayController.add(drawFriendsOverlay, true);
final Button zoomIn = (Button) findViewById(R.id.buttonZoomIn);
zoomIn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ZoomIn(myMap,myMapController);
}});
final Button zoomOut = (Button) findViewById(R.id.buttonZoomOut);
zoomOut.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ZoomOut(myMap,myMapController);
}});
final Button viewMap = (Button) findViewById(R.id.buttonMapView);
viewMap.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ShowMap(myMap,myMapController);
}});
final Button viewSat = (Button) findViewById(R.id.buttonSatView);
viewSat.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
ShowSat(myMap,myMapController);
}});
}
public void LoadFriends(MapView mv, MapController mc, Cursor c){
Point myLocation = null;
Double latPoint = null;
Double lngPoint = null;
c.first();
do{
if (c.getString(c.getColumnIndex("location")) != null) {
final String geoPattern = "(geo:[\\-]?[0-9]{1,3}\\.[0
9]{1,6}\\,[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\#)";179
Pattern pattern = Pattern.compile(geoPattern);
CharSequence inputStr =
c.getString(c.getColumnIndex("location"));
Matcher matcher = pattern.matcher(inputStr);
boolean matchFound = matcher.find();
if (matchFound) {
String groupStr = matcher.group(0);
latPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(":") + 1,
groupStr.indexOf(","))) ;
lngPoint =
Double.valueOf(groupStr.substring(groupStr.indexOf(",") + 1,
groupStr.indexOf("#"))) ;
Point friendLocation = new
Point(latPoint.intValue(),lngPoint.intValue());
drawFriendsOverlay.addNewFriend(c.getString(c.getColumnIndex("name")),
friendLocation);
}
}
}while(c.next());
LocationManager myManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
Double myLatPoint =
myManager.getCurrentLocation("gps").getLatitude()*1E6;
Double myLngPoint =
myManager.getCurrentLocation("gps").getLongitude()*1E6;
myLocation = new Point(myLatPoint.intValue(),myLngPoint.intValue());
drawFriendsOverlay.addNewFriend("Me", myLocation);
mc.centerMapTo(myLocation, false);
mc.zoomTo(9);
mv = null;
}
public void ZoomIn(MapView mv, MapController mc){
if(mv.getZoomLevel()!=21){
mc.zoomTo(mv.getZoomLevel()+ 1);
}
}
public void ZoomOut(MapView mv, MapController mc){
if(mv.getZoomLevel()!=1){
mc.zoomTo(mv.getZoomLevel()- 1);
}
}
public void ShowMap(MapView mv, MapController mc){
if (mv.isSatellite()){180
完成本项目的最后一个任务是创建主活动,FindAFriend。该活动被放置在一个壳内来呼叫
本章创建的活动。
mv.toggleSatellite();
}
}
public void ShowSat(MapView mv, MapController mc){
if (!mv.isSatellite()){
mv.toggleSatellite();
}
}
protected class DrawFriendsOverlay extends Overlay{
public String[] friendName = new String[0];
public Point[] friendPoint = new Point[0];
final Paint paint = new Paint();
@Override
public void draw(Canvas canvas, PixelCalculator calculator, Boolean
shadow){
for(int x=0;x<friendPoint.length; x++){
int[] coords = new int[2];
calculator.getPointXY(friendPoint[x], coords);
RectF oval = new RectF(coords[0] - 7, coords[1] + 7,
coords[0] + 7, coords[1] - 7);
paint.setTextSize(14);
canvas.drawText(friendName[x],
coords[0] +9, coords[1], paint);
canvas.drawOval(oval, paint);
}
}
public void addNewFriend(String name,Point point ){
int x = friendPoint.length;
String[] friendNameB = new String[x + 1];
Point[] friendPointB = new Point[x + 1];
System.arraycopy(friendName, 0, friendNameB, 0, x );
System.arraycopy(friendPoint, 0, friendPointB, 0, x);
friendNameB[x] = name;
friendPointB[x]= point;
friendName = new String[x + 1];
friendPoint = new Point[x + 1];
System.arraycopy(friendNameB, 0, friendName, 0, x + 1 );
System.arraycopy(friendPointB, 0, friendPoint, 0, x + 1 );
}
}
}181
创建 FindAFriend FindAFriend FindAFriend FindAFriend 活动  第十一章(9)  (9)  (9)  (9)
要开始本节,创建两个文件,findafriend.xml 和 FindAFriend.java。再说一次,这些文件将
为当前部分独自保留你的布局和代码。布局文件非常的基本并且只有一个 TextView。这个T
extView 将被用来写入到 friends 的列表中。完整的 findafriend.xml 文件应当显示如下:
完整的 FindAFriend.java 文件如下。文件中的所有代码在本章中已经讨论过。首先,读取
数据库并且写入结果到一个 ListView。给予用户一个菜单项目来编辑或者删除条目,或者启
动 FriendsMap 活动。很简单,对不对?
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:gravity="center_vertical"
android:paddingLeft="27dip"
/>
package android_programmers_guide.FindAFriend;
import android_programmers_guide.FindAFriend.Friends;
import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ContentUris;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
public class FindAFriend extends ListActivity {
300 Android: A Programmer’s Guide
public static final int DELETE_ID = Menu.FIRST;
public static final int INSERT_ID = Menu.FIRST + 1;
public static final int FIND_FRIENDS = Menu.FIRST + 2;
private static final String[] PROJECTION = new String[] {
Friends.Friend._ID, Friends.Friend.NAME};182
private Cursor mCursor;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setDefaultKeyMode(SHORTCUT_DEFAULT_KEYS);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(Friends.Friend.CONTENT_URI);
}
setupList();
mCursor = managedQuery(getIntent().getData(), PROJECTION, null,
null);
ListAdapter adapter = new SimpleCursorAdapter(this,
R.layout.findafriend_item, mCursor,
new String[] {Friends.Friend.NAME}, new int[]
{android.R.id.text1});
setListAdapter(adapter);
}
private void setupList() {
View view = getViewInflate().inflate(
android.R.layout.simple_list_item_1, null, null);
TextView v = (TextView) view.findViewById(android.R.id.text1);
v.setText("X");
getListView().setBackgroundColor(Color.GRAY);
v.measure(MeasureSpec.makeMeasureSpec(View.MeasureSpec.EXACTLY,
100),
MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED,
0));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, R.string.menu_insert).setShortcut('3', 'a');
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(
Menu.ALTERNATIVE, 0, new ComponentName(this, FindAFriend.class),
null, intent, 0, null);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
final boolean haveItems = mCursor.count() > 0;183
if (haveItems) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(),
getSelectedItemId());
Intent[] specifics = new Intent[1];
specifics[0] = new Intent(Intent.EDIT_ACTION, uri);
Menu.Item[] items = new Menu.Item[1];
Intent intent = new Intent(null, uri);
intent.addCategory(Intent.SELECTED_ALTERNATIVE_CATEGORY);
menu.addIntentOptions(Menu.SELECTED_ALTERNATIVE, 0, null,
specifics, intent, 0, items);
menu.add(Menu.SELECTED_ALTERNATIVE, DELETE_ID,
R.string.menu_delete)
.setShortcut('2', 'd');
menu.add(Menu.SELECTED_ALTERNATIVE, FIND_FRIENDS,
R.string.find_friends).setShortcut('4', 'f');
if (items[0] != null) {
items[0].setShortcut('1', 'e');
}
} else {
menu.removeGroup(Menu.SELECTED_ALTERNATIVE);
}
menu.setItemShown(DELETE_ID, haveItems);
return true;
}
@Override
public boolean onOptionsItemSelected(Menu.Item item) {
switch (item.getId()) {
case DELETE_ID:
deleteItem();
return true;
case INSERT_ID:
insertItem();
return true;
case FIND_FRIENDS:
Intent findfriends = new Intent(this, FriendsMap.class);
startActivity(findfriends);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long
id) {
Uri url = ContentUris.withAppendedId(getIntent().getData(), id);184
这个是本书中最长的一个活动,需要注意的是你所做的相关所需的编程工作还是非常的少。
下一步,运行这个活动并且查看所有工作的成果。
运行 FindAFriend FindAFriend FindAFriend FindAFriend 活动  第十一章(10)    (10)    (10)    (10)
在 Android 模拟器中运行 FindAFriend 活动。应当从一个空的列表开始,如下图
(略)。要增加第一个朋友,点击菜单按钮并选择 Add  Friend 选项。
本选项启动你创建的定制 View。在提供的行处输入一个朋友的名称,点击返回
按钮返回到主活动。
现在在 ListView 中应当有一个朋友的名称。再次点击菜单按钮,显然你拥有更
多的选项了。如下图(略)。选择 Edit  Location 选项。会再次来到定制控制。
输入坐标信息。
最后,返回到主活动并选择 Find  Friend 选项。这样分别会清除在旧金山的位置
和你朋友在非洲海岸的位置。
试试这个:实时位置更新
试着修改 FindAFriend 应用程序,当你移动时,来更新“ME”标记。这个应该很
容易通过使用 update()方法实现。
问专家
Q:SQLite 数据库可以通过代码的方式创建吗?
A:是的。但是,为了更好的了解 Android 的目的,我选择了手工创建数据库例子 。
可以在 FriendsProvider  Content  Provider 通过代码创建数据库的 creation
方法自行修改本项目。
String action = getIntent().getAction();
if (Intent.PICK_ACTION.equals(action)
|| Intent.GET_CONTENT_ACTION.equals(action)) {
setResult(RESULT_OK, url.toString());
} else {
startActivity(new Intent(Intent.EDIT_ACTION, url));
}
}
private final void deleteItem() {
mCursor.moveTo(getSelectedItemPosition());
mCursor.deleteRow();
}
private final void insertItem() {
startActivity(new Intent(Intent.INSERT_ACTION,
getIntent().getData()));
}
}Q:需要一个分开的类来执行 BaseColumns?
A:不。可以直接从 Friends 类中定义条目。如果你创建的一个 Content  Provider
被其它开发者使用,但是他们不知道数据库的底层结构,那么你就需要提供一个
定义的类。

你可能感兴趣的:(24、Android应用-找一个朋友 (整合SQLite/Content Provider/Google Maps))