JAR Bundler использует OSXAdapter, что приводит к зависанию или прекращению работы приложения

Я создал простое Java-приложение, которое каждую секунду в течение 10 секунд подряд добавляет новую строку в JTable, Он состоит из трех классов.

Основной класс, который вызывается после запуска программы

public class JarBundlerProblem {
    public static void main(String[] args)
    {
        System.err.println("Initializing controller");
        new Controller();
    }
}

Контроллер, который создает графический интерфейс и изменяет его через doWork()

public class Controller {
    public Controller()
    {
        doWork(null);
    }
    public static void doWork(String s)
    {
        GUI gui = new GUI();

        for (int i=0; i<10; i++)
        {
            gui.addRow("Line "+(i+1));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

И, наконец, графический интерфейс

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class GUI {
    private JFrame frame = new JFrame();
    private DefaultTableModel model = new DefaultTableModel();
    private JTable table = new JTable(model);
    private JScrollPane pane = new JScrollPane(table);

    public GUI()
    {
        model.addColumn("Name");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
    }
    public void addRow(String name)
    {
        model.addRow(new Object[]{name});
    }
}

Поскольку я занимаюсь разработкой для OS X, и мне нужно иметь возможность связать свое приложение с определенным типом файлов (скажем, .jarbundlerproblem), Я должен связать мой JAR файл в APP используя Apple Jar Bundler. Я сделал это успешно, мое приложение открывается, считает до десяти, выписывая каждую секунду.

Теперь для проблемы

По умолчанию дважды щелкните .jarbundlerproblem и связывание файла с моим приложением не передаст файл, который я дважды щелкнул в качестве аргумента приложения. Видимо, это просто Java на OS X работает.

Так как мне нужно видеть, по какому файлу дважды щелкнули мышью, я использую OSXAdapter - библиотеку Java, созданную Apple для этой цели. Это я реализовал, изменив конструктор моего Controller класс и добавил еще один метод registerForMacOSXEvents():

public Controller()
{
    registerForMacOSXEvents();
    //doWork(null);
}
public void registerForMacOSXEvents() {
    try {
        OSXAdapter.setFileHandler(this, getClass().getDeclaredMethod("doWork", new Class[] { String.class }));
    } catch (Exception e) {
        System.err.println("Error while loading the OSXAdapter:");
        e.printStackTrace();
    }
}

Но после этой (незначительной) модификации мое приложение начинает работать. Иногда он не открывается, хотя я вижу в консоли, что он только что начал (Initializing controller написано), но после нескольких попыток он в конечном итоге запустится, но окна будут полностью пустыми в течение первых 10 секунд, а после этого будут добавлены 10 строк.

Помогите

Теперь я немного боролся с этим, и похоже, что не так много документации, касающейся ни OSXAdapter, ни Jar Bundler. Что я делаю неправильно? Или я не должен использовать OSXAdapter или Jar Bundler?

3 ответа

Решение

После этого я не до конца убежден, что SwingWorker - более простое (ака: лучше) решение - по-прежнему требуется дополнительная синхронизация потоков (между рабочим потоком и "внешним" потоком, который передается в файле / именах). В любом случае (пользуясь возможностью учиться, и пусть это будет ошибкой:), ниже приведено грубое доказательство примера концепции основной идеи:

  • реализовать контроллер как SwingWorker, который направляет ввод из внешнего потока в EDT
  • заставить его принимать ввод (от адаптера, fi) через метод doWork(..), который ставит входные данные в очередь для публикации
  • реализовать doInBackground для успешной публикации ввода

открытые вопросы

  • синхронизировать доступ к локальному списку (не эксперт по параллелизму, но уверен, что это нужно сделать)
  • надежное определение конца внешнего потока (здесь просто останавливается, когда входная очередь пуста)

Обратная связь приветствуется:-)

public class GUI {
    private JFrame frame = new JFrame();
    private DefaultTableModel model = new DefaultTableModel();
    private JTable table = new JTable(model);
    private JScrollPane pane = new JScrollPane(table);

    public GUI() {
        model.addColumn("Name");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
    }

    public void addRow(String name) {
        model.addRow(new Object[] { name });
    }

    /**
     * Controller is a SwingWorker.
     */
    public static class Controller extends SwingWorker<Void, String> {
        private GUI gui;

        private List<String> pending;

        public Controller() {
            gui = new GUI();
        }

        public void doWork(String newLine) {
            if (pending == null) {
                pending = new ArrayList<String>();
                pending.add(newLine);
                execute();
            } else {
                pending.add(newLine);
            }
        }

        @Override
        protected Void doInBackground() throws Exception {
            while (pending.size() > 0) {
                publish(pending.remove(0));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        /**
         * @inherited <p>
         */
        @Override
        protected void process(List<String> chunks) {
            for (String object : chunks) {
                gui.addRow(object);
            }
        }

    }

    /** 
     * Simulating the adapter.
     * 
     *  Obviously, the real-thingy wouldn't have a reference 
     *  to the controller, but message the doWork refectively 
     */
    public static class Adapter implements Runnable {

        Controller controller;

        public Adapter(Controller controller) {
            this.controller = controller;
        }

        @Override
        public void run() {
            for (int i=0; i<10; i++)
            {
                controller.doWork("Line "+(i+1));
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    public static void main(String[] args)
    {
        System.err.println("Initializing controller");
        new Adapter(new Controller()).run();
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(GUI.class.getName());
}

Похоже, вы блокируете поток рассылки событий (EDT). SwingWorker будет лучшим выбором, но этот пример реализует Runnable,

Приложение: Вы можете посмотреть на этот проект в качестве примера архитектуры MVC. Также показано, как создать пакет приложений Mac OS без использования JAR Bundler. Больше на MVC может быть найдено здесь.

Кроме того, в этом примере показан один из подходов к автоматической прокрутке JTable, Нажмите на большой палец, чтобы приостановить прокрутку; отпустить, чтобы возобновить.

Приложение: Ваше приложение отстает на 10 секунд при запуске. Поскольку это точное время, за которое Controller спит, он наверняка спит на EDT. Sscce будет диспозитивным. Вместо этого выполните работу в другом потоке и обновите модель в EDT. SwingWorker имеет process() метод, который делает это автоматически, или вы можете использовать invokeLater() как показано ниже. Пока ваше приложение не будет правильно синхронизировано, мало надежды на то, что события Apple будут работать.

Приложение: Вы можете вызвать isDispatchThread() в Controller Проверять. Указанный проект включает в себя .dmg с приложением Mac и ant файл, который создает пакет на месте через цель dist2,

Приложение: См. Также альтернативные подходы, показанные здесь.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

/** @seehttps://stackru.com/questions/7519244 */
public class TableAddTest extends JPanel implements Runnable {

    private static final int N_ROWS = 8;
    private static String[] header = {"ID", "String", "Number", "Boolean"};
    private DefaultTableModel dtm = new DefaultTableModel(null, header) {

        @Override
        public Class<?> getColumnClass(int col) {
            return getValueAt(0, col).getClass();
        }
    };
    private JTable table = new JTable(dtm);
    private JScrollPane scrollPane = new JScrollPane(table);
    private JScrollBar vScroll = scrollPane.getVerticalScrollBar();
    private JProgressBar jpb = new JProgressBar();
    private int row;
    private boolean isAutoScroll;

    public TableAddTest() {
        this.setLayout(new BorderLayout());
        jpb.setIndeterminate(true);
        this.add(jpb, BorderLayout.NORTH);
        Dimension d = new Dimension(320, N_ROWS * table.getRowHeight());
        table.setPreferredScrollableViewportSize(d);
        for (int i = 0; i < N_ROWS; i++) {
            addRow();
        }
        scrollPane.setVerticalScrollBarPolicy(
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        vScroll.addAdjustmentListener(new AdjustmentListener() {

            @Override
            public void adjustmentValueChanged(AdjustmentEvent e) {
                isAutoScroll = !e.getValueIsAdjusting();
            }
        });
        this.add(scrollPane, BorderLayout.CENTER);
        JPanel panel = new JPanel();
        panel.add(new JButton(new AbstractAction("Add Row") {

            @Override
            public void actionPerformed(ActionEvent e) {
                addRow();
            }
        }));
        this.add(panel, BorderLayout.SOUTH);
    }

    private void addRow() {
        char c = (char) ('A' + row++ % 26);
        dtm.addRow(new Object[]{
                Character.valueOf(c),
                String.valueOf(c) + String.valueOf(row),
                Integer.valueOf(row),
                Boolean.valueOf(row % 2 == 0)
            });
    }

    private void scrollToLast() {
        if (isAutoScroll) {
            int last = table.getModel().getRowCount() - 1;
            Rectangle r = table.getCellRect(last, 0, true);
            table.scrollRectToVisible(r);
        }
    }

    @Override
    public void run() {
        while (true) {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    addRow();
                }
            });
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    scrollToLast();
                }
            });
            try {
                Thread.sleep(1000); // simulate latency
            } catch (InterruptedException ex) {
                System.err.println(ex);
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                TableAddTest nlt = new TableAddTest();
                f.add(nlt);
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
                new Thread(nlt).start();
            }
        });
    }
}

Вот вариант примера @kleopatra, в котором постоянно работает Controller принимает новые записи в doWork() в то время как SwingWorker обрабатывает pending Записи асинхронно в фоновом потоке. ArrayBlockingQueue обрабатывает синхронизацию.

import java.awt.EventQueue;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.table.DefaultTableModel;

public class GUI {

    private static final Random rnd = new Random();
    private JFrame frame = new JFrame();
    private DefaultTableModel model = new DefaultTableModel();
    private JTable table = new JTable(model);
    private JScrollPane pane = new JScrollPane(table);

    public GUI() {
        model.addColumn("Name");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(pane);
        frame.pack();
        frame.setVisible(true);
    }

    public void addRow(String name) {
        model.addRow(new Object[]{name});
    }

    /**
     * Controller is a SwingWorker.
     */
    private static class Controller extends SwingWorker<Void, String> {

        private static final int MAX = 5;
        private GUI gui;
        private BlockingQueue<String> pending =
            new ArrayBlockingQueue<String>(MAX);

        public Controller() {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    gui = new GUI();
                }
            });
        }

        private void doWork(String newLine) {
            try {
                pending.put(newLine);
            } catch (InterruptedException e) {
                e.printStackTrace(System.err);
            }
        }

        @Override
        protected Void doInBackground() throws Exception {
            while (true) {
                // may block if nothing pending
                publish(pending.take());
                try {
                    Thread.sleep(rnd.nextInt(500)); // simulate latency
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
            }
        }

        @Override
        protected void process(List<String> chunks) {
            for (String object : chunks) {
                gui.addRow(object);
            }
        }
    }

    /** 
     * Exercise the Controller.
     */
    private static class Adapter implements Runnable {

        private Controller controller;

        private Adapter(Controller controller) {
            this.controller = controller;
        }

        @Override
        public void run() {
            controller.execute();
            int i = 0;
            while (true) {
                // may block if Controller busy
                controller.doWork("Line " + (++i));
                try {
                    Thread.sleep(rnd.nextInt(500)); // simulate latency
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("Initializing controller");
        // Could run on inital thread via
        // new Adapter(new Controller()).run();
        // but we'll start a new one
        new Thread(new Adapter(new Controller())).start();
    }
}
Другие вопросы по тегам