flutter

January 24, 2024 · View on GitHub

github

這裏打包了 flutter 開發環境,使用 code-server 進行代碼編輯

它可用於開發 android/linux 平臺的程式

example

services:
  code:
    image: king011/dev-flutter:3.16.8
    restart: always
    ports:
      # 訪問 http://127.0.0.1:9090 設置啓動 xray 更新代理等 ...
      - "9090:80/tcp"
      # 訪問 http://127.0.0.1:9000 打開 code-server 進行代碼編寫
      - "9000:8080/tcp"
    devices:
      # 將硬件映射到容器 以便  emulator 可以使用 kvm
       - /dev/kvm:/dev/kvm
    volumes:
      # 如果要使用 usb 調試 android 真實設備
      - /dev/bus:/dev/bus

      # 將 x11 映射,以便運行 gui 程式
      - /tmp/.X11-unix:/tmp/.X11-unix

      # 可選的將 android-studio 安裝到 容器
      - $HOME/volumes/flutter/android-studio:/opt/android-studio

      # 持久化 存儲 android sdk
      - $HOME/volumes/flutter/android:/opt/android
      # 持久化 存儲用戶數據
      - $HOME/volumes/flutter/home:/home/dev

      # 將本地項目目錄 掛接到容器中 
      - ./project:/home/dev/project
    environment:
      # 設置 X11
      - DISPLAY=$DISPLAY
      - BIND_ADDR=0.0.0.0:8080
      # 設置上密碼驗證,這樣即時你不在自己電腦旁邊也可以打開瀏覽器遠程開發
      - AUTH=password
      # 如果要遠程開發將密碼設置複雜點,並且推薦設置 https,這裏只是演示設置了很簡單的密碼
      - PASSWORD=123
  xray: # xray 容器用於管理網路,設置透明代理,突破朝鮮網路封鎖
    image: king011/xray-webui:v0.0.7
    restart: always
    environment:
     - XRAY_ADDR=:80
    volumes:
      # 存儲代理設定 
      - ./xray:/data
    cap_add: # 設置上這兩個特權才能在容器中使用 iptables 設置透明代理
      - NET_ADMIN
      - NET_RAW
    # 接管 code 的網路
    network_mode: service:code

android emulator

  • 對於 linux 宿主機可以直接將 kvm 映射到容器來啓動 android emulator,但這無法使用硬件加速,速度會比較慢
  • 對於 windows 宿主機,無法在容器中使用 android emulator

對於這一問題最好的解決方法是在宿主機中啓動 android emulator,容器中建立 tcp 隧道直接在遠程 emulator 中進行調試。

emulator 啓動會創建兩個 tcp 隧道

  • 監聽 :5555(默認) 端口供 adb 連接測試
  • 向 127.0.0.1:5037(默認) 的 adb 服務註冊自己

所以只需要把宿主機的 127.0.0.1:5037 映射到容器(emulator 向容器中的adb服務註冊),並把容器的 :5555 端口映射到宿主機(容器adb將連接宿主機的emulator)。

你可以在容器中使用下述指令創建端口映射

ssh -N -R 127.0.0.1:5037:127.0.0.1:5037 -L 127.0.0.1:5555:127.0.0.1:5555 Your_Host_User@Your_Host_IP

你可以使用下述兩個指令,來驗證 adb 是否識別到了遠程的 emulator

# 查詢 android 設備
adb devices

# 查詢 flutter 支持的設備
flutter devices

streamf

除了使用 ssh 映射容器和宿主機也可以使用 streamf 來映射,它是一個強大靈活的 stream 轉發工具。如果 emulator 運行在 windwos 或遠程服務器上,使用 streamf 可能會比 ssh 更加容易

services:
  code:
    image: king011/dev-flutter:3.16.8
    restart: always
    ports:
      # 訪問 http://127.0.0.1:9090 設置啓動 xray 更新代理等 ...
      - "9090:80/tcp"
      # 訪問 http://127.0.0.1:9000 打開 code-server 進行代碼編寫
      - "9000:8080/tcp"
    devices:
      # 將硬件映射到容器 以便  emulator 可以使用 kvm
       - /dev/kvm:/dev/kvm
    volumes:
      # 如果要使用 usb 調試 android 真實設備
      - /dev/bus:/dev/bus

      # 將 x11 映射,以便運行 gui 程式
      - /tmp/.X11-unix:/tmp/.X11-unix

      # 可選的將 android-studio 安裝到 容器
      - $HOME/volumes/flutter/android-studio:/opt/android-studio

      # 持久化 存儲 android sdk
      - $HOME/volumes/flutter/android:/opt/android
      # 持久化 存儲用戶數據
      - $HOME/volumes/flutter/home:/home/dev

      # 將本地項目目錄 掛接到容器中 
      - ./project:/home/dev/project
    environment:
      # 設置 X11
      - DISPLAY=$DISPLAY
      - BIND_ADDR=0.0.0.0:8080
      # 設置上密碼驗證,這樣即時你不在自己電腦旁邊也可以打開瀏覽器遠程開發
      - AUTH=password
      # 如果要遠程開發將密碼設置複雜點,並且推薦設置 https,這裏只是演示設置了很簡單的密碼
      - PASSWORD=123
  xray: # xray 容器用於管理網路,設置透明代理,突破朝鮮網路封鎖
    image: king011/xray-webui:v0.0.7
    restart: always
    environment:
     - XRAY_ADDR=:80
    volumes:
      # 存儲代理設定 
      - ./xray:/data
    cap_add: # 設置上這兩個特權才能在容器中使用 iptables 設置透明代理
      - NET_ADMIN
      - NET_RAW
    # 接管 code 的網路
    network_mode: service:code
  streamf:
    image: king011/streamf:v0.0.2
    restart: always
    volumes:
      - ./streamf.jsonnet:/data/streamf.jsonnet:ro
    network_mode: service:code
  host:
    image: king011/streamf:v0.0.2
    restart: always
    volumes:
      - ./host.jsonnet:/data/streamf.jsonnet:ro
    network_mode: host
    depends_on:
      - streamf

streamf 使用 service:code 的網路,它用於在容器中創建 tcp 隧道 streamf.jsonnet 定義了它的行爲

local ports = std.range(5552, 5555);
local timeout = '1s';
{
  logger: {
    level: 'info',
    source: false,
  },
  pool: {
    size: 1024 * 32,
    cache: 128,
  },
  dialer: [
    {
      tag: 'dialer_android',
      timeout: timeout,
      url: 'basic://',
      network: 'pipe',
      addr: 'pipe-l/android.socket',
      retry: 2,
    },
    {
      tag: 'dialer_adb',
      timeout: timeout,
      url: 'basic://127.0.0.1:5037',
      retry: 2,
    },
  ] + [
    {
      tag: 'dialer_emulator_' + port,
      timeout: timeout,
      url: 'basic://',
      network: 'portal',
      addr: 'portal_' + port,
    }
    for port in ports
  ],
  listener: [
    {
      network: 'tcp',
      addr: ':8000',
      mode: 'http',
      router: [
        {
          method: 'WS',
          pattern: '/dev/android',
          access: 'any token, but listener and dialer must matched. allow empty too.',
          dialer: {
            tag: 'dialer_android',
            close: '1s',
          },
        },
      ],
    },
  ] + [
    {
      network: 'pipe',
      addr: 'pipe-l/android.socket',
      mode: 'http',
      router: [
        {
          method: 'POST',
          pattern: '/adb',
          dialer: {
            tag: 'dialer_adb',
            close: '1s',
          },
        },
      ] + [
        {
          method: 'POST',
          pattern: '/emulator/' + port,
          portal: {
            tag: 'portal_' + port,
            timeout: timeout,
            heart: '40s',
            heartTimeout: '1s',
          },
        }
        for port in ports
      ],
    },
  ] + [
    {
      network: 'tcp',
      addr: ':' + port,
      dialer: {
        tag: 'dialer_emulator_' + port,
        close: '1s',
      },
    }
    for port in ports
  ],
}

host 例子中使用了宿主機的網路,如果 emulator 不在宿主機(遠程運行 emulator)或宿主機是 windwos 則可以刪除 host 容器,但需要在運行 emulator 的機器上使用 host.jsonnet 運行 streamf 以建立 tcp 隧道。host.jsonnet 定義如下

local ports = std.range(5552, 5555);
local timeout = '1s';
local addr = '10.89.1.20:8000';
{
  logger: {
    level: 'info',
    source: false,
  },
  pool: {
    size: 1024 * 32,
    cache: 128,
  },
  dialer: [
    {
      tag: 'dialer_android',
      timeout: timeout,
      url: 'ws://' + addr + '/dev/android',
      access: 'any token, but listener and dialer must matched. allow empty too.',
      retry: 2,
    },
    {
      tag: 'dialer_adb',
      timeout: timeout,
      url: 'http://abc.com/adb',
      method: 'POST',
      network: 'pipe',
      addr: 'pip-c/android.socket',
      retry: 2,
    },
  ] + [
    {
      tag: 'emulator_' + port,
      timeout: timeout,
      url: 'basic://127.0.0.1:' + port,
      retry: 2,
    }
    for port in ports
  ],
  bridge: [
    {
      timeout: timeout,
      url: 'http://abc.com/emulator/' + port,
      method: 'POST',
      network: 'pipe',
      addr: 'pip-c/android.socket',
      dialer: {
        tag: 'emulator_' + port,
        close: '1s',
      },
    }
    for port in ports
  ],
  listener: [
    {
      network: 'pipe',
      addr: 'pip-c/android.socket',
      dialer: {
        tag: 'dialer_android',
        close: '1s',
      },
    },
    {
      network: 'tcp',
      addr: ':5037',
      dialer: {
        tag: 'dialer_adb',
        close: '1s',
      },
    },
  ],
}

baseURL 應該依據你的真實情況修改爲連接 streamf 容器的 ip