13 мая 2013 в 11:50
JAVA+Swing в 2013. Стоит ли?
На Хабре Swing не любят. Поиск по «Swing» дает либо нейтральные, либо негативные упоминания. Вот некоторые из них:
- «Java-апплеты (доразвивались до смертельной болезни под названием Swing)»
- «Swing — мягко говоря не самый оптимальный UI фреймворк»
- «Swing был ужасен»
Я не возьмусь утверждать, что Swing — идеал. Это неправда. Однако я постараюсь описать те плюсы и минусы с которыми пришлось столкнуться. Visual Watermark для групповой защиты фотографий. Java версия у меня появилась в 2011. Мне захотелось сделать порт под Mac и вылизать интерфейс, но писать отдельную программу под каждую платформу у меня не было никакой возможности.
В начале 2011 UI-библиотеки для кросс-платформенной разработки были в таком состоянии:
- QML был весь в багах: меню появлялись под компонентами, демка падала, поддержки в QtCreator не было. Ускоренная отрисовка появилась только в Qt5 прошлой осенью.
- Qt не подошел, т.к. был целиком на “нативных” компонентах, а часто требовалось где-то изменить отрисовку.
- Juce подходила по функционалу, не глючила и не падала. Стоила приемлемых денег да еще и с открытым кода. Отпугнул меня C++. Это замечательный язык на котором пишут огромное количество умнейших людей. Учитывая мой маленький опыт и размер задачи, сложность C++ – это перебор. Плюс, выяснилось, что Xcode не умеет рефакторить C++.
- Adobe Air не поддерживает multi-threading.
- Mono+GTK Мне кажется, что к эту комбинация могла бы решить мои проблемы. В тот момент отпугнул очевидный косяк с неработающими горячими клавишами в GTK. Судя по MonoDevelop, он не пофикшен до сих пор.
- JavaFX не было под Mac.
- SWT намного легче, чем Swing и в целом хорош. Не стал писать на SWT потому, что смотрел я его самым последним. Уже было потрачена уйма времени и я закончил эксперименты на первом попавшемся баге («плавали» кнопки по высоте на тулбаре).
На тот момент Java была частью Mac OS X, имела отличный Native Look & Feel, а JRE под Windows весила всего 12 мегабайт. Я был наивно уверен в успехе. В итоге, после 2 или 3х месяцев работы я оказался с первой версией программы на Java Swing.
На сегодняшний день в QML и JavaFX исправлены описанные проблемы. Поэтому, если вы готовы работать со сценическим графом, то вам стоит взять их на тест-драйв.Qt перешел под крыло фирмы Digia. Выпуск бета-версии под iPhone и Android дает надежду на дальнейшее развитие библиотеки.JavaFX стала библиотекой с открытым кодом в феврале этого года. Её совместимость с OpenJDK планируется на JDK 9. Когда выйдет 9ка неизвестно. Релиз 8ой версии запланирован на начало 2014 года.О хорошем
Начну с хорошего. Вдруг подумаете, что я тоже в Swing-хейтеры записался?!
Вся отрисовка hardware-accelerated. Любое Swing-приложение отрисовывается на GPU, от разработчика ничего не требуется. Это делает возможным анимации в приложении. В том числе, когда приложение полноэкранное или развернуто на 24’’ мониторе.
MVC. Swing критикуют за массивность: каждые компонент состои из представления, контроллера и модели. В то же время, это дает возможность быстро добавить нужную фичу в существущий компонент. Все очень гибко.
Java – это managed код. Вы избавляетесь от кучи возможных багов, «доступных» только для C++-разработчиков. Риск Access Violation сведен к минимуму. Хотя это совершенно не означает, что у вас не будет других багов. Утечек памяти, например.
Отличная среда разработки. Eclipse, Intellij IDEA, NetBeans – выбор огромный. Везде есть рефакторинги, форматирование кода, авто-комплит, поддержка unit-тестов,
Очень много библиотек. LayoutManager’ы, работа с нативными объектами, строками, вебом – всего не перечислить. Это огромный козырь Java как платформы.
Очень много ответов на вопросы. Вот, например, доля вопросов на StackOverflow по каждой из UI-библиотек.
Примерно каждый сотый вопрос на StackOverflow – это вопрос о Swing. На практике, это означает, что большинство проблем уже решены. Скорее всего, один-на-один с проблемой вы не останетесь.
О плохом
Предыдущая часть похожа на сладкий пресс-релиз. Исправляюсь. Вот с чем вы можете столкнуться.
Не фиксятся критичные баги. File.exists не работает с момента релиза JDK7 и фикса нет до сих пор. Даже если баг критический, вы можете ждать фикса годами.
Ситуация может стать еще хуже, если вы планируете использовать нативный код. Я столкнулся с ситуацией, когда использование модальных окон (например, открытие OpenFileDialog) приводит к зависаниям на некоторых компьютерах. При том, что Java Native Foundation используется согласно примерам в документации. И баунти на StackOverflow мне не помог:-)
Баг с file.exists можно обойти с помощью классов из java.nio. Это новый API, который был призван решить проблемы производительности с развесистыми папками.
Что нужно сделать:
- Запустить приложение с параметром –Dfile.encoding=UTF-8
- Вместо File.exists используем Files.exists(Paths.get(fileName))
- Вместо File.listFiles используем
try (DirectoryStream<Path> ds = Files.newDirectoryStream(folder)) {
for (Path file : ds) {
// do something
}
} catch (IOException e) {
e.printStackTrace();
}
Или фиксить этот баг самостоятельно и проталкивать закладку через серию ревью.
Swing – только hardware accelerated. Это значит ваше приложение не будет работать в VMware, Parallels или через удаленный рабочий стол. Если вы не готовы с этим мириться, то смотрите в сторону SWT.
Нет 32-битных билдов под Mac. Официальная сборка только 64 бит. К сожалению, я не знаю в чем причина этого решения. Могу лишь гадать, что дело в каких-то багах.
Некоторое время Henri Gomez поддерживал 32-битные и universal билды. Готовые билды можно было скачать с его странички на code.google.com. К моему сожалению, нехватка времени и новая работа заставили Генри свернуть этот проект. Попрощавшись, он выложил свои билдежные скрипты на GitHub:
https://github.com/hgomez/obuildfactory
С их помощью можно собрать OpenJDK под Mac и Linux. Здорово, но не совсем.
С помощью этих скриптов 32-битная версия под Mac не собирается. Внутри JDK огромное количество конфигурационных файлов, в которых зашита сборка строго 64-битной версии для Mac. Изменишь ключ в главном файле и получишь неработоспособную сборку. Каким образом Henri Gomez собирал 32-битные билды мне неизвестно.
Включайте JRE в дистрибутив. Мнение руководителей Oracle о дистрибуции приложений: “standalone self-contained инсталлятор с bundled JRE для целевой платформы – это более удачная модель распространения приложений” (источник). Наиболее вероятная причина этого решения – огромное кол-во уязвимостей в апплетах: Java приняла знамя решета у Flash.
Наиболее жестко поддерживает это ограничение фирма Apple, которая удалила Java в версии Mac OS 10.7 Lion. Также они принудительно отключают ее при установке новых системных обновлений.
JRE 7 весит около 100 Мб. В архиве получается около 50. К сожалению, размер JRE от апдейта к апдейту растет и нам проблему распухшего дистрибутива придется решать.
Не все объекты BufferedImage используют аппаратное ускорение. Только для BufferedImage.TYPE_INT_*. Поэтому, начиная с JDK7, работать с TYPE_4BYTE*, TYPE_3BYTE нецелосообразно.
При доступе к данным растра BufferedImage, картинка перестает рисоваться через GPU. Зачем это сделано понятно: пользователь меняет данные, метода “закончил менять” нет и не понятно когда их пере-заливать в видео-память. По крайней мере, это логично.
В Visual Watermark я использовал C++ библиотеку для загрузки изображений и нужно было полученные пикселы превратить в объект BufferedImage. Менять по-одному пикселу очень медленно и пришлось писать напрямую в буфер растра картинки. Как только я вызвал у растра getData(), все мои картинки перестали ускоряться. Покопавшись в коде DataBufferInt, я нашел решение этой проблемы с помощью reflection и написал небольшой класс-помощник:
import java.awt.*;
import java.awt.image.*;
import java.lang.reflect.Field;
import sun.awt.image.SunWritableRaster;
import sun.java2d.StateTrackableDelegate;
// Standard library prevents image acceleration once getData() method is called
// This class provides a workaround to modify data quickly and still get hw-accel graphics
public class AcceleratedImage {
// Returns data object not preventing hardware image acceleration
public static int[] getDataBuffer(DataBufferInt dataBuffer) {
try {
Field field = DataBufferInt.class.getDeclaredField("data");
field.setAccessible(true);
int[] data = (int[])field.get(dataBuffer);
return data;
} catch (Exception e) {
return null;
}
}
// Marks the buffer dirty. You should call this method after changing the data buffer
public static void markDirty(DataBufferInt dataBuffer) {
try {
Field field = DataBuffer.class.getDeclaredField("theTrackable");
field.setAccessible(true);
StateTrackableDelegate theTrackable = (StateTrackableDelegate)field.get(dataBuffer);
theTrackable.markDirty();
} catch (Exception e) {
}
}
// Checks whether current image is in acceleratable state
public static boolean isAcceleratableImage(BufferedImage img) {
try {
Field field = DataBuffer.class.getDeclaredField("theTrackable");
field.setAccessible(true);
StateTrackableDelegate trackable = (StateTrackableDelegate)field.get(img.getRaster().getDataBuffer());
if (trackable.getState() == sun.java2d.StateTrackable.State.UNTRACKABLE)
return false;
field = SunWritableRaster.class.getDeclaredField("theTrackable");
field.setAccessible(true);
trackable = (StateTrackableDelegate)field.get(img.getRaster());
return trackable.getState() != sun.java2d.StateTrackable.State.UNTRACKABLE;
} catch(Exception e) {
return false;
}
}
public static BufferedImage convertToAcceleratedImage(Graphics _g, BufferedImage img) {
if(!(_g instanceof Graphics2D))
return img; // We cannot obtain required information from Graphics object
Graphics2D g = (Graphics2D)_g;
GraphicsConfiguration gc = g.getDeviceConfiguration();
if (img.getColorModel().equals(gc.getColorModel()) && isAcceleratableImage(img))
return img;
BufferedImage tmp = gc.createCompatibleImage(img.getWidth(), img.getHeight(), img.getTransparency());
Graphics2D tmpGraphics = tmp.createGraphics();
tmpGraphics.drawImage(img, 0, 0, null);
tmpGraphics.dispose();
img.flush();
return tmp;
}
}
Использовать его нужно вот так:
DataBufferInt dataBuffer = (DataBufferInt)bufferedImage.getRaster().getDataBuffer();
int[] data = AcceleratedImage.getDataBuffer(dataBuffer);
// Меняем данные
AcceleratedImage.markDirty(dataBuffer);
Я не проверял этот код для изображений, которые уже были выведены на экран.Нет встроенной анимации и полу-прозрачности. Объект javax.swing.Timer делает две вещи:
- Можно сделать анимацию компоентов.
- Из-за простоты класса, делать ее очень долго.
Есть библиотека Timing Framework, которая позволяет создавать анимации проще. Анимацию можно сделать вот так:
Animator viewAnimator = new Animator.Builder()
.setDuration(duration, TimeUnit.MILLISECONDS) // Устанавливаем длительность анимации
.setStartDirection(Direction.FORWARD)
.setInterpolator(new AccelerationInterpolator(0.3, 0.7)) // Заставляем двигаться с ускорением
.setRepeatCount(1).addTarget(new TimingTargetAdapter() {
@Override
public void timingEvent(Animator source, double fraction) {
// Меняем состояние
repaint();
}
@Override
public void end(Animator source) {
// Делаем что-то по окончанию анимации
}
}).build();
viewAnimator.start();
Чаще всего используется анимация положения и полу-прозрачности. Если с контролем положения в Swing все OK, то полу-прозрачность стандартные компоненты не поддерживают. Проблема не в возможностях графического движка, а в том, что компоненты не имеют свойства getAlpha/setAlpha.
Java-приложение не будет запускаться в Mountain Lion из-за GateKeeper. Чтобы решить эту проблему вам нужно подписаться на программу Mac Developer за $99/год. В замен фирма Apple выдаст вам сертифакт для подписи кода и проблема уйдет.
Подписать бандл с приложением можно вот так:
codesign –s “Developer ID” –f “path-to-my-app.app”
В сумме
На мой взгляд, самый главный минус Swing – это неуверенность в будущем платформы, т.к критичные баги остаются открытыми. Своих фиксов дожидаются только уязвимость в браузерных плагинах. Складывается ощущение, что библиотеку бросили. Все остальные проблемы уже не так важны.
И мне будет очень грустно, если это действительно так. Потому что писать десктоп приложения на Swing быстро, просто и вокруг огромное количество готового и бесплатного кода.
Пока у меня остается надежда, что у разработчиков в Oracle появится время, чтобы решить системные проблемы.
UPDATE: Из комментариев выяснил, что кастомизация контролов в Qt возможна:
Спасибо пользователю silvansky
UPDATE: Пользователь Skyggedans заметил, что в Air появилась поддержка много-поточности. Делается она за счет дополнительных SWF файлов, с которыми можно настроить коммуникацию.
Свежий взгляд
на бег
протестируй кроссовки
нового поколения
Стань
первоиспытателем!
Скачай Windows Server 2012 R2
и выиграй почетную футболку!
Скачать
Немножко отсебятины, почему Свинг не есть очень хорошо. Просто личное впечатление.
В своё время мне, РНР-шнику, пришлось поучаствовать в разработке одного приложения на Джаве. Я делал UI. На Свинге. Вобщем-то, всё было более-менее нормально, пока не возникла одна особенность — проблема с отрисовкой lightweight-компонентов на JDialog (на фрейме всё было отлично, а вот с диалогом — проблемы). Печаль ситуации была еще и в том, что мы были скованы рамками JRE 6 (в JRE 7 это уже работало нормально, но радости не приносило, т.к. использовать всё-равно надо было JRE 6).
Полез гуглить. Чисто случайно нашел данный баг на еще sun-овском(!) багтрекере. Страниц 5 разного рода изобретательности матюков по этому поводу и workaround в почти самом конце + пара комментов, что это должно работать. Workaround базировался на том, что в реализации Свинга для JRE 6 был то ли один метод в реализации JDialog был пабликом вместо логичного привата, то ли что-то в этом роде. Вобщем, работало с использованием глюка самой JRE, вернее, реализации Свинга в ней.
Но — не заработало. Погуглив еще, нашел этот же воркэраунд на каком-то форуме, где люди в комментариях отписались, что в очередном апдейте JRE 6 такой вот хак закрыли, и теперь нифига не работает. Баг с отрисовкой никуда при этом не делся, его никто не исправлял, хотя жалобы были у очень многих. И т.к. у конечного юзера, скорее всего, будет версия JRE 6 с последним апдейтом, надеяться на то, что чисто случайно воркэраунд заработает, нельзя.
Вобщем, после этой истории, в которой меня поразил не столько факт наличия бага, сколько закрытие единственного способа его обойти, отношение к Свингу испортилось окончательно, и впредь, ежели чего, буду руками-ногами отбиваться, прежде чем соглашаться иметь с ним дело.
Программирую на свинге с 96 года (swing-1.1.1, когда еще не был включен в JRE). На мой взгляд, плохая реализация сомнительной идеи (мимикрировать под нативные ОС). Свинг полностью изжил себя лет 7 назад, когда из Сана ушла вся swing-команда: Chet Haase, Romain Guy, Alex Potochkin, Kirill Grouchnikov, Amy Fowler, и др. Java полностью потеряла позиции на рынке десктопов, даже JavaFX теперь уже не спасет. Императивная отрисовка графики, разработанная в 80-х кодах, изжила себя лет 10 назад и во всех системах была заменена декларативным описанием дерева объектов как JavaFX, SVG, HTML. Кроме того, основным недостатком свинга являлись:
— скудный набор компонентов.
— любой шаг влево связан с жуткими сложностями. Я задолбался в сотый раз писать TableCellRenderer для нормального отображения данных в таблице.
— нет нормального Layout-а. GridBagLayout создавался для IDE tools, но никак не для человека. Точку поставил только MigLayout.
— нет нормального LAF. Мимикрия под системные просто ужасна. Был интересный LAF — Substance.
— общая тормознутость интерфейса. Я не знаю почему так получается, но свинговый интерфейс любой апликации лагает. С SWT такой проблемы нет.
Один вопрос: а почему Вы не выбрали архитектуру веб клиента? Например Embedded Jetty + GWT/SmartGWT или Vaadin?
Ниже прокомментирую некоторые ваши высказывания:
> На сегодняшний день в QML и JavaFX исправлены описанные проблемы. Поэтому, если вы готовы работать со сценическим графом, то вам стоит взять их на тест-драйв.
Интерфейс на JavaFX лагает как слайдшоу на моей рабочей тачке, даже банальный скроллинг. Не ведитесь на удочку Oracle, обещающей светлое будущее JavaFX. Поезд ушел 7 лет назад. Все разговоры о портировании JFX на мобильные завораживают, но, пардон, где Java под iOS/Android? Теперь у нас есть HTML5/Canvas/CSS3/SVG.
> Очень много библиотек.
80% из них уже давно не поддерживается.
> Вся отрисовка hardware-accelerated. Любое Swing-приложение отрисовывается на GPU, от разработчика ничего не требуется.
Это не так. Акселерировано толька отрисовка примитивов и опции с растрами. Поскольку разработчик сам пишет код отрисовки компонентов, то он ответственен за то, чтобы акселерация использовалась по-максимому. Известен такой хак как двойная буферизация. У меня есть книга Swing Hacks по всем подобным хакам свинга, которая занимает 300 страниц.
> Swing – только hardware accelerated.
Это тоже не верно. Swing лежит на AWT, который в свою очередь использует Toolkit, который использует graphics pipeline. Последний по возможности использует ресурсы машины. Так что swing работает и на машинах без акселерации, и на remote desktop, только отрисовка долгая, поскольку транслируется весь растр окна.
> Не все объекты BufferedImage используют аппаратное ускорение.
BufferedImage вообще не accelerated. Это просто растр в памяти. ТруЪ accelerated — это VolatileImage и CompatibleImage. При рендеринге BufferedImage по возможности внутренне создается VolatileImage (или CompatibleImage), данные которого лежат на видеокарте. Битность BufferedImage имеет значение только при переводе в VolatileImage: если она у них совпадает, перевод делается на порядок быстрее. Битность VolatileImage зависит от девайса: например X Window не поддерживала альфа канал (не знаю как в JDK7 с ее новой rendering pipeline)
> При доступе к данным растра BufferedImage, картинка перестает рисоваться через GPU.
Не при доступе, а при изменении данных связанный VolatileImage перестает быть валидным до тех пор, пока BufferedImage не отрисуется заново. Поэтому, если растр часто меняется, лучше сразу пользоваться VolatileImage. Единственная деталь — VolatileImage не гарантирует сохранность данных и в любой момент может быть очищет видеокартой.
> Нет встроенной анимации и полу-прозрачности.
Есть (был) проект SwingX, который расширял компоненты, добавляя дополнительные возможности по отрисовке (Painter).
>Один вопрос: а почему Вы не выбрали архитектуру веб клиента? Например Embedded Jetty + GWT/SmartGWT или Vaadin?
Мое мнение: пакетная обработка графики и веб плохо уживаются. Гонять туда-сюда гигабайты файлов: терять время и дорого платить за трафик.
>Я не знаю почему так получается, но свинговый интерфейс любой апликации лагает.
Может вам такие приложения попадались? Например, отзывчивость интерфейса не была приоритетом при разработке? Для бухгалтера не важно, а ведь надо время потратить, вынести часть обработки в отдельный поток…
>во всех системах была заменена декларативным описанием дерева объектов как JavaFX, SVG, HTML
Не могу согласиться, что во ВСЕХ и что декларативное описание дерева — ВСЕГДА хорошо. Например, на дерево объектов очень плохо ложатся интерфейсы с огромным количеством записей. Приходится идти на хитрости с виртуализацией объектов и урезанием пересчета дерева. И даже со всеми ухищрениями не получается достичь производительности императивной отрисовки. Для примера можете поскролить какой-нибудь TableView/Grid на WPF и сравнить его с WinForms, QT или Delphi.
Насчет во ВСЕХ системах даже и не знаю что сказать. На память только WinRT приходит.
> нет нормального LAF. Мимикрия под системные просто ужасна.
Нативный L&F в JDK 6 был просто замечательный: составные кнопки, выплывающие модальные окна — все это было. В JDK 7 несколько поломали.
>Поскольку разработчик сам пишет код отрисовки компонентов, то он ответственен за то, чтобы акселерация использовалась по-максимому.
Не понял этого высказывания. Приведете пример?
> Так что swing работает и на машинах без акселерации, и на remote
Это верно только для Win. Swing не работает без ускорителя на Mac и поэтому не работает под виртуалками и удаленном доступе.
>Не при доступе, а при изменении данных связанный VolatileImage перестает быть валидным до тех пор, пока BufferedImage не отрисуется заново.
Нет. В текущей реализации OpenJDK 7 растр перестает быть ускоренным навсегда.
> Мое мнение: пакетная обработка графики и веб плохо уживаются. Гонять туда-сюда гигабайты файлов: терять время и дорого платить за трафик.
Я не совсем про это. Сервер запускать именно локально а ГУИ рисовать в браузере. При желании все это можно сделать в один клик по иконке: запускается сервер и встроенный в окно браузер без URLя, вкладок и меню. Количество библиотек с виджетами для веба зашкаливает. Взять хотя бы SmartClient или ExtJS. Для любителей Java есть врапперы для GWT. Кроме того, битмапы и императивную графику можно рисовать на Canvas.
> Может вам такие приложения попадались? Например, отзывчивость интерфейса не была приоритетом при разработке?
Нет, это общее впечатление многих пользователей от любого свинг-интерфейса. Netbeans/Idea ощутимо лагают, интерфейс Eclipse более тормозной, но ощущения лагания нет. Еще тормознутость многих апликаций хорошо заметна, когда меняешь лейаут, например тащя JSplitPane. Лейаут в свинге пересчитывается крайне долго, и еще дольше, если количество объектов достаточно большое.
> Нативный L&F в JDK 6 был просто замечательный
Я пришел из тех времен, когда LAF был Metal и Basic. С грехом пополам сделали мимикрию под XP. Потом под Висту с опозданием на год. Последним запилили Nimbus, который ужасен. Сторонние LaF были еще ничего (Alloy, Plastic). В последние годы высказывались идеи по поводу адаптивных лейаутов: это когда например Label выравнивается по font baseline с текстом в следующем TextField, или когда автоматически отключаются бордеры внутри других контейнеров с бордерами. В Substance LaF были потуги на этот счет. Но массово все это так и не было доведено до ума.
> Например, на дерево объектов очень плохо ложатся интерфейсы с огромным количеством записей.
Согласен. С деревом MVC реализовывать неудобно. Но можно. Html, Windows, SWT живут как-то с обычными таблицами, не MVC. Строки для huge-таблицы можно генерить динамически при скроллинге даже в модели с деревом.
> И даже со всеми ухищрениями не получается достичь производительности императивной отрисовки.
А вот здесь не согласен. Умный рендерер может эффективно отсекать невидимые узлы дерева, плюс автоматически делать оптимизации рендеринга: статичное поддерево может быть прозрачно закешировано в виде битмапа. Плюс такие плюшки как декларативная анимация, лееры, эффекты, аффинные преобразования, фильтры, etc… Кроме того, декларативный рендерер позволяет использовать композитные возможности низлежащей платформы (OpenGL, DirectX): лееры и Z-order компонентов могут управляються нативно, тогда как в Swing их каждый раз всех приходится перерисовывать в правильном порядке на канве Graphics2D. Ну и самое главное — асинхронный рендеринг отдельно от Event Dispatch.
> Не понял этого высказывания. Приведете пример?
Рисуя что-то ручками в Graphics2D постоянно приходилось профилировать и думать как это лучше оптимизировать. Большинство оптимизаций сводилось к банальному битмап-кешированию сложных отрисовок, что декларативный рендерер делает автоматически. Другая рекомендуемая оптимизация — брать из Graphics2D clip-регион и не рендерить объекты, которые находятся за его пределами. И еще over 9000 рекомендаций. И все ручками.