Разработка мобильных приложений на Go
Язык программирования Go стал настоящим открытием для разработчиков инструментов для системного администрирования и DevOps благодаря комбинации возможностей низкоуровневой разработки (и в этом он подобен C) и поддержке автоматической сборки мусора, исключению прямой работы с указателями, наличию конкуретной многозадачности (goroutines) и возможности компиляции непосредственно в исполняемый образ. Постепенно Go начал использоваться для других целей: создание веб-приложений, разработка для микроконтроллеров. Почему бы не использовать все его возможности для создания мобильных приложений? В статье мы обсудим некоторые подходы к разработке приложений на Go для мобильных устройств.
Важно отметить, что архитектура мобильных операционных систем разделяет реализацию интерфейса от среды исполнения приложения и это позволяет описывать интерфейс пользователя на любой технологии (не обязательно на Java/Kotlin для Android). Однако важно помнить, что точка входа в приложение и стартовый код в любом случае должны быть созданы на соответствующей технологии (например, код на Dart/Flutter запускается из Java-кода, который подготавливает соответствующую поверхность для отображений и выполняет инициализацию необходимых сервисов из MainActivity), а сборка выполняется через Gradle, как единое приложение (JVM-часть + двоичный код, как результат компиляции исходных текстов). Аналогично с iOS, в этом случае сборку выполняет XCode и точка входа описывается как ViewController, который определен на языке Swift или ObjectiveC. Поэтому для разработки приложения на Go нужно обеспечить аналогичный уровень интеграции и сгенерировать обертку для запуска скомпилированного кода внутри привычной для платформы среды исполнения.
Одной из библиотек для создания мобильных приложений на Go является gomobile (https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile). С помощью gomobile можно создавать как библиотеки, так и полноценные приложения (упакованные в формат apk / ipa). Также поддерживается сборка приложения для OpenAL для работы с аудио с использованием Go.
Установка утилиты командной строки gomobile выполняется из официального репозитория через go install:
go install golang.org/x/mobile/cmd/gomobile@latest
export PATH=~/go/bin:$PATH
gomobile init
Пример приложения можно скачать из пакета golang.org/x/mobile/example/basic (исходные тексты https://cs.opensource.google/go/x/mobile), для сборки примера под соответствующую платформу будет использоваться команда:
gomobile build [-target android|ios] [-androidapi ] [-o filename] package
Соберем простой пример:
go mod init example.com/m
go mod tidy
go get -d golang.org/x/mobile/example/basic
gomobile build -target android -androidapi 19 golang.org/x/mobile/example/basic
Для успешной сборки должен быть установлен компонент NDK (Native Development Kit) для Android SDK. Результатом сборки будет файл apk, который также может быть автоматически установлен на устройство при замене команды gomobile build
на gomobile install
. Также может быть собрана библиотека для подключения к нативному приложению (в Android будет собран как aar, который может быть подключен через gradle implementation, в iOS — как фреймворк, на других платформах будет скомпилирован в динамическую библиотеку).
Приложение регистрируется как аргумент в функции app.Main (app из golang.org/x/mobile/app
) и получает доступ к EGL-контексту для рисования и потоку событий (жизненный цикл приложения, взаимодействия пользователя с интерфейсом, отрисовки экрана). Основное приложение может выглядеть следующим образом:
app.Main(func(a app.App) {
var glctx gl.Context
var sz size.Event
for e := range a.Events() {
switch e := a.Filter(e).(type) {
//... обработка сообщений
}
}
}
Наиболее важные типы сообщений:
lifecycle.Event
— события жизненного цикла, например отображение и скрытие Activity / ViewControllersize.Event
— событие изменение размера (например, изменение ориентации экрана)paint.Event
— событие отрисовки экрана (vsync, в случае с Android отправляется из Choreographer)touch.Event
— событие нажатия на экранkey.Event
— событие от клавиатурыmouse.Event
— событие от мыши (например, может быть на MacOS)
Сообщения могут быть получены от среды исполнения, а также отправлены через объект app.App
(функция send
).
Событие в lifecycle.Event
может быть извлечено через e.Crosses(lifecycle.StageVisible)
и при получении сообщения lifecycle.CrossOn
нужно выполнить инициализацию контекста EGL для построений, lifecycle.CrossOff
освободить контекст.
При разработке приложений интерфейс EGL представляется непосредственно через методы gl.Context
и для построения графических примитивов нужно определить фрагментный и вершинный шейдеры и сконфигурировать буферы для значений, передаваемых в шейдеры. Например, для построения прямоугольника можно определить вершинный шейдер подобным образом:
#version 100
uniform vec2 offset;
attribute vec4 position;
void main() {
//координатная сетка от (-1,-1) до (1,1)
vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0);
gl_Position = position + offset4;
}
Фрагментный шейдер может использовать координаты фрагмента (gl_Coord), но в простом варианте он может определять одноцветную фигуру:
#version 100
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
Для инициализации контекста будем использовать вспомогательные структуры из golang.org/x/mobile/exp/gl/glutil
. На событии CrossOn извлечем контекст EGL:
glctx, _ = e.DrawContext.(gl.Context)
program, err = glutil.CreateProgram(glctx, vertexShader, fragmentShader)
Для построения геометрической фигуры создадим gl.ARRAY_BUFFER и загрузим туда данные о координатах точек, группами по 3 значения с плавающей точкой x, y, z через f32.Bytes(binary.LittleEndian, ...)
. f32 импортируется из golang.org/x/mobile/exp/f32.
Для передачи параметров в шейдеры будем использовать glctx.GetAttributeLocation
(для передачи координат точек через буфер) и glctx.GetUniformLocation
(для передачи значений цвета и смещения).
Построение сцены выполняется обычным для EGL образом:
очистка сцены: glctx.ClearColor (r, g, b, alpha)
подключение программы: glctx.UseProgram (program)
заполнение буферов (glctx.BindBuffer) и значений (glctx.UniformNf, где N — 1, 2, 3 или 4)
построение изображения: glctx.DrawArrays (gl.Triangles, 0, vertexCount)
С использованием шейдеров и возможностей EGL можно как симулировать привычный интерфейс пользователя, так и создавать сложные сцены (например, с поддержкой 3d-объектов, света, камеры и других вычислительных возможностей фрагментного шейдера). При этом будут доступны все возможности стандартных пакетов Go (например, для работы с сетью, математики, криптографии и др.), а также любых подключаемых библиотек Go. Также, кроме поддержки EGL и событий жизненного цикла, доступен доступ к assets (например, для построения объектов с использованием растровых текстур и спрайтов через golang.org/x/mobile/exp/sprite/glsprite
). Экспериментально поддерживается воспроизведение и обработка звука (golang.org/x/mobile/exp/audio/al
), вывод количества кадров в секунду (построение растровых цифр в golang.org/x/mobile/exp/app/debug/fps.go
), поддержка шрифтов (на платформах Android / iOS / MacOS и Linux) в golang.org/x/mobile/exp/font
, а также извлечение данных от акселерометра, магнетометра и гироскопа (golang.org/x/mobile/exp/sensor
).
Поскольку скомпилированный код Go будет выполняться как нативное приложение и имеет прямой доступ к EGL, аудиопотокам, информации с сенсоров и событий пользовательского интерфейса, библиотека GoMobile может использоваться как для разработки приложений с имитацией интерфейсов Material Design / iOS Human Interface Guidelines, так и для создания мультимедийных и игровых приложений с возможностью переиспользования Go-библиотек.
На этом все. Также хочу пригласить всех желающих на бесплатный урок курса Golang Developer по теме: «Тестирование в Go». Зарегистрироваться на урок и узнать о курсе подробнее можно по ссылке ниже.