Статьи

Java + Swing 2013 года. Стоит ли это?

Java Swing был создан довольно давно. Если вы попытаетесь выполнить поиск по ключевым словам «Swing», вы не получите много статей. Но вы обнаружите, что некоторые люди действительно ненавидят Swing:

  • «10 вещей, которые я никогда не хочу увидеть, чтобы Java-разработчик делал снова … 2. Пишите / отлаживайте или даже используйте приложение Swing»
  • «В отличие от Swing … который был безобразен сам по себе»

На мой взгляд, это несправедливо. Я не говорю, что Swing идеален. Это конечно не правда. Но я постараюсь описать сильные и слабые стороны настоящего Swing, с которыми я столкнулся в своей практике.

Зачем использовать Swing?

Я использую Swing пару лет, в основном по вечерам. Я занимаюсь разработкой Visual Watermark , приложения для защиты нескольких фотографий от нарушения авторских прав. В 2011 году я сделал версию на Java. Я хотел портировать свое приложение для Mac и отточить пользовательский интерфейс, но о разработке отдельного приложения для каждой платформы не могло быть и речи. В начале 2011 года для межплатформенной разработки были доступны следующие библиотеки пользовательского интерфейса:

  • QML был полностью заполнен ошибками: под компонентами появилось меню, демонстрационная версия постоянно зависала, и QML не поддерживался QtCreator. Ускоренный рендеринг изображений был реализован только прошлой осенью, в Qt5.
  • Qt не отвечал моим потребностям, потому что в то время QML был недоделанным, поэтому мне пришлось бы написать код на C ++. Конечно, C ++ — великолепный язык, используемый многими умными людьми. Однако, учитывая мой недостаток опыта и сложность поставленной задачи, использование C ++ было бы излишним. Более того, я обнаружил, что XCode не может реорганизовать код C ++.
  • Функциональные возможности Juce вполне соответствовали моим потребностям. Это не было глючно и не разбилось. Это было доступно, и с открытым исходным кодом тоже! Juce не одобряет использование операторов new / delete, позволяя программисту создавать объекты только в стеке. Теоретически это должно было уменьшить количество ошибок, связанных с указателями. Я не знаю, так ли это, потому что я остановился на кодировании в C ++.
  • В Adobe Air поддержка многопоточности слишком сложна. Вы должны создать отдельный SWF-проект для каждого потока, и сложно обмениваться данными между потоками. В простейшем тесте производительности вычисления целых чисел ActionScript сильно отстает от реализаций Java, C # и C ++.
  • Казалось бы, комбинация Mono + GTK могла бы решить мои проблемы. Но в то время я решил не использовать его из-за очевидной ошибки: горячие клавиши не работали в GTK, когда использовалась неанглийская раскладка. Судя по MonoDevelop, эта ошибка еще не исправлена. Возможно, есть и другие серьезные недостатки.
  • JavaFX не был доступен для Mac.
  • SWT намного проще в использовании, чем Swing. В общем, SWT довольно хорош. Я не использовал эту библиотеку просто потому, что она была последней, которую я рассмотрел. Я уже потратил много времени, поэтому я просто прекратил свои эксперименты, как только обнаружил ошибку (кнопки «плавали» вверх и вниз на панели инструментов).

В то время Java была частью Mac OS X и имела отличный внешний вид. А размер дистрибутива JRE для Windows составлял всего 12 мегабайт. У меня была иллюзия, что я обязательно преуспею. В результате после двух или трех месяцев работы у меня появилась первая версия моего приложения, основанная на Java Swing. 

Ошибки, о которых я упоминал, уже были исправлены в QML и JavaFX. Так что, если вы готовы работать с живописным графиком, вы можете попробовать их. Qt теперь управляется Digia, которая выпустила бета-версию для iPhone и Android. Надеюсь, у библиотеки есть будущее.

JavaFX был открыт с февраля этого года. В JDK 9 ожидается, что он станет совместимым с OpenJDK. Сложно сказать, когда выйдет JDK 9. Выпуск JDK 8 запланирован на начало 2014 года.

Хорошие вещи

Я начну с некоторых хороших вещей, так что не думайте, что я просто еще один ненавистник Swing. 

Все рендеринг с аппаратным ускорением. Каждое приложение Swing отображается на GPU без каких-либо усилий со стороны программиста. Таким образом, вы можете использовать анимацию в своем приложении, даже если оно будет работать в полноэкранном режиме или максимально развернуто на экране Full HD. 

MVC. Swing критикуется за то, что он «тяжелый»: любой компонент состоит из представления, контроллера и модели. Однако этот подход позволяет легко добавить новую функцию в существующий компонент. Все очень гибко. 

Java — это управляемый код. Вы можете избавиться от множества ошибок, «доступных исключительно» для разработчиков C ++. Риск нарушения прав доступа сведен к минимуму. Но это не значит, что в вашей программе не будет других ошибок (таких как утечки памяти). 

Это отличная среда разработки. Используйте Eclipse, Intellij IDEA или NetBeans — все, что лучше соответствует вашим потребностям. Каждая из этих IDE предлагает вам рефакторинг, форматирование кода, автозаполнение, поддержку модульных тестов и множество библиотек. Менеджеры макетов, поддержка нативных объектов, строк и Интернета — вы называете это. Все это делает Java прекрасной платформой. 

Много ответов на вопросы. Например, взгляните на вопросы StackOverflow для каждой библиотеки пользовательского интерфейса. Один из ста вопросов о Swing. На практике это означает, что большинство проблем уже решены. Скорее всего, вам не придется бороться с проблемой самостоятельно.

Плохие вещи

Предыдущая часть звучала как приятный пресс-релиз, но я могу это исправить. 🙂 Вот что вы можете встретить на практике. 

Критические ошибки не исправлены. File.exists не работает с момента выпуска JDK7, и пока нет исправлений. Даже если ошибка является критической, вам, возможно, придется ждать исправления буквально годами. Ситуация может стать еще хуже, если вы собираетесь использовать нативный код. Я обнаружил, что использование модальных окон (например, открытие OpenFileDialog) может заморозить мое приложение на некоторых компьютерах. Это происходит, даже если вы используете Java Native Foundation, как показано в примерах в его документации. Предложение щедрости за решение моей проблемы в StackOverflow мне тоже не помогло. 🙂 На самом деле вы можете избежать ошибки File.exists с помощью классов java.nio. Java.nio — это новый API, целью которого изначально было решение проблем производительности при работе с большими папками. Что ты должен делать:

  1. Запустите приложение с параметром
    –Dfile.encoding=UTF-8
  2. Вместо File.exists используйте
    Files.exists(Paths.get(fileName))
  3. Вместо File.listFiles используйте 
    try (DirectoryStream ds = Files.newDirectoryStream(folder)) {
            for (Path file : ds) {
            // do something
            }
    } catch (IOException e) {
            e.printStackTrace();
    }

Или вы можете попробовать исправить ошибку, а затем протолкнуть ее через серию обзоров. 

Swing только с аппаратным ускорением (только для Mac!). Это означает, что ваше приложение не будет работать в VMware или Parallels, и удаленный рабочий стол тоже не будет работать. Если эти ограничения не приемлемы, SWT может быть вашим выбором. 

Для Mac нет 32-битных сборок. Официальная сборка только 64-битная. Увы, я не знаю, в чем причина этого выбора. Я могу только догадываться, что некоторые ошибки могут иметь место. Анри Гомес некоторое время поддерживал 32-битные и универсальные сборки. Вы можете скачать готовые сборки с его страницы на code.google.com. К сожалению, нехватка времени и новая работа заставили Анри перестать поддерживать его проект. Он попрощался со всеми и загрузил свои скрипты сборки на GitHub: https://github.com/hgomez/obuildfactory. Вы можете использовать их для создания OpenJDK для Mac или Linux. Это хорошо, но не круто. Вы не можете использовать сценарии для создания 32-битной сборки для Mac.JDK содержит тонны конфигурационных файлов, которые позволяют создавать только 64-битную сборку для Mac. Если вы измените ключ в основном файле, сборка не будет работать вообще. Я не знаю, как Анри Гомес сумел сделать 32-битные сборки. 

Включите JRE в ваш дистрибутив. Руководство Oracle считает, что автономный автономный установщик с JRE для целевой платформы является лучшей моделью для распространения приложений. Наиболее вероятной причиной такого подхода является то, что Java-апплеты содержат множество уязвимостей. Раньше Flash был плохим парнем, но теперь Java заняла свое место. Apple, похоже, является самым решительным сторонником подхода Oracle. Он даже удалил Java в Mac OS 10.7 Lion. Apple также принудительно отключает Java при установке системных обновлений. Размер JRE 7 составляет около 100 мегабайт, или около 50 мегабайт при архивировании. К сожалению, размер JRE растет с каждым обновлением, и это не хорошо. 

Не все объекты BufferedImage используют аппаратное ускорение. Аппаратное ускорение используется только для BufferedImage.TYPE_INT_ *. Итак, начиная с JDK7, не стоит использовать TYPE_4BYTE * или TYPE_3BYTE. 

При доступе к растровым данным BufferedImage изображение больше не отображается в графическом процессоре. Причина такого подхода ясна: если пользователь изменяет данные, вы не знаете, когда нужно повторно загрузить их в видеопамять, поскольку метод «изменение завершен» отсутствует. По крайней мере, это логично. При разработке Visual Watermark я использовал библиотеку C ++ для загрузки изображений, и мне нужно было преобразовать полученные пиксели в объект BufferedImage. Работа попиксельная была слишком медленной, поэтому мне пришлось записывать изображение прямо в растровый буфер. Как только я вызвал getData () для растра, аппаратное ускорение перестало работать со всеми моими картинками. Я изучил код DataBufferInt и нашел решение этой проблемы, используя рефлексию. Вот мой маленький вспомогательный класс:

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); // Изменение dataAcceleratedImage.markDirty (dataBuffer);

Я не тестировал приведенный выше код ни на одном из отображаемых изображений.

Нет встроенной поддержки анимации или полупрозрачности. Объект javax.swing.Timer хорош для двух вещей:

  1. Вы можете использовать его для добавления анимации к вашим компонентам.
  2. Класс очень прост, поэтому на это уходит много времени.

Библиотека Timing Framework предлагает более простой способ создания анимации. Вы можете создать анимацию следующим образом:

Аниматор viewAnimator = new Animator.Builder () .setDuration (duration, TimeUnit.MILLISECONDS) .setStartDirection (Direction.FORWARD) .setInterpolator (новый AccelerationInterpolator (0,3, 0,7)) .setRepeatCount (1) .addTarget {new @ Timber () Переопределить public void timerEvent (источник Animator, двойная дробь) {// Изменение состояния здесь repaint ();} @Override public void end (источник Animator) {// Сделать что-то после завершения анимации}}). Build (); viewAnimator.start ();

Чаще всего используется анимация положения или полупрозрачности. Swing позволяет точно контролировать положение, но его стандартные компоненты не поддерживают полупрозрачность. Проблема заключается не в ограничениях графического движка, а в отсутствии свойства getAlpha / setAlpha в компонентах.

Java applications won’t run in Mountain Lion because of GateKeeper. To solve the problem, you have to sign up for the Mac Developer Program, which will cost you $99 a year. Apple will give you a certificate for signing your code, and the problem will be over. You can sign your application bundle as follows: codesign –s «Developer ID» –f «path-to-my-app.app»

Bottom line

In my opinion, Swing’s major drawback is that you can never be sure about its future because of some critical bugs. Only browser plugin vulnerabilities are fixed from time to time. It almost looks like the library is no longer supported. All other problems seem insignificant in comparison. 

It makes me sad to think that Swing may be gone any time soon. You see, Swing lets you develop desktop applications quickly and easily, and there are tons of ready-made, free code that you can reuse. 

I still hope that Oracle developers will find some time and fix the problems in their platform.