By Toradex胡珊逢
在嵌入式設備上開發圖形用戶界面通常會選擇?Qt。這是一種經驗證的方案,我們可以在多個領域看到用?Qt?開發的?UI。但隨著移動端和?web?端界面更為廣泛地使用,源自于這兩個領域的技術也開始向嵌入式設備滲透。Flutter?就是一個例子。本文將介紹如何在?Torizon?平臺上如何使用?Flutter?來開發用戶界面。
Google?面向?Android, iOS?推出的跨平臺移動應?開發框架?Flutter?可以構建高質量的原??戶界?,并可以擴展支持?Web?和桌面應用。Flutter?尚未官方支持嵌入式系統,但目前?Sony?和?Ubuntu?正在致力于該工作。例如來自?Sony?的?elinux?可以在嵌入式平臺上使用?Flutter。我們也將以此為基礎,在?Verdin iMX8M Plus?的?Torizon?上運行?Flutter?應用。
我們來看看?Flutter?的構架。如下圖所示,其由三個部分構成,User app, Framework?和?Engine。flutter-elinux-linux?屬于為嵌入式提供支持的?embbedder。它可以運行在?wayland?顯示后臺,這也是?Torizon?提供的顯示框架。flutter-elinux-linux?將提供?flutter-client?,libflutter_elinux_wayland.so?和?libflutter_engine.so。這些軟件的功能參考該網頁描述。
https://github.com/sony/flutter-embedded-linux/wiki/Features-of-Embedded-Linux-embedding-for-Flutter
flutter-elinux?是?Flutter SDK?的一個非官方插件,用于為嵌入式設備創建、編譯和調試?Flutter?應用,并使用?flutter-elinux-linux?在設備上顯示。
為了減少對編譯電腦的軟件環境影響,我們將使用?docker?容器進行編譯。使用下面命令獲取?ubuntu:20.04?容器并進入其中。由于后面需要向容器內提供文件,這里將?/home/user/flutter?映射到容器內的?/opt/flutter。
-----------------------------------------------
~$ docker pull ubuntu:20.04
~$ docker run -it -v /home/user/flutter:/opt/flutter --name flutter_build ubuntu:20.04 /bin/bash
-----------------------------------------------
如果后續進入該容器重新編譯,可以使用下面命令:
-----------------------------------------------
~$ docker container start flutter_build
~$ docker exec -it flutter_build bash
-----------------------------------------------
在容器中安裝所需的軟件。
-----------------------------------------------
# apt update
# apt upgrade
# apt install clang cmake build-essential pkg-config libegl1-mesa-dev libxkbcommon-dev libgles2-mesa-dev
# apt install libwayland-dev wayland-protocols git curl wget unzip git
# apt install python2
# apt install virtualenv
-----------------------------------------------
下載編譯工具。
-----------------------------------------------
# mkdir -p /opt/flutter
# cd /opt/flutter
# git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# export PATH=$PATH:$(pwd)/depot_tools
-----------------------------------------------
默認的?ubuntu:20.04?使用?Python3,在容器里使用?virtualenv?創建?Python2?環境。
-----------------------------------------------
# virtualenv .env -p python2
# source .env/bin/activate
-----------------------------------------------
創建?.gclient?文件并指定版本。
-----------------------------------------------
# cat .gclient
solutions = [
{
"managed": False,
"name": "src/flutter",
"url": "https://github.com/flutter/engine.git@bd539267b42051b0da3d16ffa8f48949dce8aa8f",
"custom_deps": {},
"deps_file": "DEPS",
"safesync_url": "",
"custom_vars" : {
"download_android_deps" : False,
"download_windows_deps" : False,
},
},
]
-----------------------------------------------
上面的?bd539267b42051b0da3d16ffa8f48949dce8aa8f?對應?${path_to_flutter_sdk_install}/flutter/bin/internal/engine.version,兩者需要一致。如果不指定的話,會下載最新的版本。除非確實需要編譯最新版本的?Engine,否則并不推薦。
獲取代碼。
-----------------------------------------------
# gclient sync
-----------------------------------------------
編譯?embbedder。這里編譯為?arm64?目標?release?模式的?embedder。
-----------------------------------------------
# cd src
# ./flutter/tools/gn --target-os linux --linux-cpu arm64 --runtime-mode release --embedder-for-target --disable-desktop-embeddings --no-build-embedder-examples
# ninja -C out/linux_release_arm64
-----------------------------------------------
編譯成功后在?out/linux_release_arm64?目錄中可以看到?libflutter_engine.so?文件。
接下來將編譯?Embedded Linux embedding for Flutter,這里會生成?flutter-client?和?libflutter_elinux_wayland.so。如果在?X86?電腦上交叉編譯需要使用?Yocoto Project?生成包含?clang?在內的?SDK,這會涉及大量的工作內容。在?Torizon?中我們可以直接使用?debian?容器并通過?apt?命令安裝相應的軟件,在?Verdin iMX8M Plus?本地編譯。這通常適用于代碼量不是很多的項目。在?Verdin iMX8M Plus?上運行下面命令啟動?debian?容器。
-----------------------------------------------
~$ docker run -it ?-v /home/torizon/workspace:/opt/workspace torizon/debian:$CT_TAG_DEBIAN /bin/bash
-----------------------------------------------
在容器中安裝所需的軟件。
-----------------------------------------------
# apt update
# apt install clang cmake build-essential pkg-config libegl1-mesa-dev libxkbcommon-dev libgles2-mesa-dev
# apt install unzip git
# apt install curl wget
# apt install libwayland-dev wayland-protocols
# apt install libdrm-dev libgbm-dev libinput-dev libudev-dev libsystemd-dev
# cd /opt/workspace
-----------------------------------------------
下載?flutter-embedded-linux?代碼。
-----------------------------------------------
# git clone https://github.com/sony/flutter-embedded-linux.git
# cd flutter-embedded-linux/
# mkdir build
-----------------------------------------------
此時將剛才編譯的?libflutter_engine.so?復制到?build?目錄。然后分別執行下面兩個命令。
-----------------------------------------------
# cmake -DUSER_PROJECT_PATH=examples/flutter-wayland-client -DCMAKE_BUILD_TYPE=Release ..
# cmake -DUSER_PROJECT_PATH=examples/flutter-wayland-client -DCMAKE_BUILD_TYPE=Release?-DBUILD_ELINUX_SO=ON -DBACKEND_TYPE=WAYLAND -DENABLE_ELINUX_EMBEDDER_LOG=OFF -DFLUTTER_RELEASE=ON ..
-----------------------------------------------
編譯完成后在?build?目錄下可以看到生成的?flutter-client?和?libflutter_elinux_wayland.so?兩個文件。上面使用的編譯選項含義請參考該網頁說明。
接下來重新回到?X86?編譯電腦開始?Flutter?應用的編譯。用下面命令重新進入使之前的?ubuntu:20.04?容器。
-----------------------------------------------
~$ docker exec -it flutter_build bash
-----------------------------------------------
下載?flutter-elinux。這個是?Flutter SDK。
-----------------------------------------------
# cd /opt/flutter/
# git clone https://github.com/sony/flutter-elinux
# export PATH=$PATH:/opt/flutter/flutter-elinux/bin
-----------------------------------------------
運行下面命令查看安裝情況。
-----------------------------------------------
# flutter-elinux doctor
# flutter-elinux devices
-----------------------------------------------
創建一個示例工程。
-----------------------------------------------
# flutter-elinux create demo1
# cd demo1
-----------------------------------------------
按照這里的說明交叉編譯創建的工程。但在這之前需要準備?Verdin iMX8M Plus?的?arm64?格式文件系統。該文件系統可以是來自剛才在?Verdin iMX8M Plus?上編譯?Embedded Linux embedding for Flutter?的容器。
在?Verdin iMX8M Plus?運行下面命令,查看容器?ID。
-----------------------------------------------
$ docker ps -a
CONTAINER ID ???????IMAGE ??????????????????????COMMAND ????????????CREATED ????????????STATUS ????????????????????PORTS ??????????????NAMES
3dea07245b24 ???????torizon/debian:2-bullseye ??"/bin/bash" ????????2 days ago ?????????Exited (137) 2 hours ago ??????????????????????hardcore_nightingale
-----------------------------------------------
將容器的文件系統復制出來并打包。
-----------------------------------------------
$ sudo docker cp 3dea07245b24:/ arm64-sysroot
$ sudo tar cvf arm64-sysroot.tar arm64-sysroot
-----------------------------------------------
然后將?arm64-sysroot.tar?復制到?X86?編譯電腦的?flutter_build?容器中,位于?/opt/flutter?目錄。回到?flutter_build?容器,解壓?arm64-sysroot.tar。
-----------------------------------------------
# cd /opt/flutter/
# tar vxf arm64-sysroot.tar
-----------------------------------------------
進入剛才創建的?demo1?目錄,運行下面命令編譯。
-----------------------------------------------
# cd demo1
# flutter-elinux build elinux --target-arch=arm64 --target-sysroot=/opt/flutter/arm64-sysroot
-----------------------------------------------
待編譯結束后,查看?build/elinux/arm64/release/bundle,這里是?Flutter app?運行所需的所以文件。
-----------------------------------------------
# tree build/elinux/arm64/release/bundle -L 2
.
|-- data
| ??|-- flutter_assets
| ??`-- icudtl.dat
|-- demo1
`-- lib
|-- libapp.so
|-- libflutter_elinux_wayland.so
`-- libflutter_engine.so
-----------------------------------------------
libflutter_elinux_wayland.so?和?libflutter_engine.so?是?Fltter SDK?預編譯的庫文件,需要將其替換為之前編譯的庫文件。
將?flutter-client?和?bundle?文件夾復制到?Verdin iMX8M Plus?的?/home/torizon/flutter_demo?目錄。然后先啟動?weston?容器。
-----------------------------------------------
$ docker run -e ACCEPT_FSL_EULA=1 -d --rm --name=weston --net=host --cap-add CAP_SYS_TTY_CONFIG \
-v /dev:/dev -v /tmp:/tmp -v /run/udev/:/run/udev/ \
--device-cgroup-rule='c 4:* rmw' --device-cgroup-rule='c 13:* rmw' \
--device-cgroup-rule='c 199:* rmw' --device-cgroup-rule='c 226:* rmw' \
torizon/weston-vivante:$CT_TAG_WESTON_VIVANTE --developer weston-launch \
--tty=/dev/tty7 --user=torizon
-----------------------------------------------
再啟動另外一個?torizon/weston-vivante?容器,在里面我們將用命令行的方式啟動編譯好的?demo1。
-----------------------------------------------
$ docker run -e ACCEPT_FSL_EULA=1 -it --rm --name=wayland-app --user=torizon \
-v /dev/dri:/dev/dri -v /dev/galcore:/dev/galcore -v /tmp:/tmp -v /home/torizon/flutter_demo:/opt/flutter \
--device-cgroup-rule='c 199:* rmw' --device-cgroup-rule='c 226:* rmw' \
torizon/weston-vivante:$CT_TAG_WESTON_VIVANTE bash
-----------------------------------------------
在啟動的容器內運行下面命令。
-----------------------------------------------
# cd /opt/flutter
# LD_LIBRARY_PATH=/opt/flutter/bundle/lib/ ./flutter-client -b /opt/flutter/bundle
-----------------------------------------------
最后,我們將介紹如何導入一個現成的?Flutter?項目并打包為一個容器,用?docker-compose?文件啟動。這里以?covid19_mobile_app?為例進行說明。
在?flutter_build?容器中,下載?covid19_mobile_app?代碼,將之前?demo1?目錄中的?elinux?文件夾復制到?covid19_mobile_app?后再編譯。同樣,libflutter_elinux_wayland.so?和?libflutter_engine.so?也需要替換為之前編譯的庫文件。
-----------------------------------------------
# cp -r ../demo1/elinux covid19_mobile_app
# flutter-elinux pub get
# flutter-elinux build elinux --target-arch=arm64 --target-sysroot=/opt/flutter/arm64-sysroot
-----------------------------------------------
編譯結束后,將?covid19_mobile_app?的?bundle?目錄連同?flutter-client,以及下面的?startup.sh,Dockerfile?放到任一目錄中。
startup.sh
-----------------------------------------------
#!/bin/bash
LD_LIBRARY_PATH=/home/torizon/bundle/lib/ /usr/sbin/flutter-client -b /home/torizon/bundle
-----------------------------------------------
Dockerfile
-----------------------------------------------
FROM --platform=linux/arm64 torizon/weston-vivante:2
ADD bundle /home/torizon/bundle
COPY flutter-client /usr/sbin
COPY startup.sh /home/torizon
CMD [ "/home/torizon/startup.sh" ]
-----------------------------------------------
運行下面命令生成一個?flutter app?容器。
-----------------------------------------------
$ docker build -t flutter_demo:1 .
-----------------------------------------------
將?flutter_demo:1?容器和?docker-compose.yml?文件復制到?Verdin iMX8M Plus?上。
docker-compose.yml
-----------------------------------------------
services:
flutter_demo_covid19:
depends_on:
- weston
devices: []
image: flutter_demo:1
ports: []
device_cgroup_rules:
- c 199:* rmw
- c 226:* rmw
volumes:
- /tmp:/tmp:rw
- /dev/dri:/dev/dri:rw
- /dev/galcore:/dev/galcore:rw
weston:
cap_add:
- CAP_SYS_TTY_CONFIG
device_cgroup_rules:
- c 4:0 rmw
- c 4:7 rmw
- c 13:* rmw
- c 199:* rmw
- c 226:* rmw
environment:
- ACCEPT_FSL_EULA=1
image: torizon/weston-vivante:2
network_mode: host
volumes:
- source: /tmp
target: /tmp
type: bind
- source: /dev
target: /dev
type: bind
- source: /run/udev
target: /run/udev
type: bind
version: '2.4'
-----------------------------------------------
運行?docker-compose up -d?即可啟動?weston?和?flutter app?容器。
總結
Flutter?框架為圖形界面開發提供一個非常靈活的方案,使得嵌入式開發也可以從中受益。于此同時,嵌入式?Linux?對?Flutter?的支持也處于早期階段,項目開發需要充分驗證。
審核編輯:黃飛
評論