JAR Bundler que utiliza OSXAdapter provoca que la aplicación se retrase o finalice

Resuelto kba asked hace 13 años • 3 respuestas

He creado una aplicación Java simple que cada segundo durante 10 segundos consecutivos agrega una nueva fila a un archivo JTable. Consta de tres clases.

La clase principal a la que se llama una vez que se inicia el programa.

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

Un controlador que crea la GUI y la modifica a través dedoWork()

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();
            }
        }
    }
}

Y finalmente, la GUI

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});
    }
}

Como estoy desarrollando para OS X y necesito poder asociar mi aplicación con un determinado tipo de archivo (digamos .jarbundlerproblem), tengo que agrupar mi archivo JARen un Apple Jar Bundler . Lo hice con éxito, mi aplicación se abre, cuenta hasta diez y escribe cada segundo.APP

Ahora, para el problema

De forma predeterminada, hacer doble clic en .jarbundlerproblemy asociar el archivo con mi aplicación no pasará el archivo en el que hice doble clic como argumento a la aplicación. Aparentemente, esto es solo que Java funciona en OS X.

Como necesito poder ver en qué archivo se hizo doble clic, estoy usando OSXAdapter , que es una biblioteca de Java creada por Apple para ese propósito. Esto lo implementé alterando el constructor de mi Controllerclase y agregué otro método 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();
    }
}

Pero después de esta modificación (menor), mi aplicación comienza a funcionar mal. A veces, no se abre, aunque puedo ver en la Consola que acaba de iniciar ( Initializing controllerestá escrito), pero después de algunos intentos, eventualmente se iniciará, pero las ventanas estarán completamente en blanco durante los primeros 10 segundos, y después de eso, se agregarán las 10 filas.

Ayuda

Ahora, he luchado bastante con esto y parece que no hay mucha documentación sobre OSXAdapter ni Jar Bundler. ¿Qué estoy haciendo mal? ¿O no debería usar OSXAdapter o Jar Bundler en primer lugar?

kba avatar Sep 23 '11 00:09 kba
Aceptado

Parece que estás bloqueando el hilo de envío de eventos (EDT). SwingWorkerSería una mejor opción, pero este ejemplo implementa Runnable.

Anexo: puede consultar este proyecto para ver un ejemplo de arquitectura MVC . También muestra cómo construir un paquete de aplicaciones para Mac OS sin utilizar JAR Bundler. Puede encontrar más información sobre MVC aquí .

Además, este ejemplo muestra un enfoque para el desplazamiento automático de un archivo JTable. Haga clic en el pulgar para suspender el desplazamiento; suelte para reanudar.

Anexo: Su aplicación tiene un retraso de 10 segundos al iniciarse. Como esta es la hora exacta a la que Controllerduerme, seguramente estará durmiendo en el EDT. Una sscce sería dispositiva. En su lugar, trabaje en otro hilo y actualice el modelo en el EDT. SwingWorkerTiene un process()método que lo hace automáticamente, o puede usarlo invokeLater()como se muestra a continuación. Hasta que su aplicación esté sincronizada correctamente, hay pocas esperanzas de que los eventos de Apple funcionen.

Anexo: Puede invocarlo isDispatchThread()en el Controllerpara verificar. El proyecto citado incluye una .dmgaplicación para Mac y un antarchivo que crea el paquete in situ a través de target dist2.

Anexo: consulte también los enfoques alternativos que se muestran aquí .

ingrese la descripción de la imagen aquí

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://stackoverflow.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();
            }
        });
    }
}
trashgod avatar Sep 22 '2011 18:09 trashgod

Después de hacerlo, no estoy completamente convencido de que SwingWorker sea una solución más simple (también conocida como: mejor); todavía requiere sincronización de subprocesos adicional (entre el subproceso de trabajo y el subproceso "externo" que pasa los archivos/nombres). De todos modos (aprovechando la oportunidad para aprender, y ya sea por errores :), a continuación se muestra un ejemplo burdo de prueba de concepto para la idea básica:

  • implementar el controlador como SwingWorker, que canaliza la entrada del hilo externo al EDT
  • hacer que acepte entradas (del adaptador, fi) a través de un método doWork(..) que pone en cola la entrada para su publicación
  • implementar doInBackground para publicar sucesivamente la entrada

problemas abiertos

  • sincronizar el acceso a la lista local (no soy un experto en concurrencia, pero estoy bastante seguro de que es necesario hacerlo)
  • detectar de manera confiable el final del hilo externo (aquí simplemente se detiene cuando la cola de entrada está vacía)

Comentarios bienvenidos :-)

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());
}
kleopatra avatar Sep 27 '2011 10:09 kleopatra

Aquí hay una variación del ejemplo de @kleopatra en el que una ejecución continua Controlleracepta nuevas entradas en doWork(), mientras que SwingWorkerprocesa las pendingentradas de forma asincrónica en su hilo de fondo. ArrayBlockingQueuemaneja la sincronización.

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();
    }
}
trashgod avatar Sep 27 '2011 12:09 trashgod