Блеск и нищета Java для настольных систем
Вы не поверите, но в 2018 году всё ещё нужно разрабатывать Desktop приложения.
Представьте себе такой клуб анонимных Java программистов, запойных и беспробудных, которые сидят и делятся своими проблемами.
— Здравствуйте, меня зовут Юрий, я пишу Desktop приложения в 2018 году.
— Здравствуй, Юрий, давайте ему похлопаем, он смог поделиться своей проблемой!
Да, действительно, мы всё ещё пишем Desktop приложения. Обычно, не очень хочется этим заниматься, чаще всего это legacy проекты. Но бывает, что нужно писать и новые настольные приложения.
Зачем мы всё ещё это делаем, если есть web с его новыми продвинутыми возможностями: Progressive Web Apps, Service Worker, Web RTC, Web GL и т.д?
Под катом расскажу как с этим жить и при чём тут Java.
К сожалению, web и desktop все еще нельзя сравнить по количеству возможностей. Web никогда не даст нам всего, что дает нам пользовательская машина. Прежде всего, это работа с локальными файлами и устройствами. Мы можем обрабатывать большие данные на клиенте, использовать специфичное оборудование, ну и обращаться к чему захотим.
Например, можем использовать сканер отпечатка пальцев и другие модные устройства. Чаще всего дополнительное оборудование используется в enterprise приложениях: банках, ERP, бухучете и т.д. Вот там это всё точно нужно.
Кроме того, есть задачи, которые плохо могут быть решены в web. Главная из них — независимость от серверов и сети.
Особенно важно, если вашим приложением будут пользоваться на лаптопах. Будем считать, что в 2018 лаптопы окончательно победили и от них теперь никуда не деться. Очень часто люди работают с лаптопа в поездах и самолётах оффлайн и им нужно иметь возможность работать локально с данными, которые уже загружены на их машину. В web всё еще сложно реализовывать такое поведение.
А что там с Java?
В последнее время технологии Java для настольных систем не эволюционируют: разработка Swing заморожена навсегда, в SWT нет новых фич. Последняя живая технология для desktop — Java FX.
Влияние web-технологий на desktop-фреймворки очень велико. В Qt изобрели свой Qt Script и QML, основанный на JavaScript, а в Java FX реализовали поддержку CSS. Как это ни прискорбно, CSS в Java FX реализован частично и все свойства компонентов отличаются от свойств в web-UI.
В итоге — мы должны опять изучать конкретный фреймворк для desktop, вместо того чтобы переиспользовать гигантский объем готовых наработок для UI из мира web.
И кадровый вопрос тут выходит на передний план: никто не горит желанием писать настольные приложения на Swing и Java FX, но в то же время, на рынке полно хороших специалистов по web-UI.
Java на desktop вызывает боль не только из-за Java FX / Swing. Недавно, Oracle решил убить технологию Web Start.
Всё идёт к одному — специализированные UI фреймворки для desktop-приложений умирают.
Куда бежать?!
А теперь, о том как переиспользовать ваш опыт web-разработки и web-UI при разработке настольных систем на Java. Так, чтобы вам не пришлось разрабатывать отдельные web и desktop приложения.
Electron.js — это достаточно известный фреймворк, позволяющий использовать web-технологии для разработки desktop-приложений. Это технология, на базе которой построен редактор GitHub Atom. Atom — первое широко-известное приложение для desktop, построенное на HTML / JavaScript / CSS и Node.js.
Electron.js — это Open Source фреймворк, который позволяет вам писать UI для настольных приложений на web-стеке. Вы возможно удивитесь, но целая куча инструментов и приложений сейчас построены на этом фреймворке: Slack / Skype и VS Code используют для UI Electron.js.
В двух словах, Electron состоит из серверного JavaScript — Node.js и интегрированного web-браузера Chromium в одном исполняемом процессе, что позволяет использовать нативные фичи операционной системы: окна, нотификации, иконки в трее и многое другое.
До недавнего времени этот фреймворк предполагал разработку приложений только на JavaScript. Но мы придумали как применить его для наших приложений на Java!
Есть два варианта разработки приложений:
- Скомпилировать Java в JS
- Запустить JVM незаметно от пользователя и показать UI в web-браузере
Если у вас уже есть фронтенд на JS, то думать тут нечего, остаётся его запаковать в Electron.js и получить настольное приложение. Так делают, например, в мессенджере Slack.
А если у вас куча кода на Java? И при этом его нельзя просто так собрать в JS, вы сразу же лишитесь рефлексии, доступа к железу и возможности использования распространенных библиотек.
В таком случае можно использовать специальный модуль Node.js — child_process, позволяющий стартовать дочерние процессы во всех основных операционных системах. По сути, нам нужно реализовать один JS файл, который будет запускать JVM и открывать окно Electron:
if (platform === 'win32') {
serverProcess = require('child_process')
.spawn('cmd.exe', ['/c', 'demo.bat'],
{
cwd: app.getAppPath() + '/demo/bin'
});
} else {
serverProcess = require('child_process')
.spawn(app.getAppPath() + '/demo/bin/demo');
}
Главное не забыть потом этот процесс убить, когда окно приложения будет закрыто, что легко сделать при помощи модуля tree-kill:
const kill = require('tree-kill');
kill(serverProcess.pid, 'SIGTERM', function (err) {
console.log('Server process killed');
serverProcess = null;
mainWindow.close();
});
Полный код такой интеграции есть в моём туториале. В нём используется Vaadin в качестве фреймворка для UI, что позволяет писать весь код на Java без JS, а контейнер сервлетов Jetty интегрирован прямо в приложение. Мы можем запускать приложение без деплоймента, как тот же Spring Boot.
В таком варианте, наш фронтенд будет обращаться к JVM по сети, что как-то странно. Я несколько подсластил пилюлю, сменив транспорт между браузером и JVM на протокол WebSocket. Vaadin позволяет сделать это чрезвычайно просто. Так мы значительно снижаем время на отправку сообщений от фронтенда в JVM по сети, буквально до 1ms и избавляемся от ненужного HTTP мусора: заголовков и кукис, а также не создаём подключение, а всегда используем готовое.
На этом стеке я написал небольшое TODO приложение.
Там вы найдёте ещё несколько классных трюков:
- загрузка статических файлов (CSS / JS) прямо с диска в обход JVM
- доступ к API Electron.js из Java
- кастомная шапка окна на HTML и CSS
- автоматическая установка Node.js из Gradle
Ну, а теперь давайте полностью избавимся от сети! С давних времён в операционных системах *nix и Windows существует API для межпроцессного взаимодействия — IPC. В Windows он называется named pipes, а в *nix — Unix sockets. Это буфер / виртуальный файл в оперативной памяти, который находится под управлением ОС и позволяет двум независимым процессам передавать данные.
Ради разнообразия, такой подход я реализовал на Kotlin и Kotlin.js.
В Node.js мы можем легко создавать как named pipe, так и Unix socket при помощи модуля — net:
const pipeServer = net.createServer(function(stream) {
stream.on('data', function(c) {
console.log('Pipe: ', c.toString().trim());
});
stream.on('end', function() {
pipeServer.close();
});
pipeStream = stream;
console.log('Pipe is connected');
global.pipe.send('hello', {});
}).listen(PIPE_PATH, function () {
// start JVM process here
});
}
Для работы с named pipe из Java / Kotlin нужно использовать класс RandomAccessFile:
val pipe: RandomAccessFile
try {
pipe = RandomAccessFile("\\\\.\\pipe\\demo", "rw")
} catch (f: FileNotFoundException) {
println("Unable to open communication pipe")
exitProcess(-7)
}
var data: String? = pipe.readLine()
while (data != null) {
// do something
data = pipe.readLine()
}
Так мы можем полностью избавиться от сети и не запускать HTTP сервер на машине пользователя. Ну и конечно же производительность у такого решения лучше, чем передавать данные через сетевой стек.
А нам то это зачем?
Как вы возможно знаете, мы делаем инструмент для разработчиков — CUBA Studio, позволяющий быстро писать бизнес-приложения на CUBA Platform.
Мы использовали фреймворк Vaadin в CUBA Platform и CUBA Studio, что позволяло переиспользовать большое количество наработок. С самого первого релиза CUBA Studio была web-приложением, которое запускается локально и показывает UI в web-браузере.
С применением этого подхода мы наконец-то смогли дать разработчикам удобство использования настольного приложения: окна, независимость от браузера, переключение по Alt+Tab и иконку в таскбаре.
CUBA Studio все еще использует сеть, но вместо AJAX там задействован WebSocket. Этого достаточно, чтобы пользователи не чувствовали никаких задержек UI.
Экосистема Electron.js порадовала нас и дополнительными инструментами.
- модули для создания установочных файлов и пакетов
- гладкое автообновление
Как можно догадаться, CUBA Studio это не конечная цель.
Как заменить Swing в наших приложениях
С самого первого публичного релиза платформы мы предоставляем две технологии для построения UI: Web-клиент на базе Vaadin и Desktop-клиент на базе Swing. Реализация Generic UI позволяет нам писать код, который работает в двух вариантах автоматически, конечно же, с соответствующими ограничениями.
Desktop-клиент был разработан в 2011 году на базе Swing, поскольку тогда не было другой стабильной и жизнеспособной технологии для настольных приложений на Java.
Но всё меняется.
Сегодня мы сталкиваемся с новыми требованиями к пользовательским интерфейсам на Desktop: responsive UI, анимации, интеграции с сетевыми сервисами, такими как Google. По сути, приложения двигаются навстречу web UI технологиям, и мы не можем игнорировать это движение.
Я вижу всего одну альтернативу Swing и Java FX — Electron.js. Поэтому и попробовал адаптировать этот подход для наших приложений:
- Берём Web-клиент
- Добавляем Electron.js
- Пакуем всё и отдаём клиенту
Приложения на CUBA Platform могут распространятся в виде Uber JAR. Этот вариант деплоймента позволяет запустить приложение простой командой:
> java -jar app.jar
Нам остаётся только добавить специальный скрипт запуска на Electron и получить Desktop-клиент из готового web-приложения!
Полный код приложения вы найдёте здесь.
Да вы с ума сошли!
Я тоже думал что ребята из GitHub сошли с ума. Но постепенно проникся. Мне хочется иметь гибкий инструмент создания UI, а самый гибкий инструмент сегодня — это HTML / JS / CSS.