简体   繁体   English

JAR Bundler使用OSXAdapter导致应用程序滞后或终止

[英]JAR Bundler using OSXAdapter causing application to lag or terminate

I've created a simple Java application that each second for for 10 seconds consecutive seconds adds a new row to a JTable . 我创建了一个简单的Java应用程序,每秒连续10秒为JTable添加一个新行。 It consists of three classes. 它由三个类组成。

The main class that gets called once the program is started 程序启动后调用的主类

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

A controller that creates the GUI and alters it through doWork() 一个创建GUI并通过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();
            }
        }
    }
}

And finally, the GUI 最后,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});
    }
}

Since I'm developing for OS X, and I need to be able to associate my application with a certain file type (let's say .jarbundlerproblem ), I have to bundle my JAR file into an APP using Apple Jar Bundler . 由于我正在为OS X开发,我需要能够将我的应用程序与某种文件类型相关联(比方说.jarbundlerproblem ),我必须使用Apple Jar Bundler将我的JAR文件捆绑到一个APP I have done this successfully, my application opens, counts to ten, writing out each second. 我已成功完成此操作,我的应用程序打开,计数到十,每秒写出来。

Now, for the problem 现在,针对这个问题

By default, double-clicking a .jarbundlerproblem , and associating the file with my application, will not pass the file I double-clicked as an argument to the application. 默认情况下,双击.jarbundlerproblem并将文件与我的应用程序相关联,将不会将我双击的文件作为参数传递给应用程序。 Apparently, this is just Java on OS X works. 显然,这只是OS X上的Java工作原理。

Since I need to be able to see what file was double-clicked, I'm using OSXAdapter which is a Java library made by Apple for the purpose. 由于我需要能够看到双击的文件,我使用的是OSXAdapter ,这是Apple为此目的制作的Java库。 This, I've implemented by altering the constructor of my Controller class and added another method registerForMacOSXEvents() : 这个,我通过改变我的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();
    }
}

But after this (minor) modification, my application starts acting up. 但经过这次(次要)修改后,我的应用程序开始起作用。 Sometimes, it doesn't open, even though I can see in the Console that it just started ( Initializing controller is written), but after a few attempts, it will eventually start, but the windows will be completely blank for the first 10 seconds, and after that, the 10 rows will be added. 有时,它不会打开,即使我可以在控制台中看到它刚刚启动( Initializing controller已写入),但经过几次尝试后,它最终会启动,但窗口将在前10秒内完全空白,之后,将添加10行。

Help 救命

Now, I've struggled with this quite a bit, and it seems like there isn't a lot of documentation regarding neither OSXAdapter nor Jar Bundler. 现在,我已经相当努力了,似乎没有很多关于OSXAdapter和Jar Bundler的文档。 What am I doing wrong? 我究竟做错了什么? Or shouldn't I be using OSXAdapter or Jar Bundler in the first place? 或者我不应该首先使用OSXAdapter或Jar Bundler?

It looks like you're blocking the event dispatch thread (EDT). 看起来你正在阻止事件派发线程 (EDT)。 SwingWorker would be a better choice, but this example implements Runnable . SwingWorker是一个更好的选择,但是这个例子实现了Runnable

Addendum: You might look at this project for an example of MVC architecture. 附录:您可以查看此项目以获取MVC架构的示例。 It also shows how to construct a Mac OS application bundle without using JAR Bundler. 它还展示了如何在不使用JAR Bundler的情况下构建Mac OS应用程序包。 More on MVC may be found here . 有关MVC的更多信息,请点击此处

As an aside, this example shows one approach to auto-scrolling a JTable . 另外,此示例显示了一种自动滚动JTable Click on the thumb to suspend scrolling; 点击拇指暂停滚动; release to resume. 释放恢复。

Addendum: Your application lags for 10 seconds on startup. 附录:您的应用程序在启动时滞后10秒。 As this is the exact time for which the Controller sleeps, it's surely sleeping on the EDT. 由于这是Controller休眠的确切时间,因此肯定会在EDT上睡觉。 An sscce would be dispositive. 一个sscce将是决定性的。 Instead, do the work on another thread and update the model on the EDT. 相反,在另一个线程上完成工作并在EDT上更新模型。 SwingWorker has a process() method that does so automatically, or you can use invokeLater() as shown below. SwingWorker有一个自动执行的process()方法,或者您可以使用invokeLater() ,如下所示。 Until your application is correctly synchronized, there's little hope of getting Apple events to work. 在您的应用程序正确同步之前,没有希望让Apple事件发挥作用。

Addendum: You can invoke isDispatchThread() in the Controller to check. 附录:您可以在Controller调用isDispatchThread()进行检查。 The project cited includes a .dmg with a Mac application and an ant file that builds the bundle in situ via target dist2 . 引用的项目包括一个带有Mac应用程序的.dmg和一个通过目标dist2 在原位构建软件包的ant文件。

Addendum: See also the alternate approaches shown here . 附录:另请参见此处显示的替代方法。

在此输入图像描述

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

After doing it, I'm not fully convinced a SwingWorker is a simpler (aka: better) solution - still requires additional thread synching (between the worker thread and the "outer" thread which passes in the file/names). 在完成之后,我不完全相信SwingWorker是一个更简单(又名:更好)的解决方案 - 仍然需要额外的线程同步(在工作线程和传入文件/名称的“外部”线程之间)。 Anyway (taking the opportunity to learn, and be it by errors :), below is a crude proof of concept example for the basic idea: 无论如何(借此机会学习,并通过错误:),下面是基本思想的概念证明示例:

  • implement the Controller as SwingWorker, which funnels the input from the outer thread into the EDT 将Controller实现为SwingWorker,它将来自外部线程的输入汇集到EDT中
  • make it accept input (from the adapter, fi) via a method doWork(..) which queues the input for publishing 使其通过方法doWork(..)接受输入(来自适配器,fi),该方法将输入排队以进行发布
  • implement doInBackground to succesively publish the input 实现doInBackground以连续发布输入

open issues 开放式问题

  • synch the access to the local list (not an expert in concurrency, but pretty sure that needs to be done) 同步访问本地列表(不是并发专家,但很确定需要完成)
  • reliably detecting the end of the outer thread (here simply stops when the input queue is empty) 可靠地检测外部线程的结束(这里只是在输入队列为空时停止)

Feedback welcome :-) 欢迎反馈:-)

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

Here's a variation of @kleopatra's example in which a continuously running Controller accepts new entries in doWork() , while a SwingWorker processes the pending entries asynchronously in its background thread. 以下是@ kleopatra 示例的变体,其中连续运行的Controller接受doWork()新条目,而SwingWorker在其后台线程中异步处理pending条目。 ArrayBlockingQueue handles the synchronization. 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();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM