开发体验

第22章 数据库

数据库是一种数据存储技术,广泛地应用于各类网络应用中。Facebook用数据库保存每位用户的账户信息、好友列表以及用户发布的信息,亚马逊网上商店用数据库保存你能买到的所有商品的信息,而谷歌的数据库中有互联网上每个页面的信息。尽管有些应用的规模没有那么大,但几乎每一个功能完整的应用都会涉及数据存储技术。

无论使用哪种编程语言或开发环境,要想编写一个能够与数据库通信的应用,都会涉及一系列的高级编程技术:首先要搭建数据库服务器,如Oracle或MySQL等,然后要编写程序,实现本地应用与数据库服务器之间的接口。在许多大学里,只有在高级软件工程及数据库专门课程中,才会讲授与数据库有关的技术。

不过你不必因此就望而生畏,实际上App Inventor已经将数据库技术中最复杂的部分封装在数据库组件中,并利用这些组件的内置功能模块,将繁琐的数据库通信功能简化为单纯的读写操作。你的应用可以直接将数据保存到安卓设备上(本地数据库),也可以通过一些简单的设置,将数据集中保存到互联网上(网络数据库),从而实现数据在不同设备、不同用户之间的共享。

那些临时保存在变量及组件属性中的数据被称为短时信息:如果用户在表单中输入某些信息,并且在保存到数据库之前关闭了应用,那么当应用重新打开时,这些信息将不复存在。想要长期保存这些信息,就需要将它们保存到数据库中。数据库中的信息被称为永久信息,因为当应用关闭后再重新打开时,数据依然存在。

举例说明。回顾一下第4章的“开车不发短信”应用,即那个收到短信时能够自动回复的应用。这个应用中设置了一条默认的自动回复信息,同时也允许用户输入一条自定义的回复信息。如果用户将自动回复信息改为“我在睡觉,别来烦我”,然后关闭应用,那么当再次打开应用时,自动回复信息应该还是“我在睡觉,别来烦我”,而不是原来的默认信息。为了实现这一功能,必须将自定义信息保存到数据库中,而且每次启动应用时,必须从数据库中将此信息提取到应用中。

数据在本地的永久保存

App Inventor提供了两个便于操作的数据库组件:本地数据库及网络数据库。本地数据库可以将数据直接保存在安卓设备上,它适合于那些个人应用,如“开车不发短信”,这类应用不需要在不同设备以及不同用户之间共享数据。而网络数据库则是将数据保存到互联网上,并可实现不同设备及不同用户之间的数据共享。在那些多用户的网络游戏及应用中,玩家或用户之间需要进行信息交流与共享,而网络数据库是实现这些功能的基础(如第10章的“出题”应用)。

这两个数据库组件非常相似,但本地数据库更为简单一些,因此我们先来研究它。用本地数据库可以将应用中的数据直接保存在安卓设备上,不需要做任何设置,而且只能在应用中访问这些数据。

下面来看数据的保存操作。本地数据库组件有一个内置的功能模块——保存数据块,用于数据的存储操作,如图22-1所示。这段代码来自于“开车不发短信”应用。

图22-1 用本地数据库组件将数据永久保存在设备中

数据库组件使用“标记-数值”对的方式保存数据。在图22-1中,标记为“自定义短信”,而数值是用户在文本输入框中新输入的自动回复内容,比如“我在睡觉,别来烦我”。

标记是数据的名称,是信息查询的依据,而数值才是数据本身。可以将标记理解为钥匙,必须使用这把钥匙才能从数据库中提取已经存储的数据。

同样,可以将App Inventor的本地数据库理解为一个表,其中包含了许多标记-数值对。在图22-1中,当保存数据块运行结束后,设备数据库中将增加一个标记-数值对,如表22-1中所列。

表22-1 存储到数据库中的标记-数值对

标记 数值
自定义短信 我在睡觉,别来烦我

一个应用中可能有多项需要永久保存的数据,可以保存到多个标记-数值对中。标记必须是文本,而数值既可以是单个的数据(一段文本或一个数字),也可以是一个列表。每个标记只能对应一个数值,因此,当你使用同一个标记保存一个新的数值时,将覆盖原来的数值。

从本地数据库提取数据

本地数据库组件还提供了另一个内置功能模块——请求数据块,用于从数据库中提取数据。关于这个块,有两点需要说明。首先,请求数据块有两个参数,其中之一是“标记”。在使用该块时,需要为标记提供具体的值,来请求相关的数据。在“开车不发短信”应用中,保存数据时使用的标记“自定义短信”同样也用来从数据库中请求数据。其次,请求数据块是一个有返回值的块,因此必须定义一个变量,或利用某个组件的显示文本属性,来接收请求数据块的返回值,也就是说,调用请求数据块必须填充到一个插槽中。

通常会在应用启动时从数据库中提取某些数据。App Inventor提供了一个特别的事件处理程序——屏幕(Screen)初始化程序,应用启动时会触发该程序。有一种情况需要格外小心,就是数据库为空时(如应用第一次启动时),此时数据库中不存在你要访问的标记,为此,请求数据块提供了另一个参数——无标记返回。当调用请求数据块时,要为这个参数指定具体的值,以便当数据不存在时,请求数据块可以返回该参数的值。

在“开车不发短信”应用中,当应用启动时,利用屏幕初始化程序加载数据。如图22-2所示。

这里将请求数据块的返回值写入到回复内容标签的显示文本属性中。如果数据库中已经保存过数据,则将请求获得的数据写入回复内容标签中;如果没有与标记“自定义短信”相对应的数据,则将“我正在开车...”写入回复内容标签中。

图22-2 通常在应用启动时,从数据库中读取信息

网络数据库及数据共享

本地数据库组件将数据保存在安卓设备的数据库中,这一点适用于那些不需要数据共享的个人应用。例如,可能有很多人下载“开车不发短信”应用,但每个人都使用自己特有的自动回复信息,这类信息不需要与其他人共享。

当然,还有许多应用根本离不开数据共享,如Facebook、Twitter以及那些多人网络游戏等{![它们被统称为网络应用。——译者注]}。与这些应用协同工作的数据库必须运行在互联网上,而非终端设备上,只有这样,不同用户之间才能访问并共享数据库中的信息。

网络数据库相当于部署在互联网上的本地数据库,可以将应用中的数据保存到互联网上。App Inventor提供的网络数据库组件,用于在应用中访问部署在互联网上的数据库。与本地数据库一样,网络数据库组件也提供了两个内置的功能模块——保存数据块及请求数据块,它们的功能也与本地数据库中对应的块相似。

默认情况下,网络数据库组件将数据保存到一个特定的数据库中,这个数据库由App Inventor团队创建,网址是http://appinvtinywebdb.appspot.com 。这个网站中部署了一个数据库服务器,能够响应来自网络的数据请求。所谓数据请求,就是由用户提交的保存及提取数据的指令。此外,这个网站还为开发者提供了一个用户界面,可以借此查询那些已经保存过的数据。

这个默认的数据库并非一个商用的数据库,它分配给每个App Inventor开发者的权限和存储空间都是有限的,仅适用于应用的开发及测试。由于所有的App Inventor应用都可以使用该数据库,因此不能确保你的数据不被其他的应用所覆盖。

如果你只是在学习使用App Inventor,或者处在项目的早期阶段,默认的数据库就足够了,但是,如果你想创建正式发布的应用,从某些方面来说,你需要建立自己的数据库。由于本书中的例子都是用来学习的,因此我们一直都在使用默认的数据库。在本章的后面我们还将学习如何在互联网上创建自己专属的数据库,并进行相关的设置,来替代默认的数据库。

在这一节中,我们通过一个投票应用(如图22-3所示)来介绍网络数据库组件的使用方法。该应用的具体功能如下。

  • 每次应用启动之后,提示用户输入自己的Email地址,该地址既代表用户的身份(用户名),又是向数据库中保存投票信息时使用的标记。
  • 任何时候用户都可以重新投票,这时,原有的投票内容将被覆盖。
  • 用户可以看到群组中每个人的投票结果。
  • 为简单起见,需要投票的议题将在应用之外发布,如课堂上,教师宣布议题并要求每个学生进行电子投票。(其实这个例子的功能可以扩展,允许使用者在应用中发布投票议题,并向投票人显示该议题。)
图22-3 投票应用:将投票结果保存到网络数据库中

用网络数据库组件保存数据

与本地数据库组件相同,网络数据库组件也具有保存数据功能,只是数据要保存到互联网上。在这个投票的例子中,假设用户会在投票输入框中输入投票内容并点击投票按钮发送投票结果。投票结果将保存到网络数据库中,以便其他人也能看到。我们将编写投票按钮的点击事件处理程序来实现这一功能,如图22-4所示。

图22-4 将投票结果保存到网络数据库中

在用网络数据库组件保存投票信息时,使用的标记是用户的Email地址,Email地址事先已经被保存到变量“我的邮箱”中(稍后将看到);而要保存的数据是用户在投票输入框中填写的内容。现在假设用户的Email地址是“joe@zmail.com”,而他投票的内容是“比萨饼”,则保存到数据库中的信息如表22-2所示。

表22-2 保存到数据库中的投票信息

标记 数值
joe@zmail.com 比萨饼

通过互联网,网络数据库组件的保存数据块将上述标记-数值对发往数据库服务器,服务器的网址为http://appinvtinywebdb.appspot.com 。在你测试应用时,可以访问该网址,点击getValue链接,并输入刚刚保存数据时使用的标记,网页上将显示你刚刚保存的投票结果。

请求数据及数据处理

与本地数据库组件相比,用网络数据库组件提取数据的过程要复杂一些。由于本地数据库的请求数据操作是直接与安卓设备上的数据库通信,因而可以立即获得返回值。但网络数据库组件在请求数据时,数据要在互联网上进行传输,这需要一点时间,因此请求数据的操作要分两步来实现。

在调用网络数据库组件的请求数据块时,其实只是向数据库发出数据请求,正如这个块被称作“请求数据”块,因为它只是发送请求,并没有立即获得数据库的返回值。这是本地数据库组件与网络数据库组件最大的差别,图22-5中显示了两个数据库组件的请求数据块的差别。

图22-5 比较两个数据库组件的请求数据块

本地数据库组件的请求数据块立即得到返回值,因此该块的左侧有一个插头以便将返回值保存到一个变量或组件的属性中;而网络数据库组件的请求数据块不能立即得到返回值,因此左侧没有插头。

对网络数据库组件而言,当数据库服务器回应了数据请求,并将数据返回给设备时,将触发该组件的获得数据事件。因此整个提取数据的过程分为两步,首先是调用请求数据块,然后再编写获得数据事件处理程序,来处理实际接收到的数据。获得数据事件处理程序也被称作回调过程,因为实际上是某些外部实体(这里指的是位于互联网上的数据库服务器)在处理完你的请求之后,反过来调用你的程序。就像在一家繁忙的咖啡店点餐一样:你点餐,然后等待,直到咖啡师喊你的名字,你才能真正拿到你的饮料。与此同时,咖啡师也会按顺序从每个人手里收取点餐单(而且所有人都在等待有人喊自己的名字)。

请求-获得联动机制

在我们的例子中,需要保存并提取一个投票者列表,因为应用最终要借助于每个投票人的Email地址,来获取他们的的投票结果。

要想从数据库中提取数据,最简单的方法是在应用启动时,在Screen1的初始化事件中,向数据库服务器发出请求。如图22-6所示(在本例中,用“投票者列表”为标记向数据库服务器发出请求。)

图22-6 在屏幕初始化时请求数据

当投票者列表的数据从数据库返回时,将触发网络数据库组件的获得数据事件。图22-7显示了处理这个返回列表的块。

图22-7 在获得数据事件中处理返回的投票者列表

获得数据事件携带了参数“数据”,这正是我们向数据库请求的数据。像“数据”这样的参数,可以理解为事件处理程序中的局部变量,它们只在该事件处理程序范围内有效,而无法在其他事件处理程序中使用。

事件参数不具有全局性,所以如果你想在应用中随时随地使用该参数,就需要将其转移到一个全局变量中。在这个例子中,事件处理程序的第一个任务就是将这个返回的“数据”转移到变量“投票者列表”中,这意味着你可以在其他事件处理程序中使用它。

通常会在获得数据事件中使用“如果”块,原因是,如果数据库中不存在被请求的数据,则将返回空文本(“”)。通常这种情况发生在第一次启动应用时。通过判断“数据”是否为列表,可以确定是否真的有数据返回。如果“数据”为空(如果块的测试结果为假),就不必将其写入变量投票者列表了。

复杂的“请求-获取”联动举例

在相对简单的应用中,图22-7中所示的代码是一种不错的提取数据的方式,但在投票的例子中,我们需要更为复杂的逻辑。说明如下。

  • 应用启动时,程序会提示用户输入Email地址。可以使用对话框组件弹出窗口来实现这一功能(对话框组件在设计视图中组件面板的用户界面分组中)。当用户输入Email之后,将其保存为全局变量。
  • 在确定用户输入了Email地址之后,调用请求数据块来提取投票者列表。你能说出为什么吗?

为了便于读者理解后面的程序,这里译者添加了一组代码块。如图22-7-1所示,在屏幕初始化事件中,调用对话框组件的“显示文本对话框”块,该块在手机上的运行效果如图22-7-2所示。对话框没有设置“返回”按钮,只保留了一个“确定”按钮,目的是要求用户必须输入内容,否则将不允许提交投票。不过遗憾的是,目前对话框组件还做不到这一点。如果用户不输入任何内容直接点击确定按钮,则返回的结果是“(返回)”这样的文本。有兴趣的读者可以自己试试看。

图22-7-1 在屏幕初始化时调用对话框1的显示文本对话框功能
图22-7-2 对话框组件的实际运行效果

在图22-8中显示了向数据库请求数据的更为复杂的方法。

图22-8 在获得用户的Email之后调用请求数据块(不在屏幕初始化时调用)

在应用启动时(屏幕初始化事件中),利用对话框组件提示用户输入自己的Email地址;用户完成输入后,将触发对话框组件的完成输入事件;输入的信息被保存到变量中,同时显示在标签中;然后调用请求数据块来提取投票者列表。需要注意,这里没有在屏幕初始化事件中直接调用请求数据块,是因为需要首先设置用户的Email地址。

再来概述一下上述代码的功能:在应用初始化时,提示用户输入Email地址,然后以“投票者列表”为标记调用请求数据块。当请求的数据从远程数据库中返回时,触发获得数据事件。以下是后续功能的描述。

  • 在获得数据事件中,判断收到的数据是否为列表(如果已经有人使用过这个应用,将会创建投票者列表)。如果返回值中包含数据(投票者列表),则判断当前用户的Email是否已经在投票者列表中;如果没有,将其添加至列表,并将更新后的投票者列表保存到数据库中。
  • 如果数据库中没有投票者列表,我们将以此用户的Email作为唯一的项来创建投票者列表。

图22-9中显示了实现上述功能所需要的块。

图22-9 在获得数据事件中处理数据库的返回值,根据不同的返回结果执行不同的操作

在这些块中,第一个“如果...则...否则”块使用“是列表”块来判断数据库返回的数据是否为列表。如果判断结果为真,将返回的数据放入变量投票者列表中。切记,投票者列表中只有全体使用者的Email地址,但我们不确定当前用户是否也在此列表中,因此需要进一步判断:如果当前用户不在列表中,则用添加列表项块将其添加至列表,并将更新后的列表保存到数据库中。

如果数据库返回的结果不是列表,则执行否则分支;这说明还没有人使用过这个应用。此时需要使用“列表”块来设置投票者列表,将当前用户的Email地址作为列表的第一项,然后将这个只有一项的投票者列表保存到数据库中(同时也希望更多人加入!)。

多标记同时请求数据

到目前为止,投票应用还只能处理一个用户列表。每个用户都可以看到其他用户的Email地址,但还不能提取并显示每个用户的投票结果。

在此前设定的投票按钮点击事件中,将用户的投票结果以标记-数值对的方式保存到网络数据库中,其中的标记设置为用户的Email地址,数值设置为用户的投票结果。此时如果已经有两个人投票,那么相应的数据库中可能保存了类似表22-3中的数据。

表22-3 存储在数据库中的标记-数值对

标记 数值
投票者列表 [bill@zmail.com, joe@zmail.com]
bill@zmail.com 热狗
joe@zmail.com 比萨饼

当用户点击投票结果按钮时,应用将从数据库中提取所有投票结果并加以显示。现在假设已经从数据库中提取了投票者列表,并保存到全局变量投票者列表中,我们可以使用遍历列表循环,以每个投票人的Email地址为标记,来请求每个投票人的投票结果,如图22-10所示。

图22-10 使用遍历列表块请求列表中每位成员的投票结果

这里将变量“投票结果”列表设为空列表,以便将数据库返回的最新投票结果添加到列表中。在遍历列表循环中,使用网络数据库组件的请求数据块来处理列表中的每个列表项——Email,以Email的值为标记向数据库发送请求。需要注意的是,这一系列的请求发出之后,要等到数据库返回数据时,才能触发获得数据事件,继而才能将投票结果添加到“投票结果”列表中。

我们希望在应用中显示投票结果,不过事情变得有点复杂。在点击投票结果按钮发出若干个数据请求之后,将从数据库返回多个数据,这将多次触发获得数据事件。每次返回的数据中,都包含了两个信息:投票人的Email地址及投票结果,我们需要在获得数据事件的处理程序中来解析这些数据。在一个应用中,如果同时向数据库发出多个数据请求,而每次请求的标记不相同,这时需要在同一个获得数据事件的处理程序中,处理所有请求的返回值。(你可能想到编写多个获得数据事件处理程序,来分别处理每个请求。知道为什么这样做行不通吗?)

为了处理这种复杂的情况,获得数据事件处理程序需要利用事件所携带的参数“数据标记”,它会告诉你当前的返回值来自于哪一个请求。因此,如果标记是“投票者列表”,我们可以像之前那样进行处理;如果不是“投票者列表”,我们可以假设它是用户列表中某人的Email地址,是用户点击了投票结果按钮所发请求的返回值。当这些请求返回时,我们希望将返回的数据——投票人及投票结果——添加到投票结果列表中,以便向用户显示。

图22-11中显示了整个获得数据事件处理程序。

图22-11 网络数据库组件的获得数据事件处理程序

设置专属的网络数据库

本章前面提到过,设立于http://appinvtinywebdb.appspot.com 的默认数据库仅供原型设计以及应用的测试。在向真实用户正式发布应用之前,你需要为应用创建一个专属的数据库。

访问网站http://appinventorapi.com/create-a-web-database-python-2-7 ,按照上面的说明就可以创建自己的网络数据库。该网站由本书的作者之一Wolber教授创建,网站提供了示例程序,并具体说明了如何设置App Inventor网络数据库以及API。你可以从网站上下载相关的程序,并按照说明对配置文件进行少量修改,然后就可以使用了。经过设置的代码与之前使用的App Inventor默认数据库相同,它运行在谷歌的云服务上,你可以将自己的网络数据库免费部署在谷歌云服务上(直到有一天你的网站访问量迅速飙升时)。这样,你就建起了属于自己的部署在互联网上的数据库,而且与App Inventor的协议兼容,几分钟就可以运行起来,并可以用它来创建基于网络的移动应用。

一旦创建并部署了属于自己的数据库,谷歌的云服务工具会分配给你一个网址,并通过这个网址来访问你的数据库。于是你可以将应用中网络数据库组件的服务地址属性设置为这个网址,而不再是默认的网址(http://appinvtinywebdb.appspot.com )。从此以后,当你在应用中使用网络数据库组件保存及请求数据的时候,与应用进行对接的将是你自己的新数据库。

小结

利用本地数据库及网络数据库组件,App Inventor很容易实现数据的永久存储。数据以标记-数值对的方式存储。保存数据时使用的标记也用于之后对数据的提取。本地数据库组件用于将数据直接保存在设备上。当数据需要在手机之间分享时(如多人游戏或投票应用),就需要使用网络数据库组件。网络数据库组件的使用更为复杂,尤其在获取数据的环节,首先需要发出数据请求,然后再设置回调过程,即获得数据事件处理程序,同时还要设置网络数据库组件的服务地址属性。