第四章 开车不发短信
本章将引导你创建一款“开车不发短信”的应用,一个“短信应答机”,当你正在开车(或忙于工作)时,它能自动回复收到的短信,还可以大声读出短信的内容,甚至可以在自动回复的短信中报告你当前所在的位置。这款应用中用到了安卓手机的短信收发、语音合成、数据永久保存以及GPS定位等功能,为你提供了一个充分挖掘安卓手机潜能的范例。
2010年1月,美国国家安全委员会(NSC)发布了一项研究结果,每年有至少28%的交通事故——将近160万次车祸源于司机在开车时使用手机,其中至少20万次交通事故与司机收发短信有关 。因此,许多州已经全面禁止司机开车时使用手机。 Daniel Finnegan,一名旧金山大学的学生,在App Inventor编程课上,提出了一个了不起的想法,用一个应用来解决开车时收发短信泛滥的问题。如图4-1所示,他创建的应用可以对收到的任何短信进行自动回复(且免提),如回复“我正在开车,稍后与您联系”之类的内容。
后来,这款应用的功能得到扩展,可以大声读出收到的短信、并利用GPS定位功能,在自动回复信息中报告司机当前的位置信息,最终这款应用被制作成教程,发布在App Inventor的官方网站上。
就在这款应用发布到App Inventor网站上的几周之后,联邦农业保险公司(State Farm Insurance) 创建了一款名为“行进中(On the Move)”的安卓应用,其功能与“开车不发短信”类似。
我们无法确认Daniel的应用或App Inventor网站的教程是否对“行进中”产生了影响,但有趣的是,这让我们思考一种可能性:在一门编程基础课上创建的应用(竟然由一名学习创意写作的学生创建!)或许真的催生了这款装机量巨大的软件,或者,至少是对软件产业的生态有所贡献。当然,这也说明了App Inventor降低了软件开发的门槛,以至于无论是谁,只要有一个好主意,都可以快速、低成本地把灵感变成一个实实在在的、可交互的应用。连线杂志(Wired)的Clive Thompson捕捉到了这股新的气息,并写道:
总之,软件几乎影响了我们周围的一切。让我们来盘点一下那些重大的问题——全球变暖、医疗保健,还有Finnegan的例子中的高速公路安全问题,在这些问题的解决方案中,都少不了那些智能软件的参与。当然,只有少数人打算去学习编程,这意味着,社会中蕴藏的巨大创造力还有待我们去发掘。
App Inventor正是Thompson所说的那个发掘创造力的工具,它向每个人敞开了软件创作之门(注意:是创作而不是开发,就像写作一样,人人都可以表达自己,而技术不再成为创作的门槛!——译者注)。
学习内容
相比前几章来说,本章的应用更为复杂,因此我们每次只完成一项功能,从自动回复开始。以下是将要学习的内容:
- 短信收发器组件:用于处理收到的短信并发送短信;
- 文本输入框组件:用于输入自定义回复信息(配合按钮点击事件保存该信息);
- 本地数据库组件:用于在安卓手机中保存自定义信息,即使是关闭应用,信息也不会丢失;
- 屏幕初始化事件:在应用启动时加载自定义回复信息;
- 语音合成器组件:用于读出短信中的文字;
- 位置传感器组件:报告司机的当前位置。
准备开始
打开浏览器,进入App Inventor网站,创建一个新项目,将其命名为“开车不发短信”(记住,项目名不能包含空格),并将Screen1的标题属性设置为“开车不发短信”。点击连接→AI伴侣,或运行模拟器,为实时测试做好准备。
设计组件
应用的用户界面很简单,包含四个可视组件:第一个标签用于显示这款应用的功能,第二个标签用于显示自动回复短信的内容,一个文本输入框用于编辑或修改自动回复短信的内容,一个按钮用于保存已经编辑好的自定义短信内容;应用中还包含四个非可视组件:短信收发器组件、本地数据库组件、语音合成器组件以及位置传感器组件,它们将出现在“不可视组件”区。你可以在图4-2中看到上述组件在设计视图中的样子。
表4-1中列出了应用中的所有组件,你可以按照表中的内容,将组件拖入预览窗口,来创建图4-2中的用户界面。
表4-1 应用中的所有组件
按照以下方式设置组件属性:
- 设置功能说明标签的显示文本属性为“本应用在运行时,将使用以下文字自动回复收到的短信。”
- 设置回复内容标签的显示文本属性为“我正在开车,稍后与您联系。”并勾选粗体属性;
- 设置自定义短信输入框的显示文本属性为“”(让文本框为空,等待用户输入);
- 设置自定义短信输入框的提示属性为“请输入自定义短信内容”,宽度设为“充满”;设置保存按钮的显示文本属性为“保存”。
为组件添加行为
最先实现自动回复功能——对收到的任何短信进行回复;然后实现短信内容的自定义功能——让用户可以编写个性化的回复内容,并将内容永久保存在手机中;最后,让应用大声读出收到的短信,并将手机当前的位置信息添加到自动回复内容中。
来信的自动回复
短信的自动回复功能需要用到App Inventor中的短信收发器组件,可以把它想象成一个藏在手机里的小矮人,它知晓如何收发短信。当手机接收到一条短信时,将触发该组件的“收到消息”事件。拖出该事件块,并在块内添加一些代码块,看看当你收到短信时,会发生什么事情。在本章的例子中, 我们希望自动回复一条预先设定好的信息。
用三行代码块就可以实现短信的发送:①设置接收短信的电话号码,即设置短信收发器组件的电话号码属性;②设置将要发送的短信内容,这也是短信收发器组件的一个属性;③使用短信收发器组件的发送消息功能发送短信(术语:调用短信收发器组件的发送消息过程。——译者注)。表4-2列出了自动回复功能所需要的代码块,图4-3是这些代码块在编程视图中的排列。
表4-2 实现自动回复功能的代码块
块是作用
手机收到短信时,将触发短信收发器1的收到消息事件。如图4-3所示,发送者的手机号保存在参数手机号码中,短信的内容保存在参数消息内容中。
自动回复就是要向发送者发送一条短信,为此要先设置短信收发器组件的两个关键属性:电话号码及短信。将电话号码属性设置为发送者的手机号码,短信属性设置为回复内容标签中显示的内容:“我正在开车,稍后与您联系。”设置完成之后,执行短信收发器1的发送短信指令,来实现自动回复。
测试:需要用两部手机来测试程序,第一部用于运行这个应用,第二部用来向第一部发送短信。如果只有一部手机,可以在电脑上使用Google Voice或其他类似的服务,利用这些服务给运行应用的手机发送短信。设置完成后,给正在运行本应用的第一部手机发短信,看看第二部手机是否收到了回信?
编写自定义回复内容
下面来添加更多的程序块,允许用户输入自定义的回复内容。在设计视图中,已经添加了名为自定义短信输入框组件,用于输入自定义回复信息,当用户点击保存按钮时,自定义短信输入框中的内容将显示在回复内容标签中,成为自动回复短信的内容。表4-3列出了在回复内容标签中显示自定义回复内容所需要的块。
表4-3 显示自定义短信所需要的块
块的作用
一个典型的输入表单的工作流程是:首先在文本框中输入文字,然后单击保存按钮来通知系统做处理,本应用中的输入表单也不例外。图4-4显示了用户点击保存按钮时触发的点击事件处理程序。
事件处理程序将用户在自定义短信输入框中输入的文字复制到回复内容标签中。记住,回复内容标签中保存的是自动回复信息,因此要确保新输入的信息显示在回复内容标签中。
测试:输入一段自定义信息并点击保存按钮,然后用第二部手机发送短信到测试手机上,看看这次自动回复的是新的自定义内容吗?
永久保存自定义回复内容
现在用户可以自定义短信内容,来实现自动回复功能,不过这里有一个小问题:用户输入了自定义回复内容,然后关闭应用,当再次启动应用时,自定义的内容却不见了(取而代之的是默认的回复内容)。这可不是用户想要的功能,他们希望在重新启动应用时,自定义的内容还在,为此需要信息的永久保存。
在回复内容标签的显示文本属性中保存数据,从技术上讲,也算是一种数据的存储,只不过是一种临时存储。临时存储类似于人类的短时记忆,只要应用关闭,数据就会被“忘记”。如果希望应用能永久记住某些数据,就需要将临时存储的数据(组件的属性或变量)转变为永久存储的数据(数据库或文件)。
要永久地保存数据,本章要用到本地数据库组件,它可以将数据存储在安卓设备内置的数据库中。本地数据库组件有两个最常用的功能:保存数据和请求数据。前者允许应用将信息存储在设备数据库中,而后者则允许应用重新读取已存储的信息。 对于大多数的应用,都可以采取如下存储策略:
- 当用户提交新的数据时,将其存储到数据库中;
- 在应用启动时,从数据库中加载数据,并将其保存在一个变量或组件的属性中。 为了实现数据的永久保存,必须修改保存按钮的点击事件处理程序,表4-4中列出了所需要的代码块。
表4-4 用本地数据库组件保存自定义短信内容所需要的块
块的作用
使用本地数据库组件保存数据需要提供两个参数:需要保存的数据,以及存储数据时使用的标记。从回复内容标签的显示文本属性中提取要保存的数据,使用“自定义短信”为标记,将数据保存到数据库中。如图4-5所示。可以把标记想象成数据在数据库中的存放地址,是数据的唯一标识。在下一节中你将看到,在从数据库中提取数据时,必须使用相同的标记(“自定义短信”)。
应用启动时读取自定义短信
将自定义短信保存在数据库中,以便用户再次启动应用时,保存的数据可以被重新读取出来。App Inventor可以侦测到一个特殊的事件:屏幕初始化事件,当应用启动时,将触发该事件(我们在第3章打地鼠中使用过)。将“当Screen1初始化时”块拖出来,并将一些代码块放在其中,那么这些代码块会在应用启动时按顺序执行。
在本应用中,将在Screen1的初始化事件中检查数据库中是否存放了自定义短信。如果是,则使用本地数据库组件的请求数据指令加载存储的内容。实现这一功能所需的块见表4-5。
表4-5 应用启动时用于加载数据的块
块的作用
如图4-6所示,要想理解这些块的功能,必须设想用户的使用过程:首次打开应用,用户输入自定义短信,随后退出应用,然后再次打开应用。用户首次启动应用时,数据库中没有自定义短信可供提取,因此回复内容标签中显示的是默认回复。再次启动时,才有可能从数据库中提取到自定义短信,并将其显示在回复内容标签中。
应用启动时将触发屏幕初始化事件,此时使用标记“自定义短信”来调用本地数据库1的请求数据过程,该标记与之前用户存储自定义短信时使用的标记相同。如果数据库中保存过以“自定义短信”为标记的数据,则将返回的数据显示在回复内容标签中。
不过,第一次启动应用时,数据库中没有数据,只有当用户输入了自定义短信,并点击保存按钮时,数据才被保存到数据库中。本地数据库组件的请求数据过程的第二个参数“无标记返回”,就是为了处理这种没有数据的情况而设置的,即,如果没有发现可提取的数据,则返回预先设定的无标记返回值,在这个例子中,返回默认的回复内容:“我正在开车,稍后与您联系。”,并将其显示在回复内容标签中。
测试:要测试这个新功能,需要重新启动应用,以便察看数据是否保存成功,并且能否被重新提取出来。在实时测试状态下,有一个办法可以重新启动应用,就是在设计视图中修改某个组件的属性值,如修改某个标签的字号,这种修改会导致测试设备中应用的重新启动,并触发屏幕初始化事件。当然,你也可以将应用编译成.apk文件并安装到手机上,进行正式测试。应用在手机上安装运行后,输入并保存自定义信息,然后关闭应用;紧接着再打开应用,此时,如果还能看到你保存过的信息,则测试成功。
大声读出收到的短信
本节为应用添加新的功能:收到短信后,手机将大声读出短信发送者的电话号码以及短信内容。开车时收到短信,虽然有了自动回复功能,但你还是禁不住想知道来信的内容。使用安卓设备的语音合成功能,就可以在手不离方向盘的情况下,收听短信的内容。
安卓设备提供了语音合成功能,而App Inventor提供了一个语音合成器组件,它可以读出任何提交给它的文本信息 。注意:语音合成器组件读出的文本信息指的是一串字母、数字及标点符号组成的文本,而不是手机之间收发的短信。
语音合成器组件简单易用,只需调该组件的用合成语音功能,并给出需要读出的文字即可,如图4-7所示,在合成语音的代码块中,将一段文字,如“你好,世界!”填充到消息参数的插槽中,即可让安卓设备读出这段文字(前提是你事先安装了讯飞语音+,见本章的注脚4。如果尚未安装,请用“Hello World!”替代“你好,世界!”进行测试。——译者注)。
在本应用中,需要读出更多复杂的内容,既要包含短信发送者的电话号码,也要包含短信内容,而不只是像“你好,世界!”那样的静态文本。这里要用到“拼字串”块,它可以将若干个文本片段(或数字以及其他字符)连接起来,构成单一的文本对象。
需要回到之前的短信收发器1的收到消息事件中,调用语音合成器1的合成语音功能。此前,在收到消息事件中,我们实现了短信自动回复功能:设置短信收发器的电话号码及短信属性,并发送设定好的短信。现在需要添加表4-6中所列出的块来扩展该事件的处理程序。
表4-6 读出收到的短信所需的块
块的作用
在自动回复动作完成之后,将调用语音合成器1的语音合成功能,如图4-8的下半部分所示。你可以在合成语音块的消息插槽中插入任何文字。在这个例子中,“拼字串”块用来拼接需要被读出的内容,它将“收到来自”、来信号码、“的短信,内容是:”以及来信内容这四段文字串连在一起。假设收到来自1111-222发来的短信“你好!”,则最后将生成这样的信息:“收到来自1111-222的短信,内容是:你好!”。
测试:你需要第二部手机来测试这个应用。用第二部手机发送短信到运行了这个应用的测试手机上。测试手机是否读出了收到的短信?是否实现了自动回复功能?
在自动回复中加入位置信息
签到类的应用(Check-In) 可以帮助人们追踪彼此的位置信息,但这类应用最令人担忧的是隐私问题,原因之一是它引发了人们对“老大哥”的恐惧,这里的“老大哥”指的是那些设法跟踪其公民下落的集权政府。但是使用位置信息的应用的确非常有用,试想一个迷路的小孩,或者那些在森林里迷路的徒步旅行者。
在“开车不发短信”的应用中,位置跟踪让回复的短信再多一点内容,而不只是“我正在开车”,回复的信息可以是这样的:“我正在开车,我的位置是北京东直门内大街1号。”对于那些正在等待朋友或家人到来的人来说,这些额外的信息非常有益。
App Inventor提供了位置传感器组件,作为手机GPS (Global Positioning System全球定位系统)功能的接口。除了纬度和经度信息,位置传感器组件也可以接入到谷歌地图,为司机提供当前位置的地址信息 。
这里需要说明一下,位置传感器组件并不是连续地读取位置信息,在两次读取信息的动作之间,有一定的时间间隔,这是使用这一组件需要注意的地方。也就是说,手机位置的改变并不能直接触发位置信息改变事件,只有当传感器读取到新的位置信息时,才能触发位置信息改变事件,我们所要做的是,在应用中对位置信息改变事件作出响应。有两种情况会触发位置信息改变事件:①当传感器第一次收到位置信息时;②随着手机的移动,传感器读取到了新的位置信息时。表4-7中列出了响应该事件需要的块。如图4-9所示,声明一个全局变量“当前位置”,将传感器最后一次收到的位置信息保存在变量中,此后,当手机收到短信时,将该位置信息整合到自动回复信息中。
表4-7设置位置传感器的块
块的作用
当位置传感器首次读取位置信息时,将触发位置传感器组件的位置信息改变事件,随着设备的移动,传感器将读取到新的位置信息,事件将被再次触发。当事件发生时,将最新的位置信息(带有街道及门牌号的具体地址)保存在变量“当前位置”中。
以上代码只是用于获取位置信息,距离我们的最终目标——将位置信息编排在自动回复短信中,还差一步,下面继续努力。
发送带有位置信息的回复短信
修改短信收发器1的收到消息事件处理程序,利用变量当前位置中保存的地址信息,生成自动回复信息。表4-8种列出了所需的块。
表4-8 在自动回复中添加位置信息所需的代码块
块的作用
向回复短信中添加的位置信息来自于全局变量当前位置,而当前位置来自于位置传感器1的位置信息改变事件,现在来使用这段位置信息。在图4-10中,不是简单地发送回复内容标签中的文字,而是使用“拼字串”块将若干段信息整合起来:原有的自动回复信息 +“ 我的最新位置是:”+ 当前位置 。
变量当前位置的默认值是“未知”,所以如果位置传感器尚未读取到位置信息,则自动回复的第二部分内容为“我的最新位置是:未知”。如果已经读取到位置信息,则自动回复的第二部分内容有可能是这样:“我的最新位置是:北京市东城区东直门内大街1号。”
测试:用第二部手机发送短信到运行应用的手机上,第二个手机是否接收到了带有位置信息的自动回复?如果没有,请确保你已经开启了第一部手机的GPS定位功能。
完整的应用:开车不发短信
图4-11中显示了“开车不发短信”中所有的代码块。
改进
一个应用,如果你每天都在用,你总会发现它有不尽人意之处,之后便开始逐步完善它,这个应用也不例外,例如下面的改进:
- 添加更为个性化的功能,允许用户针对某些特定来信号码定制回复内容。你需要增加一个条件(如果)块,来判断这些来信的手机号。有关条件(如果)块的更多信息,请参见第18章;
- 提高定位的精确程度:根据用户是否在某个纬度/经度范围内,来确定用户的精确位置,并定制相应的回复信息。例如,如果应用判断你在房间222,它会回复“李四在222房间,现在不能回复短信。”有关位置传感器组件以及边界定位的更多信息,请参见第23章;
- 还有一种改进,针对某些特殊的来信号码,发出特殊的提示铃声,可以将这些特殊号码放在一个“通知”列表中,以便收到短信时进行判断。关于如何使用列表,请参阅第19章。
小结
下面是本章涉及到的一些概念:
- 短信收发器组件:既可以用来发短信,也可以处理收到的信息。在执行发送消息指令之前,需要为短信收发器组件设置电话号码及短信属性。为了对收到的信息进行回复,需要为短信收发器组件编写收到消息事件处理程序;
- 本地数据库组件:用于将信息永久存储在手机数据库中,以便每次应用启动时都可以加载保存过的数据。有关本地微数据库组件的更多信息,请参见第22章;
- 语音合成组件:对于所提供的任何文本信息,都可以大声地读出来(读中文需要手机中安装讯飞语音+,并做相应的设置。——译者注);
- 拼字串:用于将若干个文本片段拼凑(或连接)成一整段文字;
- 位置传感器组件:可以报告手机的纬度、经度及当前的街道地址。为了确认它是否收到了位置信息,可以从位置传感器组件的位置信息改变事件中获取相关的参数。该事件在第一次收到位置信息时被触发,并在每次位置信息更新后被再次触发。有关位置传感器组件的更多信息,请参见第23章。
如果你想对短信处理应用做进一步探索,请参考第11章的广播中心应用。