【嵌入式】总结——Qt开发(四)
与前面一致,主要目的是为了快速上手体验,并没有深入研究。本篇是对初学Qt的一次总结,并非终点。
本阶段的目的是搭建桌面-应用基本框架,并开发一个“App”。正点原子给的综合例程源码使用的是QML,本篇与其一致。
人机对战时要注意,AI可以从零生成你要的效果,你让它在此基础上添加或者优化是没有任何问题的,它可以一直给你堆叠特效,但让它修改、修复或调整是很容易出现问题的。讲一个我遇到的,让它生成应用图标的样式,它做得很好,让它在此基础上增加一些点击效果,也没有任何问题。但是让它调整代码,它常常会把函数或变量的定义域搞错,把错误提示信息给它也很难解决,只能凭借人的肉眼观察。
初期,AI给你框架,让你根据这个框架来生成整个应用,但由于对话长度有限,所以不可能根据一个对话完成所有应用的设计。但初期的目的是让人跟AI学习,来掌握项目架构怎么设计,怎么模块化开发。当熟悉之后,把这个项目分解成若干模块,每个模块又可以让AI分成若干小模块 ,由于单个对话的限制,AI不可能把每个小模块是干什么的给理清,但能分成若干小模块也够了。接着可以再开个对话让AI来设计小模块
一、学习
1,基础开发框架
新学一门编程语言时,我一般会先查看已有的源码,观察它的语法,看看哪些部分可以一眼看懂是干什么的,比如变量的定义和函数的定义使用,在各个编程语言里基本大同小异。然后再询问一些不太能理解的语法,让AI举一些例子,自己尝试对已有源码改一下,观察能否达到预期效果。重头学一遍那是不可能的,仿照例子重新写一遍那也是不可能的。
改的过程中可能会遇到各种各样的问题,也会积累一些经验,见得多了不自觉就会写了
- 如果添加了新的qml想要使用它,那么必须在qrc里添加该文件,这样在别的qml文件里使用import就可以导入对应的目录了。
- 最容易出现的问题就是忘记添加qml到qrc里了,如果你发现某个自定义组件找不到,那么就看看它是否在qrc里。
- 信号与槽:子组件的信号处理过程,既可以调用本地函数也可以触发本地信号(信号传递)
- …………
在对语法有一些初步认识后(至少能分清哪些代码块大致是干什么的,不需要很清楚),就准备尝试在已有代码的基础上重新构建(原项目不删除,用于参考),即新建一个空的桌面文件(Desktop.qml),把桌面的程序接口接到这个新建的桌面文件,不做什么大的改动。
此时,我会问问AI项目怎么组织,毕竟对这门语言啥都不懂,为了便于后续扩展,项目的组织架构是尤为重要的,最好在开头就想好。
然后根据自己的理解询问一些项目的细节,不懂就问
不断讨论项目细节,比如在原先架构上,如果添加更多应用怎么办,尽可能考虑后续扩展,争取获得一个合适的项目结构
2,测试框架
接着AI会给出项目组织架构和具体的代码,但这个代码一般都很后期,虽然内容不多,但往往包含了各个门类、各种细节,会调用各种本地的资源(实际上咱才开始,自然什么都没有)。此时,我一般会给AI一个我认为合适的项目组织架构,然后让它生成个简单的测试框架,最好没有任何依赖,仅仅靠原生组件来完成设计
依照这个简单测试应用,一步一步搭建,最后搭建一个基础可用的桌面,基于此不断优化,比如下图。说着挺简单的,但实际上会出各种各样的问题,有时候还可能重新设计方案。比如管理应用时,一开始AI给的是StackView(当时还不知道Loader,以为这个与安卓开发的那个栈是一样的),但后续想要做一个返回主菜单键和退出应用键时,用起来就很麻烦。不得以向AI抱怨,得知StackView适合做单应用页面的管理,多应用导航控制用Loader更好,在项目开始时AI竟然压根没考虑到。
- 有桌面,桌面有若干页面可以通过调用函数来动态创建或删除,下面有页面指示器(简单的小圆点组成)
- 有应用,点击后可以加载对应的应用qml文件,并让桌面不可视,应用可视。同时考虑到现代手机上除了1*1的桌面图标外,还有m*n的组件。应用qml随便设计一个简单界面即可,方便测试,应用里添加了一个返回按钮(清除应用后台)
- 有顶部栏,目前只显示时间,后续可以添加CPU温度等系统信息
- Home悬浮窗,用于在任何应用界面返回到主界面(不清除后台应用)
当初以为使用ui开发更方便,毕竟可以使用图形化工具。而qml虽然也有,但用起来很卡,而且如果qml里使用了类或者跨目录的qml文件,那么很多都无法显示出来。
但后来觉得还是“对话式开发”比声明式开发、命令式开发更方便,人负责想象,AI负责生成。即便想象很贫瘠,AI也能自己生成令人较满意的UI界面,下图这个不算。
这个计算器只能看,不能用
3,整理
当测试应用框架基本差不多,可以转正时,那么就清理原项目留下来的痕迹,把没有用到的代码能删就删 。这里只清理了qrc和main里用不到的资源,其他文件不要删除,一些具体实现细节还需要参考
后面就可以参考先前代码中的关键部分,继续开发应用,做好一个删一个
4、设计应用
在框架搭建好之后,接下来只需要专注于设计具体的桌面应用。比如此时此刻,只需要考虑modules/Calculator这个目录怎么设计,不必再关心桌面切换、应用切换等(前面已经搭建好了机制)
由于是重开一个对话,那么原先关于应用的启动等机制AI是不知道的,比如下图,那么这些我们自己保留即可。
如果在一个对话里,无论怎么问,都不能解决办法的话,最好重开一个对话,并且给出关键代码(不需要包括qml等),比如设计计算器时,表达式解析出现问题
经过一系列人机对战,得到了一个计算器应用,忽略掉必要的bug外,它已经是合格的计算器应用了(按键排布得有些不顺手)。事实上如果是自己动手设计UI,我觉得自己很难设计出这种简洁大方的UI
二、开发板调试
1,上板
改了一下pro文件,现在可以在Qt上运行,方便等下连接开发板调试(话说应该把这个功能搞到CLion上)
使用Filezila传输给虚拟机
到虚拟机里,用虚拟机以前工程里的pro.user替换掉现在的,不然rsync还需要重新配(详情见上一篇)
然后配置工程,勾选桌面端是为了看看不同平台下有没有问题
显然遇到了一个小问题,难怪之前正点原子的例程源码里会有"./",当时嫌啰嗦就删了,现在看很有用嘛
遇到下面问题,后来发现是版本问题,我使用的是5.15.2,而正点原子是5.12.9(当初看错了,以为都是5.15)
尝试编译5.15.2,有各种各样的问题,但最大的问题就是Ubuntu 16现在不支持了
除此之外,有些Qt库还没有(交叉编译Qt源码跳过了,比如OpenGL)。想想还是算了,阉割一下也能用,在舍弃掉一些效果后,终于可以运行了
烧录到开发板上,开始有些问题,原桌面进程没有沙掉
沙掉后重新运行(可以使用ssh传输命令沙掉进程)。唯一让我百思不得其解的是应用图标不见了,使用起来倒很流畅(毕竟应用和功能很少)
CPU温度本来能正常显示的,但只要我在屏幕的一个地方按久了就自动变为零。这个算小bug,先放过它。
温度显示这个功能是学原例程中desktop目录下的desktop.cpp文件的,它是通过访问用户空间里的驱动实现的,控制LED也同理。
这是现桌面的开销(很简陋所以开销小)
下面是原桌面,在例程源码中可以看到它在初次加载桌面时,实际上就把所有应用都加载了。如果不全部加载的话,开销应该会小一些。
又测试了一下,发现png是能显示的(右上角),同样的图片应用图标却不显示,那应该是打开的方式出了问题
按照这个思路,觉得是应用图标的加载有问题,看到程序里的OpacityMask和QtGraphicalEffects,知道了问题所在,开发板使用的Qt库里是没有使用OpenGL的。
去掉之后,图标可以显示,与预期一致,但我的圆角没了,先后试了不使用OpenGL的方法,还是不行(x_x)
可是使用OpenGL,对于我这个一次也没能编译成功的loser来说,属实煎熬。
好像还少了一个主菜单键,查了一下原因,也是使用了OpenGL原因,去掉使用OpenGL渲染阴影的代码就能正常显示了。此外开发板上的字库好像没有“🏠”这个字符,先用Home代替吧
2、设置启动脚本
配置启动脚本,需要编辑/etc/rc.local文件
vi /etc/rc.local
脚本里的内容基本完善,开机后会运行/opt/Desktop这个程序,并设置为后台启动。由于暂时还没学如何改uboot或linux内核关于网络的设置,就用网络重启命令来把ip重置为设定的值
#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. echo 30000 > /proc/sys/vm/min_free_kbytes source /etc/profile /opt/QDesktop >/dev/null 2>&1 & # 网络重启 /etc/init.d/networking restart exit 0
网络重启要在桌面启动后面 ,如果放在前面需要加延时(sleep 3),不然桌面程序就不会运行。要注意,修改完成并保存后,要输入sync再重启,不然数据还在缓冲区里,直接重启会丢失
注意到前面我们使用rsync调试,实际上会把编译后的桌面程序传输开发板的/opt/test/bin目录里,然后运行。如果想要调试一次就让开发板之后运行桌面程序,可以改一下rsync中传输的目录,把/opt/test/bin改为/opt,不过开发板原先的Desktop程序记得备份一下。
三、项目结构
1,基础框架
接下来回头看看这个简单的项目构成,下面是此时理想中的项目结构,后续可能会根据需求改变,目前起指导作用
业务逻辑的入口main.cpp这里,现在只注册了两个类,计算器类和系统检测类,后者目前只检测了CPU温度。这里没有花什么精力,是在原例程基础上删改得到的
界面入口main.qml这里,在了解原例程中main.qml结构后,就重做了。把界面分成两个部分,一个是桌面容器,另一个是应用容器。初始状态只加载桌面,桌面的具体设计是在Desktop.qml里,应用的具体设计在AppPage.qml里。
当点击桌面上的图标后,就把桌面容器变成不可视,然后加载应用的qml文件(创建Loader),并可视,这就是应用启动的逻辑。退出应用就是让桌面可视,此时要么关闭应用示例(清后台),要么让它不可视。实际上就是把桌面和应用都当成了某个界面,没有从属关系,只是简单的并列,把桌面当成某个“应用”也无妨。
在上面两侧之外的是悬浮窗,这个位于整个界面的上层,确保无论是在桌面还是应用都不会影响它的显示。
除了上面三个和界面相关的之外,下面就是界面逻辑了,目前有启动应用、关闭当前应用和关闭所有应用,其中关闭应用又分为清理后台还是不清理后台两种。
启动应用这里,会先判断Loader里有没有这个应用,因为前面提到过,返回主菜单不清后台时,只会把这个Loader变得不可视,不会销毁它。如此,就避免了重复创建Loader实例。如果不存在,那么创建示例。
2,桌面
桌面这里,界面分为壁纸、顶部栏和页面三个部分,壁纸可以用图片或者动态渐变、粒子效果等,这里只用了简单的渐变效果。
顶部栏只是一块区域,具体的实现在TopBar.qml里
页面这里分为页面本身(SwipeView组件)和页面指示器(下面的小圆点),由于使用过程中,Repeater这个组件在跨文件时出现了问题,这里没能把页面指示器分离出一个qml文件,只能在这里了留下了臃肿的代码。
接下来就是数据模型和创建页面、删除页面的函数。前面提到,桌面和应用并不是从属关系,所以这里的数据模型并非应用本身,只是把应用的一些属性给列举出来,方便显示应用图标(由页面组件SwipeView加载)。
但也并非与应用没有任何关系,这里保存了应用的一些信息,比如加载qml的路径、大小、类型等,只在点击应用图标时,才会加载应用本体,减少不必要的开销。
3,应用图标
应用这里是分了两个qml文件,一个是决定应用图标长什么样,另一个是动态生成应用实例。
AppPage.qml在这里属于后者,在点击桌面上的应用图标后,它会根据那个应用的信息(大小、类型、qml文件路径等)来加载应用
AppIcon.qml在这里决定了桌面上的应用图标的基础通用样式——有图片,图片下面有应用名称,可点击
4,应用
前面的只是应用图标,与桌面是从属关系,这个才是应用。modules这个目录里可以放各个应用,每个应用都是一个文件夹。目前这里只有一个计算器,计算器里分为界面和逻辑。
为了模块化,界面可由多个qml文件共同构成,这里设计了计算器的按钮和计算器整体界面。应用界面是完全独立的,相当于一个新的桌面界面。
为完成交互功能,界面文件(Calculator.qml)会嵌入C++的类,通过调用这个类里的各个函数来完成交互逻辑的实现。
下面是交互逻辑的实现文件,是QObject的派生类,给qml提供各种接口使用
具体的交互逻辑实现让人头都大
5,系统
系统级应用,使用单例模式,一直在后台加载,可暴露给需要的组件。比如获取CPU温度、系统音量等。与前面应用开发里的交互逻辑实现差不多,提供各种接口
源码暂时不发,因为现在还很简陋,只有一个计算器可以使用。而且由于先前是在正点原子例程改的,会有大量文件夹,有些乱,还没整理。应该不会等很长
在Qt Creator的视角下是很整齐
在CLion这就有些乱了(真实目录)
目前搭建好了基础框架,后续可以在这个基础上开发更多应用。但应用图标不能圆角显示这个问题,可能真的需要OpenGL支持或者使用更高的Qt版本。嗯,想了想,现在这套开发流程虽然简陋,但可以胜任一些基础功能的开发,控制个灯还是没有问题的。
嗯后续可以再搭建一套开发流程,使用imx6ull官方最新的uboot和linux,以及正点原子的根文件系统制作SD卡启动,然后再尝试编译6.8.1的Qt源码,开发6.8.1下的Qt桌面程序,再接着制作自己的根文件系统。由于前面的学习并没有涉及到更改uboot、Linux内核和根文件系统,所以只要把桌面应用程序和一些驱动模块移动到EMMC上,再改一些配置文件,就可以使用了(设备树可以不动)。
我想想,如果这样的话就有了两套开发流程,一套是EMMC上,旧的开发流程,另一套是SD卡上的,新的开发流程,即便新的尝试失败了,至少还有EMMC上的作托底。