本文档将详细讲解如何使用 Flutter 和工具打包发布多平台应用,包括 Android、iOS、Windows、macOS、Web 和 Linux。并通过 flutter_distributor 工具来实现高级打包功能。

准备工作

  1. 安装 Flutter SDK

    • 下载并安装 Flutter SDK:Flutter 官网

    • 配置环境变量:

      export PATH="$PATH:/path-to-flutter/bin"
      
  2. 检查环境配置

    flutter doctor
    
  3. 安装 flutter_distributor 工具

    • 安装 flutter_distributor

      dart pub global activate flutter_distributor
      
  4. 项目初始化

    • 创建一个新的 Flutter 项目或打开现有项目:

      flutter create my_app
      cd my_app
      

Android 打包

  1. 使用 Flutter 命令打包 APK

    • 打包 APK 文件并分离 ABI:

      flutter build apk --release --split-per-abi
      
    • 输出的打包文件位于 build/app/outputs/flutter-apk/

  2. 发布 AAB 文件

    • 如果需要发布 AAB 文件到 Google Play:

      flutter build appbundle --release
      

iOS 打包

  1. 打包 iOS 应用

    • 不签名的 Release 构建:

      flutter build ios --release --no-codesign
      
  2. 生成 IPA 文件

    • 进入构建输出目录:

      cd build/ios/iphoneos/
      
    • 创建 Payload 文件夹并移动应用:

      mkdir Payload
      cp -r Runner.app Payload/
      
    • 打包成 IPA 文件:

      zip -r Runner.ipa Payload
      

Web 打包

  1. 打包 Web 应用

    flutter build web --release
    
  2. 打包结果

    • 构建输出位于 build/web 目录中。
    • 可以将其部署到任何支持静态文件托管的服务(如 Nginx、Firebase Hosting)。

Windows 打包

  1. 打包 Windows 应用

    • 使用 Flutter 构建:

      flutter build windows
      
    • 构建结果位于 build/windows/runner/Release 目录。

  2. 高级打包工具

    • 使用 flutter_distributor 打包为 EXE 或 MSIX(详细见 高级功能)。

macOS 打包

  1. 打包 macOS 应用

    flutter build macos
    
  2. 构建输出

    • 构建结果位于 build/macos/Build/Products/Release 目录。
  3. 高级打包工具

    • 使用 flutter_distributor 打包为 DMG(详细见 高级功能)。

Linux 打包

  1. 打包 Linux 应用

    flutter build linux
    
  2. 构建输出

    • 构建结果位于 build/linux/x64/release/bundle 目录。
  3. 高级打包工具

    • 使用 flutter_distributor 打包为 DEB、RPM 或 AppImage(详细见 高级功能)。

高级功能:flutter_distributor

flutter_distributor 是一个强大的工具,支持跨平台发布和高级打包选项。

安装

确保 flutter_distributor 已安装:

dart pub global activate flutter_distributor

配置文件

为高级打包配置所需的文件:

  • macOS:

    • 如果打包 DMG 安装包:macos/packaging/dmg/make_config.yaml

      title: 应用名称
      contents:
        - x: 448
          y: 244
          type: link
          path: "/Applications"
        - x: 192
          y: 244
          type: file
          path: 应用名称.app
      
    • 如果打包 PKG 安装包:macos/packaging/pkg/make_config.yaml

      install-path: /Applications
      #sign-identity: <your-sign-identity>
      
    • distribute_options.yaml

      output: dist/
      
  • Windows:

    • 如果打包 exe 安装包:windows/packaging/exe/inno_setup.sas

      [Setup]
      AppId={{APP_ID}}
      AppVersion={{APP_VERSION}}
      AppName={{DISPLAY_NAME}}
      AppPublisher={{PUBLISHER_NAME}}
      AppPublisherURL={{PUBLISHER_URL}}
      AppSupportURL={{PUBLISHER_URL}}
      AppUpdatesURL={{PUBLISHER_URL}}
      DefaultDirName={{INSTALL_DIR_NAME}}
      DisableProgramGroupPage=yes
      OutputDir=.
      OutputBaseFilename={{OUTPUT_BASE_FILENAME}}
      Compression=lzma
      SolidCompression=yes
      SetupIconFile={{SETUP_ICON_FILE}}
      WizardStyle=modern
      PrivilegesRequired={{PRIVILEGES_REQUIRED}}
      ArchitecturesAllowed=x64
      ArchitecturesInstallIn64BitMode=x64
      CloseApplications=force
      
      [Languages]
      {% for locale in LOCALES %}
      {% if locale == 'en' %}Name: "english"; MessagesFile: "compiler:Default.isl"{% endif %}
      {% if locale == 'hy' %}Name: "armenian"; MessagesFile: "compiler:Languages\\Armenian.isl"{% endif %}
      {% if locale == 'bg' %}Name: "bulgarian"; MessagesFile: "compiler:Languages\\Bulgarian.isl"{% endif %}
      {% if locale == 'ca' %}Name: "catalan"; MessagesFile: "compiler:Languages\\Catalan.isl"{% endif %}
      {% if locale == 'zh' %}Name: "chinesesimplified"; MessagesFile: "compiler:Languages\\ChineseSimplified.isl"{% endif %}
      {% if locale == 'co' %}Name: "corsican"; MessagesFile: "compiler:Languages\\Corsican.isl"{% endif %}
      {% if locale == 'cs' %}Name: "czech"; MessagesFile: "compiler:Languages\\Czech.isl"{% endif %}
      {% if locale == 'da' %}Name: "danish"; MessagesFile: "compiler:Languages\\Danish.isl"{% endif %}
      {% if locale == 'nl' %}Name: "dutch"; MessagesFile: "compiler:Languages\\Dutch.isl"{% endif %}
      {% if locale == 'fi' %}Name: "finnish"; MessagesFile: "compiler:Languages\\Finnish.isl"{% endif %}
      {% if locale == 'fr' %}Name: "french"; MessagesFile: "compiler:Languages\\French.isl"{% endif %}
      {% if locale == 'de' %}Name: "german"; MessagesFile: "compiler:Languages\\German.isl"{% endif %}
      {% if locale == 'he' %}Name: "hebrew"; MessagesFile: "compiler:Languages\\Hebrew.isl"{% endif %}
      {% if locale == 'is' %}Name: "icelandic"; MessagesFile: "compiler:Languages\\Icelandic.isl"{% endif %}
      {% if locale == 'it' %}Name: "italian"; MessagesFile: "compiler:Languages\\Italian.isl"{% endif %}
      {% if locale == 'ja' %}Name: "japanese"; MessagesFile: "compiler:Languages\\Japanese.isl"{% endif %}
      {% if locale == 'no' %}Name: "norwegian"; MessagesFile: "compiler:Languages\\Norwegian.isl"{% endif %}
      {% if locale == 'pl' %}Name: "polish"; MessagesFile: "compiler:Languages\\Polish.isl"{% endif %}
      {% if locale == 'pt' %}Name: "portuguese"; MessagesFile: "compiler:Languages\\Portuguese.isl"{% endif %}
      {% if locale == 'ru' %}Name: "russian"; MessagesFile: "compiler:Languages\\Russian.isl"{% endif %}
      {% if locale == 'sk' %}Name: "slovak"; MessagesFile: "compiler:Languages\\Slovak.isl"{% endif %}
      {% if locale == 'sl' %}Name: "slovenian"; MessagesFile: "compiler:Languages\\Slovenian.isl"{% endif %}
      {% if locale == 'es' %}Name: "spanish"; MessagesFile: "compiler:Languages\\Spanish.isl"{% endif %}
      {% if locale == 'tr' %}Name: "turkish"; MessagesFile: "compiler:Languages\\Turkish.isl"{% endif %}
      {% if locale == 'uk' %}Name: "ukrainian"; MessagesFile: "compiler:Languages\\Ukrainian.isl"{% endif %}
      {% endfor %}
      
      [Tasks]
      Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if CREATE_DESKTOP_ICON != true %}unchecked{% else %}checkedonce{% endif %}
      Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if LAUNCH_AT_STARTUP != true %}unchecked{% else %}checkedonce{% endif %}
      [Files]
      Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
      ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
      
      [Icons]
      Name: "{autoprograms}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"
      Name: "{autodesktop}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; Tasks: desktopicon
      Name: "{userstartup}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; WorkingDir: "{app}"; Tasks: launchAtStartup
      [Run]
      Filename: "{app}\\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: {% if PRIVILEGES_REQUIRED == 'admin' %}runascurrentuser{% endif %} nowait postinstall skipifsilent
      
      
      [Code]
      function InitializeSetup(): Boolean;
      var
        ResultCode: Integer;
      begin
        Exec('taskkill', '/F /IM 应用名称.exe', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
        Result := True;
      end;
      

      windows/packaging/exe/make_config.yaml

      app_id: 6L913538-42B1-4596-G479-BJ779F21A65D
      publisher: 应用名称
      publisher_url: https://github.com/aaa/aaa
      display_name: 应用名称
      executable_name: 应用名称.exe
      output_base_file_name: 应用名称.exe
      create_desktop_icon: true
      install_dir_name: "{autopf64}\\应用名称"
      setup_icon_file: ..\..\windows\runner\resources\app_icon.ico
      locales:
        - ar
        - en
        - fa
        - ru
        - pt
        - tr
      script_template: inno_setup.sas
      
    • 如果打包 msix windows/packaging/msix/make_config.yaml

      display_name: 应用名称
      publisher_display_name: 应用名称
      identity_name: 应用名称.appioi
      msix_version: 2.5.7.0
      logo_path: windows\runner\resources\app_icon.ico
      capabilities: internetClient, internetClientServer, privateNetworkClientServer
      languages: en-us, zh-cn, zh-tw, tr-tr,fa-ir,ru-ru,pt-br,es-es
      protocol_activation: 应用名称
      execution_alias: 应用名称
      certificate_path: windows\sign.pfx
      certificate_password:
      publisher: CN=8CB43675-F44B-4AA5-9372-E8727781BDC4
      install_certificate: "false"
      enable_at_startup: "true"
      startup_task:
        parameters: --autostart
      
  • Linux:

    • linux/packaging/appimage

      有两个文件linux/packaging/appimage/AppRun

      #!/bin/bash
      
      cd "$(dirname "$0")"
      export LD_LIBRARY_PATH=usr/lib
      
      # Usage info
      show_help() {
      cat << EOF
      Usage: ${0##*/} ...
      start app or app, when no parameter is given, app is executed.
          -v              show version
      EOF
      }
      show_version() {
          printf "app version "
          jq .version <./data/flutter_assets/version.json
      }
      # Initialize variables:
      service=0 #declare -i service
      OPTIND=1
      
      # Resetting OPTIND is necessary if getopts was used previously in the script.
      # It is a good idea to make OPTIND local if you process options in a function.
      
      # if no arg is provided, execute app
      if [[ $# == 0 ]];then
          exec ./app
      else
      
      # processing arguments
      
          case $1 in
              appCli)
                  exec ./appCli ${@:3}
                  exit 0
                  ;;
              h)
                  show_help
                  exit 0
                  ;;
              v)  show_version
                  exit 0
                  ;;
              *)
                  show_help >&2
                  exit 1
                  ;;
          esac
      
      
      
      fi
      

      linux/packaging/appimage/make_config.yaml

      display_name: App名称
      
      icon: ./assets/images/source/ic_launcher_border.png
      
      keywords:
        - Hi
      
      generic_name: App名称
      
      actions:
        - name: Start
          label: start
          arguments:
            - --start
        - name: Stop
          label: stop
          arguments:
            - --stop
      
      categories:
        - Network
      
      startup_notify: true
      
      app_run_file: AppRun
      
      # You can specify the shared libraries that you want to bundle with your app
      #
      # flutter_distributor automatically detects the shared libraries that your app
      # depends on, but you can also specify them manually here.
      #
      # The following example shows how to bundle the libcurl library with your app.
      #
      # include:
      #   - libcurl.so.4
      include: []
      
    • linux/packaging/deb 文件 linux/packaging/deb/make_config.yaml

      display_name: Hiddify
      package_name: hiddify
      maintainer:
        name: hiddify
        email: linux@hiddify.com
      
      priority: optional
      section: x11
      installed_size: 6604
      essential: false
      icon: ./assets/images/source/ic_launcher_border.png
      
      postinstall_scripts:
        - echo "Installed Hiddify"
      postuninstall_scripts:
        - echo "Surprised Why?"
      
      keywords:
        - Hiddify
        - Proxy
        - VPN
        - V2ray
        - Nekoray
        - Xray
        - Psiphon
        - OpenVPN
      
      generic_name: Hiddify
      
      categories:
        - Network
      
      startup_notify: true
      
    • linux/packaging/rpm linux/packaging/rpm/make_config.yaml

      display_name: App
      url: https://github.com/app/app-next/
      license: Other
      
      packager: App
      packagerEmail: linux@App.com
      
      priority: optional
      section: x11
      installed_size: 6604
      essential: false
      icon: ./assets/images/source/ic_launcher_border.png
      
      keywords:
        - Hi
      
      generic_name: App
      
      group: Applications/Internet
      
      startup_notify: true
      

命令示例

  1. 打包 macOS (DMG 和 PKG)

    flutter_distributor package --flutter-build-args=verbose --platform macos --targets dmg,pkg
    
  2. 打包 Windows (EXE 和 MSIX)

    flutter_distributor package --flutter-build-args=verbose --platform windows --targets exe,msix
    
  3. 打包 Linux (DEB、RPM 和 AppImage)

    flutter_distributor package --flutter-build-args=verbose --platform linux --targets deb,rpm,appimage
    

常见问题

1. 如何调试打包错误?

使用 -v 查看详细日志:

flutter build apk -v

2. flutter_distributor 不工作?

确保 dart 的全局路径已配置:

export PATH="$PATH:$HOME/.pub-cache/bin"

3. 打包 Web 访问问题?

检查服务器配置是否正确,确保支持 HTML5 路由。


结语

通过本文档,您可以轻松完成 Flutter 多平台应用的打包与发布。高级用户还可以利用 flutter_distributor 工具实现更复杂的打包需求。如有问题,请参考 Flutter 官方文档 或加入社区寻求帮助。