Qt跨平台开发Windows & Android

目标

使用Qt自带的OpenGL库,自行编译Android Arm64架构可用的Assimp静态库。使用Visual Studio和Qt进行编码,可以在Windows以及Android平台运行。

环境配置

配置Qt Android开发环境

教程在这里

注意SDK版本号必须一致

配置Qt VS开发环境

  1. 安装插件并设置QtVersions

  2. 用VS创建项目,然后用插件创建.pro文件,并添加这一行:

1
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

自动生成的.pro文件只包含很少的配置,后面会添加很多自定义的配置,需要经常进行备份,以防止被覆盖

项目建立

基本工作流

在Qt中修改ui等资源后,VS里不会立即同步,需要先用VS编译一下才会生成对应的中间文件。新添加的界面还需要手动添加到项目里

使用OpenGL

下面开始正式建立项目并在Qt中使用OpenGL,可以参照这个博客:learn opengl with Qt_ItaLink-CSDN博客

核心是继承QOpenGLWidget并实现三个成员函数,手动添加回调函数。添加控件并提升为这个自定义控件类型,这样自定义回调就可以调用起来了,回调里写初始化和绘制代码。需要注意函数调用时机,我们在外部手动调用update来及时更新。

其中主要有三个函数:

  1. initializeGL初始化被调用一次
  2. resizeGL在大小改变时被调用,并触发paintGL
  3. paintGL在移动、显隐时被调用,updateGL触发paintGL

外部可以调update来加入paint队列,注意文档说了,只是加入处理队列,所以只有最后一次有效。同理,再paint之外的绘制也会被paint覆盖。

外部在这三个函数之外要想使用OpenGL的API,需要一个OpenGLWidgetmakeCurrent(而这三个函数被调之前都已经makeCurrent,所以内部不需要再调)

底层函数的实现在不同平台下是不一样的,具体头文件的选择需要用宏来控制。头文件在Windows下是QOpenGlFunctions_3_3_Core,Android下是QOpenGLFunctions_ES2,具体函数的接口跟glew、glad的一样。

关于平台的判断,Android平台有个宏__ANDROID__,因为这里只考虑Windows和Android两个平台,所以可以像下面这样简单判断一下:

Platform.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once

#if defined(__ANDROID__)
# define PLATFORM_ANDROID true
#else
# define PLATFORM_WINDOWS true
#endif

// set the rest to false
#if !defined(PLATFORM_WINDOWS)
# define PLATFORM_WINDOWS false
#endif

#if !defined(PLATFORM_ANDROID)
# define PLATFORM_ANDROID false
#endif

配置assimp库

windows的很好编译,用cmake打开CMakeLists就能创建VS解决方案了。需要注意的是,除了链接lib之外,还要把dll放到项目生成的exe同级目录里。

而Android的比较麻烦,我一开始下的是3.0版的,但是发现代码实在太旧了,一大堆错误,本来想自己改的,但是发现根本改不完,错误越来越多……后来才看到别人的文章,要用新一点的版本。链接在这里

基本跟着文章走就行了,需要注意脚本里的写的路径要跟自己的实际安装情况对应,还有cmake得是用android studio装的,不然里面没有ninja, 还要注意第二个脚本的-DANDROID_STL=c++_staticstatic而不是shared(感谢这篇文章),不然最后会运行失败。

对于文章里用的两个脚本,我的是这样的:

make_standalone_toolchain.bat

1
2
python E:/SDKs/android-ndk-r18b-windows-x86_64/android-ndk-r18b/build/tools/make_standalone_toolchain.py --arch=arm64 --stl=libc++ --api=28 --install-dir=android-toolchain-api28-arm64
pause

build_assimp.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@echo off
cls

REM *NOTE* Change these based on
SET ASSIMP_DIR=./
SET OUTPUT_DIR=assimp-build-arm64v8a
SET ANDROID_PATH=E:\SDKs\android-sdk_r24.4.1-windows\android-sdk-windows
SET NDK_PATH=E:\SDKs\android-ndk-r18b-windows-x86_64\android-ndk-r18b
SET NDK_TOOLCHAIN=%~dp0android-toolchain-api28-arm64
SET CMAKE_TOOLCHAIN=%NDK_PATH%\build\cmake\android.toolchain.cmake
SET CMAKE_PATH=D:\DownloadApps\AndroidSDK\cmake\3.18.1

REM *NOTE* Careful if you don't want rm -rf, I use it for testing purposes.
rm -rf %OUTPUT_DIR%
mkdir %OUTPUT_DIR%

REM pushd doesn't seem to work ):<
cd %OUTPUT_DIR%

if not defined ORIGPATH set ORIGPATH=%PATH%
SET PATH=%CMAKE_PATH%\bin;%ANDROID_PATH%\tools;%ANDROID_PATH%\platform-tools;%ORIGPATH%

cmake ^
-GNinja ^
-DCMAKE_TOOLCHAIN_FILE=%CMAKE_TOOLCHAIN% ^
-DASSIMP_ANDROID_JNIIOSYSTEM=ON ^
-DANDROID_NDK=%NDK_PATH% ^
-DCMAKE_MAKE_PROGRAM=%CMAKE_PATH%\bin\ninja.exe ^
-DCMAKE_BUILD_TYPE=Release ^
-DANDROID_ABI="arm64-v8a" ^
-DANDROID_NATIVE_API_LEVEL=28 ^
-DANDROID_FORCE_ARM_BUILD=TRUE ^
-DCMAKE_INSTALL_PREFIX=install ^
-DANDROID_STL=c++_static ^
-DCMAKE_CXX_FLAGS=-Wno-c++11-narrowing ^
-DANDROID_TOOLCHAIN=clang ^

-DASSIMP_BUILD_TESTS=OFF ^

../%ASSIMP_DIR%

cmake --build .

cd ..

pause

Qt里项目右键添加库之后,还要在项目-Build&Run-Build-构建步骤-Build Android APK添加Additional Libraries,这样才会打进APK里,不然运行时会报错找不到库!实际上这个步骤就是修改了下.pro文件。

最后的项目结构:

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3rd/
assimp/
include/
lib/
assimp-vc143-mtd.lib
libassimp.so
src/
main.cpp
MainWindow.cpp
MainWindow.h
MainWindow.qrc
MainWindow.ui
MyOpenGLWidget.cpp
MyOpenGLWidget.h
.pri
.pro
.vcxproj

最后的.pro配置文件

.pro配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 添加模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

# 指定C++标准
CONFIG += c++17

# 分平台添加链接库,注意VS自动创建的是不对的
win32: LIBS += -L$$PWD/3rd/assimp/lib -lassimp-vc143-mtd
unix:!macx: LIBS += -L$$PWD/3rd/assimp/lib/ -lassimp

# 库的头文件路径
INCLUDEPATH += $$PWD/3rd/assimp/include
DEPENDPATH += $$PWD/3rd/assimp/include

# 将库文件打包进APK里
contains(ANDROID_TARGET_ARCH,arm64-v8a) {
ANDROID_EXTRA_LIBS = \
$$PWD/3rd/assimp/lib/libassimp.so
}

# 自定义头文件搜索路径
INCLUDEPATH += $$PWD/src

资源文件路径问题

  • 在Windows下,资源就按照相对项目路径就能加载

  • 而Android下,需要将资源打包到APK中,引用路径也有所不同

    在.pro文件里添加配置,把项目目录下的res文件夹都放到assets/res目录(Android打包后的资源路径)里;

    1
    2
    3
    4
    5
    android {
    ress.files += res/*
    ress.path = /assets/res
    INSTALLS += ress
    }
    代码里引用:

    1
    auto file = QFile("assets:/res/test.txt"); // 注意用std标准库是加载不了的,要用Qt的文件接口

因为前面加了写了平台判断宏,所以一个通用的文件加载接口可以这样写:

LoadBinary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
std::string GetPlatformPath(const std::string& path)
{
#if PLATFORM_ANDROID
return "assets:/" + path;
#else
return path;
#endif
}

std::vector<char> LoadBinary(const std::string& path)
{
auto file = QFile(GetPlatformPath(path).data());
if (!file.open(QIODevice::ReadOnly)) {
LogError("Failed to open: ", path);
return {};
}
auto content = file.readAll();
return std::vector<char>(content.data(), content.data() + content.size());
}

安卓手机运行

手机连接数据线,设置里进入开发者模式并打开USB调试和允许USB安装应用,并保持屏幕亮起,运行前注意检查编译工具。

后来又添加了lua库,使用qt来编译库的,需要注意的是安卓下面的库只能是动态共享库,生成后缀是.so才行。库项目自带的两个文件不能直接删掉,可以把文件内容清空。之后引入到项目里时还需要注意库名的写法。(可以观察下自动生成的配置是怎么样的)