Using Qt as shared system libraries in Android
在Android中使用Qt作为共享系统库
October 21, 2022 by Tinja Paavoseppä | Comments
2022年10月21日Tinja Paavoseppä|评论
In Android 7.0 namespaces for native libraries were introduced. What this means is that apps can only access the public libraries provided with the Android NDK and the ones in their own native library directory. This is why, by default, every Qt Android app deploys with its own set of Qt libraries which get extracted to the app's native library directory. Due to the aforementioned restrictions, these libraries can't be used by other apps. For most user apps, this is OK - the app can be installed on a number of devices, with no guarantees that the Qt libraries could be found there, or that they would be the correct version!
在Android 7.0中,引入了本地库的命名空间。这意味着应用程序只能访问Android NDK提供的公共库以及它们自己的本地库目录中的公共库。这就是为什么默认情况下,每个Qt Android应用程序都会部署自己的一组Qt库,这些库会被提取到应用程序的本地库目录中。由于上述限制,这些库不能被其他应用程序使用。对于大多数用户应用程序来说,这没关系-该应用程序可以安装在许多设备上,但不保证Qt库可以在那里找到,或者它们是正确的版本!
However, if you are an OEM planning to use Qt to implement your IVI system on top of Android Automotive, this is not ideal. The thought of having to deploy the same libraries with each app may not sound appealing. The more Qt apps you have, the bigger the problem becomes, those multiple sets of libraries take up space.
然而,如果您是一个OEM,计划使用Qt在Android Automotive上实现您的IVI系统,这并不理想。必须为每个应用程序部署相同的库的想法听起来可能并不吸引人。你拥有的Qt应用程序越多,问题就越大,这些多组库占用了空间。
The good news is that system apps have looser restrictions placed on them - they can use all the native libraries under the system library directory. So in this blog post, we will have a look at how to build an Android image with Qt libraries installed as shared system libraries. We will also learn how to use the new Unbundled deployment, introduced in Qt 6.4.0, to help make it easier to build and deploy apps that will link to Qt libraries present on the target device instead of bundling all the libraries with the APK.
好消息是,系统应用程序对它们有更宽松的限制——它们可以使用系统库目录下的所有本地库。因此,在这篇博客文章中,我们将了解如何使用作为共享系统库安装的Qt库构建Android映像。我们还将学习如何使用Qt 6.4.0中引入的新的Unbundled部署,以帮助更轻松地构建和部署将链接到目标设备上的Qt库的应用程序,而不是将所有库与APK绑定。
Note that due to the restrictions placed by Android, these libraries will only be available to system apps installed on the system partition.
请注意,由于Android的限制,这些库将仅对安装在系统分区上的系统应用程序可用。
Adding the libraries
添加库
Since the apps do not come with their own set of libraries, we need to make sure the Qt libraries can be found on the device. To add them, we need to modify the AOSP build tree. If you would like a refresher on how to build an Android Automotive emulator image, take a look here! For this post, we build an Android Automotive 12 x86_64 image that can be run on an Android emulator as a virtual device, with the Qt libraries and an example app included.
由于这些应用程序没有自己的库,我们需要确保Qt库可以在设备上找到。要添加它们,我们需要修改AOSP构建树。如果您想了解如何构建Android汽车仿真器映像,请查看此处!在本文中,我们构建了一个Android Automotive 12 x86_64映像,该映像可以作为虚拟设备在Android模拟器上运行,其中包括Qt库和示例应用程序。
After we have set up the AOSP build environment as described in the link above, we create a directory named "qt" under <aosp-root>/external. Here we will add the Qt libraries our app(s) use - you can copy them from under your Qt for Android installation. We need to also create an Android.mk or Android.bp file to let the AOSP build know what we want to include. Here, we use the former. Let's add the Android.mk file under the same folder. Now, our directory structure would look like this:
按照上面的链接所述设置AOSP构建环境后,我们在<AOSP-root>/external下创建一个名为“qt”的目录。在这里,我们将添加我们的应用程序使用的Qt库-您可以从Android安装的Qt下复制它们。我们还需要创建一个Android.mk或Android.bp文件,让AOSP构建知道我们想要包含什么。在这里,我们使用前者。让我们添加Android.mk文件位于同一文件夹下。现在,我们的目录结构如下所示:
You could also add a subdirectory for e.g. the target architecture (x86_64 in this case) and place the libraries there. Just make sure LOCAL_SRC_FILES
points to the source file in relation to Android.mk file's location. Also note that the actual list of libraries you need to add will depend on your app, here we have added the libraries our example app for this post uses.
您还可以为目标体系结构(在本例中为x86_64)添加一个子目录,并将库放在那里。只需确保LOCAL_SRC_FILES指向与Android.mk文件位置相关的源文件。还请注意,您需要添加的库的实际列表将取决于您的应用程序,这里我们添加了本文示例应用程序使用的库。
In the Android.mk file, we first set the LOCAL_PATH
to the current working directory. This needs to be done only once at the beginning of the file. Then, an entry is added for each library we want to install.
在Android.mk文件中,我们首先将LOCAL_PATH设置为当前工作目录。这只需要在文件开头执行一次。然后,为要安装的每个库添加一个条目。
LOCAL_PATH := $(call my-dir)
# One entry for each library
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
# The name should match the library name without the .so suffix
LOCAL_MODULE := libQt6Quick_x86_64
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
# Path to the library, relative to LOCAL_PATH
LOCAL_SRC_FILES := $(LOCAL_MODULE)$(LOCAL_MODULE_SUFFIX)
# Relative path to install location -# this will result in the libraries being installed under system/lib(64)/qt/
LOCAL_MODULE_RELATIVE_PATH := qt
LOCAL_CHECK_ELF_FILES := false
include $(BUILD_PREBUILT)
# And the rest of the entries
...
In the example above, we say we want to add a module called libQt6Quick_x86_64
, which is a prebuilt C++ shared library, and the prebuilt .so which has the same name as the module can be found in the same directory as the Android.mk file.
在上面的例子中,我们说我们想添加一个名为libQt6Quick_x86_64的模块,这是一个预构建的C++共享库,而与该模块同名的预构建.so可以在与Android.mk文件相同的目录中找到。
We use:
我们使用:
LOCAL_MODULE_RELATIVE_PATH:= qt
to tell we want the libraries installed under a subdirectory called "qt" under the system library directory. By default, the AOSP build will check ELF files to make sure all the library's dependencies have been listed appropriately in its LOCAL_SHARED_LIBRARIES
entry. This can be disabled by adding a line inside the module declaration to tell it to skip the check:
告诉我们,我们希望这些库安装在systemlibrary目录下名为“qt”的子目录下。默认情况下,AOSP构建将检查ELF文件,以确保在其LOCAL_SHARED_LIBRARIES条目中正确列出了库的所有依赖项。这可以通过在模块声明中添加一行来禁用,以告诉它跳过检查:
LOCAL_CHECK_ELF_FILES := false
Notice that in this case, the AOSP build will not install the library's dependencies with it, as it does not know them. In this case, you will need to tell it explicitly to add each of them. This can be achieved by listing them all as installable modules under the product's make file. For example, we can modify the file <aosp-root>/packages/services/Car/car_product/build/car.mk
and list all the libraries we want to install under PRODUCT_PACKAGES
:
注意,在这种情况下,AOSP构建不会安装库的依赖项,因为它不知道它们。在这种情况下,您需要明确地告诉它添加其中的每一个。这可以通过将它们作为可安装模块列在产品的make文件下来实现。例如,我们可以修改文件<aosp root>/packages/services/Car/Car_product/build/Car.mk并在PRODUCT_PACKAGES下列出所有要安装的库:
# Append to existing packages, using the name you gave the library module
PRODUCT_PACKAGES += \
... \
libQt6Core_x86_64 \
libQt6Quick_x86_64 \
# The rest of the libraries...
...
After all the libraries have been added, we can move on to including the app!
添加完所有库后,我们可以继续添加应用程序!
Building the app
构建应用程序
Next, let's create a simple Qt app - the one created by Qt Creator when you choose "New project - Qt Quick Application" will work just fine for this purpose. Let's call it simply QtApp.
Now if we build it using a Qt for Android kit and the default deployment and take a look at the APK contents, we can see it has included all of its Qt dependencies inside it, under the "lib" directory.
接下来,让我们创建一个简单的Qt应用程序-当您选择“新项目-Qt Quick应用程序”时,由Qt Creator创建的应用程序将非常适合此目的。让我们简单地称之为QtApp。
现在,如果我们使用Qt for Android工具包和默认部署构建它,并查看APK内容,我们可以看到它在“lib”目录下包含了所有Qt依赖项。
Since we will be providing the libraries as system libraries, we do not need this. So, to take advantage of Unbundled deployment, we set a couple of Qt CMake properties in the app's CMakeLists.txt:
由于我们将以系统库的形式提供这些库,所以我们不需要这样做。因此,为了利用Unbundled部署,我们在应用程序的CMakeLists.txt中设置了几个Qt CMake属性:
set_target_properties(${target_name} PROPERTIES
QT_ANDROID_NO_DEPLOY_QT_LIBS True
QT_ANDROID_SYSTEM_LIBS_PREFIX /system/lib64/qt/
)
Setting QT_ANDROID_NO_DEPLOY_QT_LIBS to true tells the deployment tool not to package the C++ libraries into the APK; and we use QT_ANDROID_SYSTEM_LIBS_PREFIX to tell the app where to search for them on the device, instead. Here, we set the path to point to /system/lib64/qt/
, as the image we are building is a 64-bit one. If you are targeting a 64-bit architecture like arm64 or x86_64 this is the default directory for the libraries. If building a 32-bit image, x86 or armv7, swap it for /system/lib/qt/
instead.
将QT_ANDROID_NO_DEPLOY_QT_LIBS设置为true告知部署工具不要将C++库打包到APK中;我们使用QT_ANDROID_SYSTEM_LIBS_PREFIX来告诉应用程序在设备上的何处搜索它们。这里,我们将路径设置为指向/system/lib64/qt/,因为我们正在构建的映像是64位映像。如果您的目标是像arm64或x86_64这样的64位体系结构,这是库的默认目录。如果构建32位映像x86或armv7,请将其替换为/system/lib/qt/。
If we do a fresh build after setting the properties, you may notice straight away the APK size is much smaller than a usual Qt Android app - for this simple app the size drops from over 12 MB to just over 100 kB! If you take a peek inside the APK, you will notice the lib folder is missing.
如果我们在设置属性后重新构建,您可能会立即注意到APK的大小比通常的Qt Android应用程序小得多-对于这个简单的应用程序,大小从超过12 MB降至刚刚超过100 kB!如果你在APK里面看一眼,你会发现lib文件夹不见了。
Next, it's time to include the app in the image. As mentioned in the beginning, only apps installed under the system partition can use the libraries we are installing, so we need to add the app we just built as a system app. So again, we create a folder under the AOSP tree - let's call it QtApp and place it under <aosp-root>/packages/apps/Car/
.
接下来,是时候将应用程序包含在图像中了。如前所述,只有安装在系统分区下的应用程序才能使用我们正在安装的库,因此我们需要将我们刚刚构建的应用程序添加为系统应用程序。因此,我们再次在AOSP树下创建一个文件夹-让我们将其命名为QtApp,并将其放置在<AOSP root>/packages/apps/Car/下。
Under the folder we created, we then copy the APK we just built and its own library. The library can be found under the app's build directory and will by default be called libapp<app-name>_<target-arch>.so
. So if our app would be called QtApp, we would copy the library libappQtApp_x86_64.so. Again, we also create an Android.mk file. This time the contents look a bit different:
在我们创建的文件夹下,我们复制刚刚构建的APK及其自己的库。该库可以在应用程序的构建目录下找到,默认情况下称为libapp<app name>_<target arch>.so。因此,如果我们的应用程序被称为QtApp,我们将复制库libappQtApp_x86_64.so。同样,我们也创建了一个Android.mk文件。这次的内容看起来有点不同:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
# Module name should match apk name to be installed (without the .apk extension)
LOCAL_MODULE := QtApp
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
# Adds listed files under app's native library dir
LOCAL_PREBUILT_JNI_LIBS := \
# Replace with the name of your app's library
libQtApp_x86_64.so \
LOCAL_CERTIFICATE := platform
include $(BUILD_PREBUILT)
This time we let the build know we want to add a prebuilt app.
We also list the libraries we are providing with the app, and want to be installed under the app's own native library directory - here the app's own library, libappQtApp_x86_64.so:
这一次,我们让构建知道我们想要添加一个预构建的应用程序。
我们还列出了我们随应用程序提供的库,并希望安装在应用程序自己的本地库目录下-这里是应用程序自己库libappQtApp_x86_64.so:
LOCAL_PREBUILT_JNI_LIBS := \
# Replace with the name of your app's library
libappQtApp_x86_64.so \
The prebuilt JNI libraries for the app do not need to be declared as modules before including them. Instead, we provide the path to the library, suffix included, in relation to the Android.mk file.
应用程序的预构建JNI库不需要在包含它们之前声明为模块。相反,我们提供了与Android.mk文件相关的库路径,包括后缀。
Now that both the libraries and app have been added to the AOSP tree, we can build our image! For that, we follow the directions listed here.
现在,库和应用程序都已添加到AOSP树中,我们可以构建我们的图像了!为此,我们遵循此处列出的说明。
Once we've created an Android Virtual Device based on the image we built, we can launch it on the emulator and have a look. We can see there is a folder /system/app/QtApp
, the contents of which will look like this:
一旦我们根据我们构建的图像创建了一个Android虚拟设备,我们就可以在模拟器上启动它并进行查看。我们可以看到有一个文件夹/system/app/QtApp,其内容如下:
So the app's own library is now the only one found from under the app's own directory. Meanwhile, all the other Qt libraries are now installed under /system/lib64/qt/.
因此,应用程序自己的库现在是从应用程序自己目录下找到的唯一库。同时,所有其他Qt库现在都安装在/system/lib64/Qt/下。
Now QtApp, and all the other Qt system apps installed on the device can use that single set of shared libraries - no need for each app to bring its own!
现在,QtApp和设备上安装的所有其他Qt系统应用程序都可以使用同一组共享库-无需每个应用程序都自带!