数据存储组件
目录:
云数据库
云数据库是一个不可见组件,允许您将数据存储在连接到互联网的数据库服务器上(使用Redis),这样你的App上所有用户就能共享数据。 默认情况下,数据将存储在 MIT 维护的服务器中,但是您可以设置和运行自己的服务器。 设置服务地址属性和服务端口属性以访问您自己的服务器。
属性
- 项目编号
- 获取此云数据库项目的编号。
- 服务端口
- 要使用的Redis服务器端口,默认为6381。
- 服务地址
- 用于存储数据的Redis服务器地址,“DEFAULT”表示默认使用MIT服务器。
- 令牌
- 此字段包含用于登录到支持的Redis服务器的身份验证令牌。 如果上面服务地址设置“DEFAULT”的话,这个值请不要编辑,系统会自动填上。 一个系统管理员还可以为您提供一个特殊值,可用于在彼此之间共享数据来自多人的多个项目。如果使用您自己的Redis服务器,请在服务器的配置并在此处输入。
- 使用SSL
- 设置为
真则使用SSL加密通道与云数据库/Redis服务器通信。如果上面服务地址设置“DEFAULT”的话,这个应该设置为真。
事件
- 云数据库错误(消息)
- 表示与云数据库Redis服务器通信时发生错误。
- 数据发生变化(标签,值)
- 表示云数据库项目中的数据发生了变化,事件触发时
标签已被更新成最新的值。 - 第一项已删除(值)
- 由
从列表中删除第一项方法触发的事件。参数值是列表中第一个对象,现在已被删除。 - 已获得值(标签,值)
- 指示
获取值请求已成功。 - 收到标签列表(值)
- 当收到已知标签列表时触发事件,是对
获取标签列表方法调用的响应。 - 更新完成(标签,操作)
- 表示将数据存储到云数据库的操作已完成。
方法
- 追加值到列表(标签,待添加项)
- 以原子(Atomic)方式将值附加到列表末尾。如果两个设备同时使用此功能,两个设备都会被追加并且不会丢失数据。
- 清除标签(标签)
- 从云数据库中删除标签。
- 云服务已连接()
- 如果在网络上并且能够连接到云数据库服务器,则返回
真。 - 获取标签列表()
- 要求云数据库检索属于该项目的所有标签。 结果列表在事件
收到标签列表中返回。 - 获取值(标签,无标签时返回值)
- 要求云数据库获取存储在给定标签下的值。
它将结果传递给
已获得值中给出。 - 从列表中删除第一项(标签)
- 获取列表的第一个元素并自动删除它。 如果两个设备同时使用此功能,一个将获取第一个元素,另一个将获取第二个元素,如果没有可用元素,则会出现错误。 当元素可用时,将触发
第一项已删除事件。 - 保存值(标签,待存储值)
- 要求云数据库将给定的
待存储值存储在给定的标签下。
加密签名
不可见组件:常用的摘要(MD5、SHA1、SHA256)、HMAC-SHA256 签名、AES 加解密、Base64 编解码和随机字符串。 调用各家接口时的参数签名、提交前把密码转成摘要、把敏感数据加密后再保存,都用它。
所有方法直接返回结果文本;文本一律按 UTF-8 处理。出错(如密钥长度不对、密文损坏)时返回空文本, 并触发屏幕的「出现错误时」事件,原因用中文说明。结果与 openssl、Python 等常用工具一致,可与服务器端互通。
使用指导密码摘要、接口签名、AES 加密保存、Basic 认证、与服务器互通的完整案例查看 →
快速上手
接口签名:把参数按约定拼成一串,用密钥做 HMAC-SHA256(很多接口要求大写,再套一层转大写):
when Button1.Click() {
var ts = Clock1.SystemTime()
Web1.Url = join("https://example.com/api?appid=10086&ts=", ts, "&sign=",
upcase(Crypto1.HmacSHA256(join("appid=10086&ts=", ts), "my-secret")))
Web1.Get()
}
AES 加密保存,读取时解密(密钥 16 个字符、IV 16 个字符,即 AES-128-CBC):
when Button1.Click() {
TinyDB1.StoreValue("note", Crypto1.AesEncrypt(TextBox1.Text, "1234567890123456", "abcdefghijklmnop"))
}
when Button2.Click() {
TextBox1.Text = Crypto1.AesDecrypt(TinyDB1.GetValue("note", ""), "1234567890123456", "abcdefghijklmnop")
}
属性
- 摘要格式
- 摘要与 HMAC 的输出格式:
hex(小写十六进制,默认)或base64(部分云服务的签名要求 Base64)。 需要大写十六进制时用文本的「转大写」。
方法
- AES解密(密文,键,IV)
- 解密 AES加密 得到的 Base64 密文,返回明文。密钥与 IV 必须与加密时相同, 不对时返回空文本并报「解密失败:密钥或 IV 不对,或密文已损坏」。
- AES加密(文本,键,IV)
- AES 加密,返回 Base64 密文(不换行)。
密钥为 16、24 或 32 个字符,分别对应 AES-128/192/256;IV填 16 个字符时用 CBC 模式,留空时用 ECB 模式;填充方式为 PKCS5Padding(与 PKCS7 等价)。 与服务器对接时,双方的密钥、IV、模式要一致。 - Base64解码(Base64文本)
- 把 Base64 解码为文本(UTF-8)。不是有效的 Base64 时返回空文本。
- Base64编码(文本)
- 把文本按 UTF-8 编码为 Base64(不换行)。图片与 Base64 互转请用图片处理。
- HmacSHA256签名(文本,键)
- 用密钥对文本做 HMAC-SHA256 签名,调用各家接口签名最常用。输出格式见摘要格式。
- MD5摘要(文本)
- 计算 MD5 摘要(32 位十六进制)。常用于接口签名、文件校验;MD5 已不适合单独用来保存密码。
- 随机字符串(长度)
- 生成指定长度(1~1024)的随机字符串,只含大小写字母和数字,使用安全随机数。 可用作 AES 密钥、IV,或接口签名要求的随机串(nonce)。
- SHA1摘要(文本)
- 计算 SHA1 摘要(40 位十六进制)。
- SHA256摘要(文本)
- 计算 SHA256 摘要(64 位十六进制)。
数据文件
不可见组件,用于读取 CSV 和 JSON 数据格式的文件,提供各个维度的列表数据,便于解析出我们想要的数据,也可以作为其他组件的数据源。
属性
- 列名列表
- 获取当前已加载的源文件的列名列表。
- 对于 CSV 文件,将返回第一行的数据列表。
- 对于 JSON 文件,将返回 JSON 对象中的键列表。
- 列数据
- 获取当前已加载的源文件的列数据列表。
- 默认作用域
- 指定使用
数据文件组件访问文件的默认作用域。App作用域适用于大多数应用程序。兼容模式可用于旧的应用程序(新约束之前)Android 上的文件访问。 - 行数据
- 获取当前已加载的源文件的行数据列表。
- 源文件
- 设置数据解析的源文件,然后异步解析文件。结果存储在
列数据、行数据及列名列表属性中。文件格式为 CSV 或 JSON 格式。
事件
无
方法
- 读取文件(文件名)
- 开始加载数据源文件,文件内容的格式是 CSV 或 JSON。
- 在
文件名前加上/来读取SD 卡上的特定文件(例如,/myFile.txt将读取该文件/sdcard/myFile.txt)。 - 读取应用程序打包的资源(也适用于AI伴侣),
文件名以//(两个斜杠)开始。 - 如果一个
文件名不以/开头,打包的应用程序会从应用程序的私有存储读取,AI伴侣则是/sdcard/AppInventor/data目录。
- 在
文件管理器
不可见组件,用于写入或读取设备上的文件,外部文件的路径均由作用域 属性指定,不论应用程序是AI伴侣运行还是已编译、以及应用运行的 Android 版本。
由于较新版本的 Android 要求将文件存储在App特定目录中,因此 默认作用域 设置为 App,如果使用的是旧版 Android 并且需要访问兼容的公共存储,将 默认作用域 属性更改为兼容,当然你也可以使用代码块来修改作用域属性。
下面是每种作用域类型的简述:
-
App
[推荐]:Android 2.2及更高版本上文件将从应用程序特定存储中读取和写入,在 Android 早期版本上,文件将写入兼容存储中。-
App的根目录为:
/storage/emulated/0/Android/data。读写文件在指定的files目录下,如图:
(上面是AI伴侣的App目录,如果最终编译apk运行,则到
appinventor.ai_[账户名].[项目名]目录下查看文件) -
写入文件的参考代码如下:

-
生成的文件如下:

-
- 程序包 :从应用程序包中读取文件,应用程序包属于只读存储,不可写入。
- 缓存 :文件将从应用程序的缓存目录读取和写入,可以在缓存中重新创建临时文件,也允许用户清理临时文件以重新获得存储空间。
- 兼容 :文件将使用 App Inventor 在nb187版本之前的规则从文件系统读取和写入,也就是说,将从中读取以单个
/开头的文件名写入外部存储目录的根目录,例如/sdcard/。 兼容功能将无法在 Android 11 或更高版本上运行。 中文网注:我们与MIT官方最新版本一样,出于安全性考虑,不支持直接从根目录访问文件,如/sdcard/,推荐使用App模式。 -
私有 :文件将从应用程序的私有目录读取和写入,使用这个作用域存储的数据对其他App不可见。 与App模式类似,读写文件的目录在
files的data子目录下:
- 共享 :文件将从设备的共享媒体目录中读取和写入,例如
图片目录。
- 注1:在
兼容模式下,文件名可以采用以下三种形式之一:- 私有文件:没有前导
/,写入应用程序私有存储(例如,file.txt) - 外部文件:有一个前导的
/,写入公共存储(例如,/file.txt) - 应用程序包:有两个前导的
//,只能读取(例如,//file.txt)
- 私有文件:没有前导
- 注2:在所有作用域内,以两个斜杠 (
//) 开头的文件名是程序包中的文件,只读,不可写。
属性
- 默认作用域
- 指定使用
文件管理器组件访问文件的默认作用域,不指定默认私有。 - 读权限
- 仅用于“界面设计”视图的属性,用于启用
App作用域之外的文件的读取权限。 - 作用域
- 表示
读取文件和保存文件等操作的当前作用域。 - 写权限
- 仅用于“界面设计”视图的属性,用于启用
App作用域之外的文件的写入权限。
事件
- 文件存储完毕(文件名)
- 当文件内容已被写入完成后,触发该事件。
- 获得文本(文本)
- 当文件内容已被读取完成后,触发该事件。
方法
- 追加内容(文本,文件名)
- 将文本追加写入到文件末尾。如果文件不存在,则创建该文件。查看
保存文件了解有关文件写入位置的信息。写入成功后,将触发
文件存储完毕事件。 - 拷贝文件(源作用域,源文件名,目标作用域,目标文件名)
- 将第一个文件的内容复制到第二个文件。
- 删除(文件名)
- 从存储中删除文件。
-
文件名以/开头的是用来删除特定的SD卡中的文件(例如,/myFile.txt将读取该文件/sdcard/myFile.txt)。 -
文件名开头没有/,则删除位于程序的私有存储中文件。 -
以
//(双斜杠)开头的文件名是程序包资产文件,是只读的,无法删除会报错。
-
- 是否存在(作用域,路径)
- 测试在指定作用域内给出的路径是否存在。
- 是否是目录(作用域,路径)
- 测试在指定作用域内给出的路径是否是目录。
- 列出目录(作用域,目录名称)
- 获取给定目录中的文件和目录列表。
- 创建目录(作用域,目录名称)
- 创建一个新目录,只要在完成时目录存在,就返回
真,也就是创建之前目录已经存在的话,也是返回真。 - 制作完整路径(作用域,路径)
- 将作用域和路径转换为单个字符串形式的完整路径,便于其他组件使用。
关于Android存储路径更深入请参考《Android存储系统基础知识:内部存储,外部存储,App特定目录ASD 及 getASD代码实现》。
- 移动文件(源作用域,源文件名,目标作用域,目标文件名)
- 将文件从一个位置移动到另一个位置。
- 读取文件(文件名)
- 从存储中的文件中读取文本。
-
文件名以/开头的是用来读取 SD 卡上的特定文件(例如,/myFile.txt将读取该文件/sdcard/myFile.txt)。 -
以
//(双斜杠)开头的文件名是读取应用程序打包的资源(也适用于AI伴侣)。 -
文件名开头没有/,它将从应用程序的私有存储中读取文件。
-
- 删除目录(作用域,目录名称,递归)
- 从文件系统中删除目录。如果
递归为真,所有内容都将被删除;如果递归为假,则只有该目录为空时才能被删除。 - 保存文件(文本,文件名)
- 将文本保存到文件中。
-
文件名以/开头则该文件是写入 SD 卡(例如,写入/myFile.txt会将文件写入/sdcard/myFile.txt)。 -
文件名开头没有/,它将是写入程序的私有数据目录中,其他人无法访问该目录手机上的程序。 -
AI伴侣较为特殊,它作为一个独立的App拥有一个私有目录,但是在测试多个项目时,由于这些App都是运行在AI伴侣的App上,因此会共用AI伴侣的私有目录,当这些程序编译后独立运行,则私有目录就各自独立,互不干扰了。
请注意:如果文件已存在,此块将覆盖该文件。如果你想给文件添加内容请使用
追加内容方法添加到现有文件。 -
文件增强
不可见组件:功能完整的文件工具箱。基于广受欢迎的 FileTools 拓展(v10)内化为原生组件并更名「文件增强」,
方法名与原拓展一致,新项目无需再导入 .aix;同时修复了原拓展完成事件不触发、Android 10 以上路径解析等问题。
功能亮点
- 文件操作:复制、移动、删除(目录连同内容一起删)、重命名、创建目录、判断是否存在,结果直接返回真假。
- 大文件不卡界面:复制文件、列目录有「异步」版本,完成后在对应事件里拿结果。
- 目录浏览:列出目录下的文件(可按扩展名过滤,如
"jpg,png",可递归子目录、可包含文件夹)、只列子文件夹。 - 文件信息:文件或文件夹大小、最后修改时间(自定义日期格式)、MIME 类型。
- 存储与路径:应用专属目录(无需存储权限)、剩余/总空间;
文件路径与
content://URI 互转,把相册、文件选择器返回的 URI 变成可直接使用的文件路径。 - 素材读取:以
//开头的路径指向应用素材(如//data.json),打包 APK 和 AI伴侣 调试里都能用。
需要在桌面创建快捷方式请用应用工具组件。
路径写法:/storage、/sdcard、/data、/mnt 开头的系统路径原样使用;其它路径(无论是否以 / 开头)都从外部存储根目录算起;// 开头为应用素材。
使用指导文件增强工具:复制、移动、删除、重命名文件,列出目录内容,查询容量与路径等查看 →
快速上手
- 拖一个 文件增强 到屏幕(不可见组件,位于「数据存储」分类);
- 下面是最常用的「异步列目录 + 异步复制」模式:目录里文件多时用异步版本,完成后在事件里取结果,避免卡界面。
when 按钮1.Click() {
文件增强1.FileListAsync(文件增强1.ApplicationSpecificDirectory(), "txt", true, false)
}
when 文件增强1.GotFileList(文件列表) {
标签1.Text = join("共找到 ", length(文件列表), " 个文件")
}
when 文件增强1.FileCopied(是否成功, 响应) {
if 是否成功 {
标签1.Text = "复制完成"
} else {
标签1.Text = join("复制失败:", 响应)
}
}
复制、移动、创建目录、删除都直接返回真假,可以接在「如果」里判断;只有大文件复制需要用 异步复制文件,在文件复制完成时事件里拿结果。
属性
无
事件
方法
- 应用专属目录()
- 返回应用专属外部目录路径(ASD)。 关于 ASD 的背景知识可参考《Android存储系统基础知识》。
- 拷贝文件(从,到)
- 复制文件,返回是否成功。大文件请改用异步复制文件,以免卡住界面。
- 异步复制文件(来源,目标)
- 在后台线程复制大文件,完成后触发文件复制完成时事件。
- 创建目录(目录)
- 创建目录(含上级目录),返回是否成功;目录已存在也返回真。
- 删除(文件或文件夹名)
- 删除文件或目录(目录连同所有子目录一起删),返回是否成功。
- 是否存在(文件名或目录名)
- 检查文件或目录是否存在。
- 异步获取文件列表(目录,过滤器,含文件夹,递归)
- 与文件列表参数相同,但在后台线程执行,避免文件过多的目录卡住界面; 结果通过获得文件列表时事件返回。
- 获取资源文件列表()
- 返回素材根目录文件列表。
- 文件或目录大小(路径)
- 返回文件或目录大小(字节)。
- 文件路径(文件名)
- 返回文件名对应的绝对路径。相对名会按应用目录解析,例如传入
mFile.txt返回/storage/emulated/0/Android/data/应用包名/files/mFile.txt。 - 文件列表(目录,过滤器,含文件夹,递归)
- 返回目录中的文件列表。
过滤器按扩展名过滤(如mp3、txt),不想过滤就传空文本;含文件夹为真时结果包含子目录;递归为真时会继续深入子目录取文件。 - 目录列表(目录)
- 返回目录下的直接子目录列表。
- 剩余空间(目录名称)
- 返回目录可用空间(字节)。
- 获取内容URI(文件名)
- 将文件名转换为 file:// 内容 URI。
- 是否文件(路径)
- 判断路径是否为文件。
- 最后修改时间(路径,格式)
- 返回文件/文件夹的最后修改时间,
格式使用 SimpleDateFormat 格式(如yyyy-MM-dd HH:mm:ss)。 - MIME类型(文件名)
- 根据文件名返回 MIME 类型,如
readme.txt返回text/plain。 - 移动文件(从,到)
- 移动文件(也可以用来改名),返回是否成功。同一存储内直接改名,大文件也很快。
- URI转路径(内容URI)
- 将内容 URI 转换回文件路径(与「获取内容URI」相反),常用于处理系统选择器返回的 URI。
- 重命名文件(文件名,新文件名)
- 重命名文件。
- 总空间(目录名称)
- 返回目录总空间(字节)。
FileTools 拓展(旧版)
FileTools 已内置为原生组件「文件增强」。新项目无需导入拓展;旧版 AIX 下载仍保留。
提供一些额外的更加强大的文件相关的操作。是 文件管理器 的加强拓展。
.aix 拓展下载:
FileTools demo程序下载:
属性
无
事件
无
方法
-

返回应用程序特定目录的路径。
-

返回可用存储目录的列表。
-

将文件从源文件夹复制到目标文件夹。
-

将文件从源异步复制到目标。使用此功能复制大文件以避免运行时错误。
-

如果应用程序特定目录不存在,则创建该目录。
ASD(app specific directory)相关知识请参考《Android存储系统基础知识:内部存储,外部存储,App特定目录ASD 及 getASD代码实现》。
-

创建一个目录。它用布尔值 true 或 false 触发“Directory Created”。
-

删除给定的文件或文件夹。如果是目录,则所有子目录将被删除,这可能需要一些时间。它会触发布尔值 true 或 false 的“FileDeleted”事件。
-


如果文件或文件夹存在则返回 true,否则返回 false。
-

从给定目录返回文件列表(如果存在)。使用文件扩展名作为过滤器,如 mp3、txt 等。如果您不想使用过滤器,则使用空字符串。另外,如果不想获取子目录,则设置 ‘ withFolders’ to false else true。如果recursive设置为true,那么它也会递归地从子目录中获取文件。
-

与 FilesList 的工作方式相同,但它异步获取文件列表,这拒绝了从具有如此多文件的目录获取文件列表时出现任何运行时错误的机会。它会引发带有文件列表的“GotFileList”事件。
-

从资产返回文件列表。
-

如果存在则返回路径中的文件名。
-

返回文件或文件夹的当前大小。
-

从文件名返回文件路径。在这种情况下,它将返回 /storage/sdcard/mFile.txt。
-

返回给定目录的文件夹列表。
-

返回目录的可用大小(以字节为单位)。注意:它使用绝对文件路径。
-

将文件路径转换为内容 uri。
-

将文件从源异步移动到目标。
-

检查给定路径是否是完整路径。例如:/testt.txt 和 /mnt/sdcard/Android/com.sunny.notez/files/testt.txt 不相同。
-

返回文件夹/文件是否可执行。
-

如果路径是文件则返回 true,否则返回 false。
-

如果文件/文件夹被隐藏则返回 true,否则返回 false。
-

如果文件/文件夹可读则返回 true,否则返回 false。
-

如果文件/文件夹可写则返回 true,否则返回 false。
-

给定格式的文件/文件夹的最后修改时间。
-

给定文件的 Mime 类型。在上述情况下,它将返回 text/plain。
-

将文件从源移动到目标并删除源文件。
-

将内容 uri 转换为文件路径。
-

重命名文件而不删除它。
-

返回目录的总空间。注意:它使用绝对文件路径。
SQLite 数据库
不可见组件,内置的迷你本地数据库引擎:兼容主流 SQL 语法,支持事务与异步执行。 现已内置到设计器的「数据存储」分类,无需再导入拓展;积木方法名与原拓展一致, 更详尽的历史图文说明保留在原拓展文档。
使用指导SQLite 数据库组件:兼容主流 SQL 语法的迷你本地数据库引擎,支持事务查看 →
快速上手:本地记事本
界面:拖入 文本输入框1、按钮1(文字改为「添加」)、列表显示框1,以及一个 SQLite(默认名 SQLite1)。
代码块:屏幕打开时建表并显示已有记录;点「添加」写入一条并刷新。
when Screen1.Initialize() {
SQLite1.OpenDatabase()
SQLite1.Execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT)", list())
刷新列表()
}
when 按钮1.Click() {
SQLite1.Insert("notes", list("content"), list(文本输入框1.Text))
文本输入框1.Text = ""
刷新列表()
}
procedure 刷新列表() {
列表显示框1.Elements = SQLite1.SelectSQL("SELECT content FROM notes ORDER BY id DESC", list())
}
运行效果:输入文字点「添加」,最新一条出现在列表顶部;退出应用再打开,记录仍在。
要点:
- 必须先打开数据库再执行 SQL,否则执行直接返回假(不报错)。
- 插入的第 3 个参数是一行的值(与列名一一对应的扁平列表),不要再套一层列表。
- SQL查询返回「行的列表」:只查一列时每行直接就是该列的值(上例可直接设给列表显示框);
查多列时每行是一个列表,按列序号取值(
行[1]为第一列)。 - 数据量大时改用带「异步」的版本(如 SQL查询(异步)),结果在对应的「完成时」事件里拿,避免卡界面。
属性
- 数据库名
- 数据库文件的名称,默认
db.sqlite3。 - 数据库版本
- 数据库版本号,默认 3。修改后打开数据库会触发数据库升级时事件, 用于增删改表结构。
- 返回列名
- 查询结果里是否包含列名,默认不包含。开启后每个列值为 [列名, 值] 形式的两元素列表。
事件
- 数据库创建时()
- 数据库文件首次创建时触发。
- 数据库升级时(旧版本,新版本)
- 数据库版本号升高时触发,可在事件里按版本差异修改表结构。
- SQL出错时(消息)
- 执行 SQL、导入导出、打开数据库等任何操作出错时触发,
消息给出具体错误内容。建议总是处理这个事件,便于排查问题。 - 执行完成时(标签,返回结果)
- 异步执行完成后触发,返回调用时指定的标签与执行结果。
- 查询完成时(标签,行数,行数据)
- 异步查询完成后触发,返回标签、行数与结果行数据。
方法
数据库与表管理
- 打开数据库()
- 打开数据库,若文件不存在会自动创建。已打开时本调用无任何效果。
- 关闭数据库()
- 关闭数据库。已关闭时无任何操作;未提交的事务会被回滚。
- 数据库是否打开()
- 数据库已打开时返回真。
- 数据库是否存在()
- 数据库文件存在时返回真。
- 数据库路径()
- 返回数据库的完整路径(无论文件是否存在)。
- 删除数据库()
- 永久删除已关闭的数据库文件。
- 导入数据库(文件名)
- 导入一个 SQLite 数据库文件,完全替换当前已关闭的数据库。成功返回真。
- 导出数据库(文件名)
- 把已关闭的数据库导出为完整的 SQLite 数据库文件。成功返回真。
- 全部表名()
- 返回所有表名组成的列表。出错或未打开时返回空列表。
- 表是否存在(表名)
- 指定表存在时返回真。
- 表行数(表名)
- 返回表中的行数。出错或未打开时返回 -1。
事务
- 开始事务()
- 在打开的数据库上开始一个事务,支持嵌套。
- 提交事务()
- 提交最后一次打开的事务。
- 回滚事务()
- 回滚最后一次打开的事务。
执行 SQL(非查询)
- 执行(SQL语句,绑定参数)
- 执行单条参数化 SQL(建表、插入、更新、删除等非 SELECT 语句),返回是否成功。 每个绑定参数替换 SQL 里对应的问号。
- 异步执行(标签,SQL语句,绑定参数)
- 异步执行单条参数化 SQL,完成后触发执行完成时事件。
- 执行文件(文件名)
- 执行文件中的多条 SQL 语句,返回成功执行的数量。文件名以
//开头表示工程素材。
查询
- SQL查询(SQL语句,绑定参数)
- 执行一条带参数的 SELECT 语句并返回记录列表。只查一列时每个元素就是该列的值; 查多列时每个元素本身是各列值的列表。未打开时返回空列表。
- 异步SQL查询(标签,SQL语句,绑定参数)
- 异步执行一条带参数的 SELECT 语句,完成后触发查询完成时事件。
增删改
- 插入(表名,列列表,值)
- 执行 INSERT 语句,返回新行的行 ID;出错或未打开时返回 -1。
- 插入文件(表名,文件名)
- 从文件批量插入多行数据,返回插入的行数。文件第一行应为逗号分隔的列名, 其余每行是对应的值。
- 替换(表名,列列表,值)
- 执行 REPLACE 语句(按主键存在则替换、不存在则插入),返回行 ID。
- 更新(表名,列列表,值,WHERE条件,绑定参数)
- 执行 UPDATE 语句,返回受影响的行数。省略条件时更新全部行。
- 删除(表名,WHERE条件,绑定参数)
- 执行 DELETE 语句,返回受影响的行数。省略条件时删除全部行。
电子表格(依赖谷歌服务,国内无法使用)
电子表格已从组件面板隐藏:新项目请改用 WPS在线表格(国内可用,方法和事件同名)。已经用了电子表格的老项目不受影响,照常打开和运行。
电子表格是一个不可见的组件,用于存储和接收来自使用 Google Sheets API 的 Google Sheets 文档。
要使用此组件,首先必须拥有 Google Developer 帐户,然后,必须在该 Google Developer 下创建一个新项目帐户,在该项目上启用 Google Sheets API,最后创建一个Sheets API 的服务帐户。
有关如何创建服务帐户以及在何处查找的说明使用 Google 表格组件的其他相关信息,可以在此处找到。
行号和列号是从 1 开始索引的。
属性
- ApplicationName
- 您的应用程序名称,用于进行API调用时使用。
- CredentialsJson
- 包含服务账户凭据的JSON文件
- SpreadsheetID
- 您想要编辑的Google Sheets文件的ID。您可以在Google Sheets文件的URL中找到spreadsheetID。
事件
- ErrorOccurred(errorMessage)
- 当API调用遇到错误时触发。错误详情在
errorMessage中。 - FinishedAddColumn(columnNumber)
AddColumn块的回调事件,在表格值更新后调用。同时返回新列的列号。- FinishedAddRow(rowNumber)
AddRow块的回调事件,在表格值更新后调用。同时返回新行的行号。- FinishedClearRange()
ClearRange块的回调事件,在表格值更新后调用。- FinishedRemoveColumn()
RemoveColumn块的回调事件,在表格值更新后调用。- FinishedRemoveRow()
RemoveRow块的回调事件,在表格值更新后调用。- FinishedWriteCell()
WriteCell块的回调事件,在表格值更新后调用。- FinishedWriteColumn()
WriteColumn块的回调事件,在表格值更新后调用。- FinishedWriteRange()
WriteRange块的回调事件,在表格值更新后调用。- FinishedWriteRow()
WriteRow块的回调事件,在表格值更新后调用。- GotCellData(cellData)
ReadCell块的回调事件。cellData是单元格中的文本值。- GotColumnData(columnData)
ReadColumn块的回调事件。columnData是按行号递增顺序排列的单元格文本值列表。- GotFilterResult(行号列表,行数据列表)
ReadWithQuery块的回调事件。response是满足查询条件的行列表。- GotRangeData(rangeData)
ReadRange块的回调事件。rangeData是一个行列表,其维度与rangeReference相同。- GotRowData(rowDataList)
ReadRow块的回调事件。rowDataList是按列号递增顺序排列的单元格文本值列表。- GotSheetData(sheetData)
ReadSheet块的回调事件。sheetData是一个行列表。
方法
- AddColumn(sheetName,data)
- 给定一个值列表作为
data,将这些值追加到表格的下一个空列中。总是从顶部行开始向下填充。完成后触发FinishedAddColumn回调事件。 - AddRow(sheetName,data)
- 给定一个值列表作为
data,将这些值追加到表格的下一个空行中。总是从最左侧列开始向右填充。完成后触发FinishedAddRow回调事件。同时返回新行的行号。 - ClearRange(sheetName,rangeReference)
- 清空给定范围内的单元格。完成后触发
FinishedClearRange回调事件。 - GetCellReference(行,列)
- 将行和列的整数表示转换为Google Sheets中使用的A1表示法(单个单元格)。例如,行1和列2对应字符串”B1”。
- GetRangeReference(行1,列1,行2,列2)
- 将范围四个角的行和列的整数表示转换为Google Sheets中使用的A1表示法。例如,选择从行1列2到行3列4的范围对应字符串”B1:D3”。
- ReadCell(sheetName,cellReference)
- 在指定的sheetName页面上,读取给定cellReference处的单元格,并触发
GotCellData回调事件。cellReference可以是A1表示法的文本块,或是getCellReference块的结果。 - ReadColumn(sheetName,column)
- 在指定的sheetName页面上,读取给定colNumber处的列,并触发
GotColumnData回调事件。 - ReadRange(sheetName,rangeReference)
- 在指定的sheetName页面上,读取给定rangeReference处的单元格,并触发
GotRangeData回调事件。rangeReference可以是A1表示法的文本块,或是getRangeReference块的结果。 - ReadRow(sheetName,rowNumber)
- 在指定的sheetName页面上,读取给定rowNumber处的行,并触发
GotRowData回调事件。 - ReadSheet(sheetName)
- 读取整个Google Sheets文档并触发
GotSheetData回调事件。 - ReadWithExactFilter(sheetName,colID,value)
- 筛选Google Sheet中给定列号与提供值完全匹配的行。
- ReadWithPartialFilter(sheetName,colID,value)
- 筛选Google Sheet中给定列号包含提供值字符串的行。
- RemoveColumn(sheetName,column)
- 从表格中删除指定列(column 为文本,例如 “A” 或 “1”)。这不是清空列,而是完全删除它。表格的grid id可以在Google Sheets文档URL的”gid=”后面找到。完成后触发
FinishedRemoveColumn回调事件。 - RemoveRow(sheetName,rowNumber)
- 从表格中删除指定行号(从1开始)的行。这不是清空行,而是完全删除它。表格的grid id可以在Google Sheets文档URL的”gid=”后面找到。完成后触发
FinishedRemoveRow回调事件。 - WriteCell(sheetName,cellReference,data)
- 给定文本或数字作为
data,将值写入单元格。会覆盖单元格中的现有数据。完成后触发FinishedWriteCell回调事件。 - WriteColumn(sheetName,column,data)
- 给定一个值列表作为
data,将这些值写入 column 指定的列中(文本,例如 “A” 或 “1”),从上到下覆盖现有值。(注意:不会清空整个列。)完成后触发FinishedWriteColumn回调事件。 - WriteRange(sheetName,rangeReference,data)
- 给定一个列表的列表作为
data,将这些值写入范围内的单元格。范围的行列数必须与数据的维度匹配。此方法会覆盖范围内的现有数据。完成后触发FinishedWriteRange回调事件。 - WriteRow(sheetName,rowNumber,data)
- 给定一个值列表作为
data,将这些值写入指定行号的行中,从左到右覆盖现有值。(注意:不会清空整个行。)完成后触发FinishedWriteRow回调事件。
WPS在线表格
不可见组件:读写金山文档(WPS)在线表格,国内直接可用,用来替代依赖谷歌服务的电子表格。 适合报名表、签到表、库存清单、成绩查询这类「多台手机共用一张表、电脑上也能直接打开编辑」的场景。
原理:在金山文档表格里放一段 AirScript 脚本(本页提供,整段复制即可),App 带着「脚本令牌」调用这段脚本, 由脚本在表格里完成读写。方法和事件与电子表格同名,谷歌表格做的项目换成本组件基本不用改代码块。
行号、列号都从 1 开始;读出的单元格一律是文本(数字 98 读出来是 "98",空单元格是空文本)。
写入时数字积木写成数值,文本积木写成文本(手机号用文本输入框的内容写入,不会变成科学计数法)。
使用指导WPS在线表格:通过金山文档的脚本令牌读写在线表格(国内可用,替代谷歌表格)查看 →
准备:粘贴脚本、拿到链接和令牌
- 电脑浏览器打开 金山文档,新建一个表格,把第一个工作表改名为
报名,第一行写标题:姓名、手机、时间; - 菜单「效率」→「高级开发」→「AirScript 脚本编辑器」,点左侧「文档共享脚本」旁的 + 新建脚本 (必须是「文档共享脚本」,「我的脚本」没有 webhook 链接),把下面的通用脚本整段粘贴进去并保存;
- 在左侧脚本列表里找到这个脚本,点它的「更多」→「复制 webhook 链接」,填到组件的 WebhookUrl 属性;
- 编辑器工具栏点「脚本令牌」→ 创建令牌(首次需要实名认证),复制后填到组件的 Token 属性。 令牌默认 180 天到期,创建时可以延长,到期后 App 会报「脚本令牌无效或已过期」。
通用脚本(不要修改,组件的读写方法都靠它):
// App Inventor「WPS在线表格」通用脚本 v1(fun123.cn):整段复制粘贴,无需修改
var a = Context.argv || {}
var s = a.sheet ? Application.Sheets.Item(a.sheet) : Application.ActiveSheet
if (!s) {
throw new Error('找不到工作表:' + a.sheet)
}
// 列号转列字母:1→A,27→AA
function col(n) {
var t = ''
while (n > 0) {
t = String.fromCharCode(65 + (n - 1) % 26) + t
n = Math.floor((n - 1) / 26)
}
return t
}
// 有数据的最后一行、最后一列(空表都是 0)
function used() {
var u = s.UsedRange
var r = u.Row + u.Rows.Count - 1
var c = u.Column + u.Columns.Count - 1
if (r === 1 && c === 1) {
var v = s.Range('A1').Value2
if (v === null || v === undefined || v === '') {
return { row: 0, col: 0 }
}
}
return { row: r, col: c }
}
// 写入区域:只有一个单元格时直接写值
function put(range, values) {
if (Array.isArray(values) && values.length === 1 && values[0].length === 1) {
values = values[0][0]
}
s.Range(range).Value2 = values
}
var u
switch (a.action) {
case 'read':
return s.Range(a.range).Value2
case 'write':
put(a.range, a.values)
return true
case 'clear':
s.Range(a.range).ClearContents()
return true
case 'readRow':
u = used()
return u.col === 0 ? [] : s.Range('A' + a.row + ':' + col(u.col) + a.row).Value2
case 'readSheet':
u = used()
return u.row === 0 ? [] : s.Range('A1:' + col(u.col) + u.row).Value2
case 'addRow':
u = used().row + 1
put('A' + u + ':' + a.lastCol + u, a.values)
return u
case 'removeRow':
s.Range('A' + a.row).EntireRow.Delete()
return true
default:
throw new Error('未知操作:' + a.action + '(这是 App Inventor 通用脚本,自定义脚本请另建一个)')
}
快速上手:在线报名表
手机填姓名和手机号提交,数据追加到金山文档的「报名」表;点「刷新」把所有人的姓名显示在列表里。 电脑上打开这张表,能实时看到手机提交的数据,也可以直接在表里改。
- 拖一个WPS在线表格,填好 WebhookUrl 和 Token(见上一节);
- 拖两个文本输入框(
文本输入框_姓名、文本输入框_手机)、两个按钮(按钮_提交、按钮_刷新)、 一个标签(标签_状态)和一个列表显示框; - 搭下面的代码块:
when 按钮_提交.Click {
var 时间 = 计时器1.FormatDateTime(计时器1.Now(), "yyyy-MM-dd HH:mm")
WPS在线表格1.AddRow("报名", [文本输入框_姓名.Text, 文本输入框_手机.Text, 时间])
标签_状态.Text = "提交中…"
}
when WPS在线表格1.FinishedAddRow(行号) {
标签_状态.Text = join("已提交,在第 ", 行号, " 行")
}
when 按钮_刷新.Click {
WPS在线表格1.ReadSheet("报名")
}
when WPS在线表格1.GotSheetData(表格数据) {
var 姓名列表 = []
foreach 行 in 表格数据 {
listAdd(姓名列表, 行[1])
}
列表显示框1.Elements = 姓名列表
}
when WPS在线表格1.ErrorOccurred(错误信息) {
标签_状态.Text = 错误信息
}
运行后提交一条,电脑上的表格里立刻多出一行;点「刷新」,列表第一项是标题「姓名」,下面是所有报名的人。
(要去掉标题行,可以在循环里跳过第 1 行,或改用 ReadRange 读 A2:A200。)
按条件查找
按列筛选与谷歌表格一致:精确匹配数据找某列等于给定文本的行, 部分匹配数据找某列包含给定文本的行,都区分大小写,返回的行号含标题行(标题行是第 1 行)。
when 按钮_查询.Click {
WPS在线表格1.ReadWithExactFilter("报名", 2, 文本输入框_手机.Text)
}
when WPS在线表格1.GotFilterResult(行号列表, 行数据列表) {
if length(行号列表) == 0 {
标签_状态.Text = "没有报名记录"
} else {
标签_状态.Text = join("已报名,姓名:", 行数据列表[1][1])
}
}
运行自己写的脚本
需要通用脚本做不到的事(如只返回某人的成绩、跨表统计)时,在同一个表格里另建一个文档共享脚本,
用它的 webhook 链接调用运行脚本:参数字典在脚本里用 Context.argv 读取,脚本 return 的值由
获得脚本结果返回(对象变字典、数组变列表)。一个组件只对应一个 webhook 链接,
通用脚本和自定义脚本要同时用时,放两个 WPS在线表格组件。
例:成绩表第 1 列是学号、第 2 列是姓名、第 3 列是分数,只按学号返回这个人的成绩:
var id = String(Context.argv.id)
var n = Application.ActiveSheet.UsedRange.Rows.Count
for (var i = 2; i <= n; i++) {
if (String(Application.ActiveSheet.Range('A' + i).Value2) === id) {
return { name: Application.ActiveSheet.Range('B' + i).Value2, score: Application.ActiveSheet.Range('C' + i).Value2 }
}
}
return {}
when 按钮_查询.Click {
WPS在线表格_查分.RunScript(makeDict(pair("id", 文本输入框_学号.Text)))
}
when WPS在线表格_查分.GotScriptResult(返回结果) {
标签_状态.Text = join(dictLookup(返回结果, "name", "查无此人"), " ", dictLookup(返回结果, "score", ""))
}
常见问题
| 现象 | 原因与处理 |
|---|---|
| 报「脚本令牌无效或已过期」 | 令牌默认 180 天到期,在脚本编辑器「脚本令牌」里延期或重建,把新令牌填回 Token |
| 报「请先填写 WebhookUrl」 | 属性没填;链接要从脚本列表「更多」→「复制 webhook 链接」复制完整 |
| 报「找不到工作表」或脚本出错 | 工作表名要和表格底部标签一字不差;工作表名留空则用当前工作表 |
| 报「未知操作」 | WebhookUrl 填的是自定义脚本的链接,读写方法要用通用脚本的链接 |
| 报「脚本返回的数据格式不对」 | 通用脚本被改过或不完整,重新整段粘贴 |
| 日期读出来是一串数字 | 表格里的日期按序号存储;写入时用文本(如 2026-09-26),读出来就是文本 |
| 每次操作要等一两秒 | 每次调用都要经过金山文档服务器执行脚本;多行数据用 写入范围 一次写完,别循环逐格写 |
| 数据安全 | 令牌和链接会打包进 App,拿到安装包的人就能读写这张表。只放可公开的数据、用专门的表格;敏感数据用自定义脚本只返回需要的部分,不要用通用脚本 |
属性
- 访问令牌
- 脚本令牌:在金山文档脚本编辑器工具栏点「脚本令牌」创建(需实名认证,默认 180 天到期,可延期)。
- Webhook链接
- 脚本的 webhook 链接:在脚本编辑器左侧脚本列表的「更多」菜单里复制,形如
https://www.kdocs.cn/api/v3/ide/file/…/script/…/sync_task。
事件
- 出现错误时(错误信息)
- 请求出错(链接或令牌不对、网络不通、工作表不存在、脚本出错等)时触发。没有处理这个事件时,错误会显示给用户。
- 添加行完成(行号)
- 添加行完成,给出新行的行号。
- 清除范围完成()
- 清除范围完成。
- 删除行完成()
- 删除行完成。
- 写入单元格完成()
- 写入单元格完成。
- 写入范围完成()
- 写入范围完成。
- 获得单元格数据(单元格数据)
- 读取单元格完成。
- 获得筛选结果(行号列表,行数据列表)
- 精确匹配数据或部分匹配数据完成:行号列表是匹配行的行号(从 1 开始,含标题行),行数据列表是这些行的数据。
- 获得范围数据(范围数据)
- 读取范围完成,数据是列表的列表(每行一个列表)。
- 获得行数据(行数据列表)
- 读取行完成,行尾的空单元格已去掉。
- 获得脚本结果(返回结果)
- 运行脚本完成,是脚本
return的值(对象变字典、数组变列表,没有 return 时为空文本)。 - 获得表格数据(表格数据)
- 读取工作表完成,数据是列表的列表,第一个列表是第 1 行。
方法
- 添加行(工作表名,数据)
- 在有数据的最后一行之后追加一行,完成后触发添加行完成。工作表名留空则用当前工作表(下同)。
- 清除范围(工作表名,范围标识)
- 清空一个范围(如
A2:C9)的内容,保留格式。 - 读取单元格(工作表名,单元格标识)
- 读取一个单元格(如
B2)。 - 读取范围(工作表名,范围标识)
- 读取一个范围(如
A1:C5)。 - 读取行(工作表名,行号)
- 读取第几行。
- 读取工作表(工作表名)
- 读取整个工作表:从 A1 到有数据的最后一行、最后一列。
- 精确匹配数据(工作表名,列ID,值)
- 找出第几列(从 1 开始)等于给定文本的行,区分大小写。
- 部分匹配数据(工作表名,列ID,值)
- 找出第几列(从 1 开始)包含给定文本的行,区分大小写。
- 删除行(工作表名,行号)
- 删除第几行,下面的行上移。
- 运行脚本(参数字典)
- 运行你自己写的 AirScript 脚本(WebhookUrl 要填那个脚本的链接),参数在脚本里用
Context.argv读取。 - 写入单元格(工作表名,单元格标识,数据)
- 写入一个单元格。
- 写入范围(工作表名,范围标识,数据)
- 把列表的列表(每行一个列表)写到范围。范围可以只写左上角单元格(如
A2),按数据的行数和列数自动扩展;行长短不一时用空文本补齐。
微数据库
微数据库是一个不可见的组件,用于存储应用程序的数据。
使用 App Inventor 创建的应用程序在每次运行时都会进行初始化,这意味着如果一个应用程序设置变量的值,然后用户退出应用程序,该变量的值将在下次运行应用程序时不会被记住。 相比之下,微数据库是一个持久化的数据存储,每次运行应用程序时,存储在“微数据库”中的数据都能被记住并获取。例如游戏中保存最高分,并在每次玩游戏时获取它。
-
数据项由标签和值组成,要存储数据项需要指定标签(标签必须是文本块,为数据命名),然后可以根据标签获取存储在该标签下的数据。
-
不能使用“微数据库”在手机上的两个不同App之间传递数据,可以在多屏应用的不同屏幕之间共享数据。
-
在使用AI伴侣开发应用时,使用该AI伴侣的所有应用都将共用一个微数据库,而一旦应用打包之后,数据的共享将不复存在。因此在开发过程中,每次创建新项目时,都需留心清空微数据库。
总结下来,就是“微数据库”只能在同一个App间共享数据,AI伴侣算一个App(所有被测试的程序均归到AI伴侣App上),程序打包编译apk后算作各自的App。
微数据库生命周期:经验证,卸载App后,本地微数据库也会被清理,App重新安装后数据从零开始;而不卸载App覆盖更新时,则不会清理微数据库中的数据!
属性
- 命名空间
- 用于存储数据的命名空间。
事件
无
方法
- 清除所有数据()
- 清除整个数据存储。
- 清除标签数据(标签)
- 清除指定
标签下的数据。 - 获取数据()
- 以字典形式获取所有数据。
- 获取标签列表()
- 返回数据存储中所有标签的列表(是一个列表对象)。
- 获取值(标签,无标签时返回值)
- 获取指定
标签下的数据,如果没有该标签,则返回无标签时返回值中指定的值。 - 保存值(标签,待存储值)
- 将
存储值保存到指定标签下,当应用程序重新启动时,存储仍然存在于手机上。 -
存储值可以是文本,也可以是数字,还可以是列表。重复保存同一个标签的话,第二次会覆盖第一次的值,即以最新存储的值为准。
知识拓展
-
微数据库的
存储值可以是列表吗?我们写了一个测试程序如下:
测试结果是:2。说明值取出后成功还原了列表,也说明列表是能够直接存储进微数据库的。
网络微数据库
网络微数据库 组件通过与Web服务通信以存储及查询数据,虽然这个组件是有用的,但是非常有限,主要是作为对那些想要创建自己的组件与 Web 对话的Demo应用。
随附的 Web 服务位于 http://tinywebdb.appinventor.mit.edu。该组件有方法保存值 和获取值 ,“保存”和“获取”的含义取决于Web服务。在目前的实现中,所有标签和值是字符串(文本),后续版本可能会放开这一限制。
中文网注:
MIT官方的功能很单一,且仅支持英文内容,不支持中文文本存储,中文文本获取出来是乱码。
目前国内也有免费的网络微数据库,支持中文存储和读取,功能上也进行了一定的拓展,详细可以去各自的网站上查看中文文档。2个网站体验差不多,网站如下:
https://tinywebdb.cn/ 经测试,单个键值的容量大小约为 64KB 字节,超过则会保存失败。
https://tinywebdb.appinventor.space/ 经测试,单个键值的容量大小约为 9000 字节(9KB),超过则会保存失败。
想用自己的电脑或云服务器保存数据,可以照着《网络微数据库后台搭建教程》自建,提供完整源码下载。
更多请参考《App Inventor 2 网络微数据库你用对了吗?》。
属性
- 服务地址
- 指定Web服务的 URL,默认值是 http://tinywebdb.appinventor.mit.edu。
事件
- 已获得值时(网络数据库标签,网络数据库值)
获取值请求服务器执行成功时触发该事件。- 值存储完毕时()
保存值请求服务器执行成功时触发该事件。- 发生Web服务故障时(消息)
- 与Web服务器的通信发出错误信号时触发该事件。
网络连接失败(包括连接被拒、DNS 失败或超时)可能返回 Communication with the web service timed out.;此提示不能仅按“服务器响应慢”理解,也需检查服务是否启动、地址和网络是否正确。HTTP 404、500 等非成功响应返回 Communication with the web service encountered a protocol exception.。两端使用相同提示,不会同时触发“已获得值”或“值存储完毕”。
该组件使用固定的标签/值协议,不支持自定义请求头,也不提供原子追加。多人同时读取列表再保存会覆盖其他人的更新;需要鉴权头或并发事务时,请使用 Web 接口及具备相应能力的服务端。
扫码添加客服咨询