На Хабре Swing не любят. Поиск по «Swing» дает либо нейтральные, либо негативные упоминания. Вот некоторые из них:
- «Java-апплеты (доразвивались до смертельной болезни под названием Swing)»
- «Swing — мягко говоря не самый оптимальный UI фреймворк»
- «Swing был ужасен»
Я не возьмусь утверждать, что Swing — идеал. Это неправда. Однако я постараюсь описать те плюсы и минусы с которыми пришлось столкнуться.
Почему 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 появится время, чтобы решить системные проблемы.
Автор: ivann