• Verdin Family
  • AM62 | iMX8M Mini | iMX8M Plus | i.MX 95 Verdin Evaluation Kit NEW | iMX95 NEW
  • Apalis Family
  • iMX8 | iMX6 | T30
  • Colibri Family
  • iMX8X | iMX6 | iMX7 | iMX6ULL | VF61 | VF50 | T30 | T20
  • By SoC
  • Torizon
  • | Torizon OS | TorizonCore Builder | Visual Studio Code Integration | Remote Updates | Fleet Monitoring
  • Embedded Linux

    在嵌入式设备上开发图形用户界面通常会选择 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 的支持也处于早期阶段,项目开发需要充分验证。

    Author: 胡珊逢 ,FAE,韬睿(上海)
    < Previous Back to Blog Contact Us Share this on:

    Leave a comment

    Reply for:

  •