Считается, что код, один раз написанный на Java, безо всяких проблем одинаково хорошо работает на любой платформе, под которую есть виртуальная машина. В первом приближении, если не использовать всякие там JNI, это действительно так. Но вот я, можно сказать, лишь недавно начал играться с языком , а уже успел столкнуться с ситуацией, когда на самом деле это нефига не так.
Напишем простейшую программу, использующую Swing , которая создает пустой фрейм. Также программа создает иконку в системном трее с выпадающим меню. У этого меню есть единственный пункт «Exit», завершающий работу программы. Сворачивание в трей мне было влом реализовывать, но если интересно, рецепт можно найти на StackOverflow .
У меня получился такой код:
import java.awt.* ;
import javax.swing.* ;
import java.awt.event.ActionEvent ;
import java.awt.event.ActionListener ;
import java.net.* ;
public class TrayIconExample {
public static final String APPLICATION_NAME = «TrayIconExample» ;
public static final String ICON_STR = «/images/icon32x32.png» ;
public static void main ( String [ ] args ) {
SwingUtilities . invokeLater ( new Runnable ( ) {
@Override
public void run ( ) {
createGUI ( ) ;
}
} ) ;
}
private static void createGUI ( ) {
JFrame frame = new JFrame ( APPLICATION_NAME ) ;
frame. setMinimumSize ( new Dimension ( 300 , 200 ) ) ;
frame. setDefaultCloseOperation ( WindowConstants . EXIT_ON_CLOSE ) ;
frame. pack ( ) ;
frame. setVisible ( true ) ;
setTrayIcon ( ) ;
}
private static void setTrayIcon ( ) {
if ( ! SystemTray. isSupported ( ) ) {
return ;
}
PopupMenu trayMenu = new PopupMenu ( ) ;
MenuItem item = new MenuItem ( «Exit» ) ;
item. addActionListener ( new ActionListener ( ) {
@Override
public void actionPerformed ( ActionEvent e ) {
System . exit ( 0 ) ;
}
} ) ;
trayMenu. add ( item ) ;
URL imageURL = TrayIconExample. class . getResource ( ICON_STR ) ;
Image icon = Toolkit . getDefaultToolkit ( ) . getImage ( imageURL ) ;
TrayIcon trayIcon = new TrayIcon ( icon, APPLICATION_NAME, trayMenu ) ;
trayIcon. setImageAutoSize ( true ) ;
SystemTray tray = SystemTray. getSystemTray ( ) ;
try {
tray. add ( trayIcon ) ;
} catch ( AWTException e ) {
e. printStackTrace ( ) ;
}
trayIcon. displayMessage ( APPLICATION_NAME, «Application started!» ,
TrayIcon. MessageType . INFO ) ;
}
}
Если запустить эту программу под Windows, то все вполне себе чинно-блинно и работает в соответствии с ожиданиям:
А вот то же приложение, запущенное под Ubuntu Linux в дектоп-окружении Unity :
Налицо сразу множество проблем: (1) фон у иконки серый вместо прозрачного, (2) выпадающее меню выглядит не нативно, (3) а всплывающее сообщение выглядит, как говно. И это еще не самый худший вариант! Под Unity на другой машине всплывающее сообщение вовсе не появляется, а выпадающее меню рисуется как бы зачеркнутым, потому что поверх него рендерится кусок таскбара. Под Gnome не появляется иконка, а всплывающее сообщение появляется где-то чуть ниже нижней границы экрана, при условии, что таскбар расположен вверху. Под i3 с его мизерным таскбаром иконка превращается в желтый квадратик с черной точкой, а пункт меню невозможно прочитать, потому что поверх него отображается всплывающая подсказка нашей иконки.
В общем, иконки в трее под Linux абсолютно неюзабельны. Притом, похоже, что проблема известна уже не менее четырех лет , так что на скорое ее исправление надеяться не приходится.
Какими костылями можно это подпереть:
- Заюзать JNI, отказавшись тем самым от одной из главных фичей Java;
- Таскать за собой нативные приложения под N поддерживаемых платформ, которые будут рисовать нативные иконки и менюшки, взаимодействуя с ними через stdin/stdout или еще как-то;
- Не использовать прозрачность и меню, а также при запуске под Linux показывать уведомления через notify-send, плюс предоставить пользователю возможность указать в настройках альтернативное приложение;
- Под Linux по умолчанию ничего не делать с треем и спрятать соответствующую галочку поглубже в настройках приложения;
- Никогда и ничего не делать с треем, там и без нас уже полно иконок;
Короче, с кроссплатформенностью в Java не все так здорово, как нас пытаются заверить. И это я только поигрываюсь с Java в свободное время. Более опытные Java-разработчики наверняка без труда назовут еще немало примеров. Невольно начинаешь задумываться, а не обман ли вся эта ваша JIT-компиляция? Мало того, что я, несчастный пользователь, должен устанавливать виртуальную машину, так мне еще приходится тратить процессорное время на то, чтобы скомпилировать программу. Выходит, разработчики прислали мне какой-то полуфабрикат? Самим скомпилировать лень было? Да и с точки зрения разработчиков уж не проще ли самостоятельно собирать нативное приложение под N целевых платформ и ни в чем себя не ограничивать?
Исходники к этой заметке лежат тут . Как можно сделать иконку для трея в Gimp рассказано здесь . А там чувак предлагает воркэраунд.
Как всегда, буду рад вашим комментариям.
Дополнение: Запустили jar-ник под маками, вроде там все норм.