8

我想在我的 Java 桌面应用程序上生成警报:

  • 使用特定日期/时间设置的警报,可以在 5 分钟或 5 个月内
  • 触发警报时,我需要能够创建 SWT 应用程序
  • 我需要它才能在任何操作系统上工作。软件用户可能会有 Windows(其中 90%),其余的 Mac OS(包括我)
  • 软件许可证必须允许我在商业程序中使用它,而不需要开源它(因此,没有 GPL)
  • 我不能要求用户安装 Cygwin,所以实现需要原生于 Windows 和 Unix

我正在使用 Java、Eclipse、SWT 进行开发,我的应用程序是使用 Java Web Start 从我的服务器部署的。我正在使用 Mac OS X.6 进行开发。


我想我有几个选择:

  1. 在启动时运行我的应用程序,并自己处理所有事情;
  2. 使用系统服务。
  3. 在 Unix 上使用 cron 表,在 Windows 上使用计划任务

在启动时运行

我真的不喜欢这个解决方案,我希望有更优雅的东西。
Refs:我想在 Mac OS/Windows 上的 System Startup 上运行我的 Java 程序。我怎样才能做到这一点?

系统服务

如果我将它作为系统服务运行,我可以从中受益,因为操作系统将确保我的软件:

  • 一直在运行
  • 没有/不需要 GUI
  • 失败时重新启动

我研究了一些我可以使用的资源:

  • run4j — CPL — 仅在 Windows 上运行,似乎是一个有效的候选者
  • jsvc — Apache 2.0 — 仅适用于 Unix,似乎是一个有效的候选者
  • Java Service Wrapper — 各种 — 我买不起付费许可证,而免费的是 GPL。因此,我不想/不能使用这个

我在系统服务选项中的问题是:

  1. 还有其他选择吗?
  2. 我计划的实施是否正确:

    • 在应用程序启动时,检查服务是否存在
    • 如果未安装:
      • 升级用户以安装服务(Unix 上的 root,Windows 上的 UAC)
      • 如果主机操作系统是 Windows,请使用 run4j 注册服务
      • 如果主机操作系统是 Unix,使用 jsvc 注册服务
    • 如果它没有运行,启动它

因此,在第一次运行时,应用程序将安装服务并启动它。当应用程序关闭时,服务仍在运行,并且不再需要该应用程序,除非它未注册。
但是,我想我仍然怀念“启动时运行”功能。

我对吗?我错过了什么吗?

cron / 任务计划程序

在 Unix 上,我可以轻松地使用 cron 表,而无需应用程序将用户升级为 root。我不需要处理重启、系统日期更改等。看起来不错。

在 Windows 上,我可以使用Task Scheduler,甚至在命令行中使用AtSchTasks。这看起来不错,但我需要它从 XP 到 7 兼容,我不能轻易测试它。


那你会怎么做?我错过了什么?你有什么建议可以帮助我选择最好和最优雅的解决方案吗?

4

5 回答 5

3

Bicou:太好了,你分享了你的解决方案!

请注意,“schtasks.exe”有一些本地化问题,如果你想用它创建一个每日触发器,在英文 Windows 上你必须使用“daily”,在德文 Windows 上(例如)你有改为使用“täglich”。

为了解决这个问题,我schtasks.exe使用/xml-option 实现了调用,提供了一个我通过模板创建的临时 xml 文件。

创建此类模板的最简单方法是“手动”创建任务并使用任务管理 GUI 工具中的“导出”功能。

于 2012-10-01T11:22:27.347 回答
2

在您列出的可用选项中,恕我直言,选项 3 更好。由于您只寻找执行应用程序的外部触发器,因此 CRON 或计划任务是比您列出的其他选项更好的解决方案。通过这种方式,您消除了应用程序的复杂性,并且您的应用程序不需要一直运行。它可以在外部触发,当执行结束时,您的应用程序将停止。因此,避免了不必要的资源消耗。

于 2012-08-10T09:44:21.593 回答
2

这是我最终实施的:

public class AlarmManager {
    public static final String ALARM_CLI_FORMAT = "startalarm:";
    public static SupportedOS currentOS = SupportedOS.UNSUPPORTED_OS;

    public enum SupportedOS {
        UNSUPPORTED_OS,
        MAC_OS,
        WINDOWS,
    }

    public AlarmManager() {
        final String osName = System.getProperty("os.name");
        if (osName == null) {
            L.e("Unable to retrieve OS!");
        } else if ("Mac OS X".equals(osName)) {
            currentOS = SupportedOS.MAC_OS;
        } else if (osName.contains("Windows")) {
            currentOS = SupportedOS.WINDOWS;
        } else {
            L.e("Unsupported OS: "+osName);
        }
    }

    /**
     * Windows only: name of the scheduled task
     */
    private String getAlarmName(final long alarmId) {
        return new StringBuilder("My_Alarm_").append(alarmId).toString();
    }

    /**
     * Gets the command line to trigger an alarm
     * @param alarmId
     * @return
     */
    private String getAlarmCommandLine(final long alarmId) {
        return new StringBuilder("javaws -open ").append(ALARM_CLI_FORMAT).append(alarmId).append(" ").append(G.JNLP_URL).toString();
    }

    /**
     * Adds an alarm to the system list of scheduled tasks
     * @param when
     */
    public void createAlarm(final Calendar when) {
        // Create alarm
        // ... stuff here
        final long alarmId = 42;

        // Schedule alarm
        String[] commandLine;
        Process child;
        final String alarmCL = getAlarmCommandLine(alarmId);
        try {
            switch (currentOS) {
            case MAC_OS:
                final String cron = new SimpleDateFormat("mm HH d M '*' ").format(when.getTime()) + alarmCL;

                commandLine = new String[] {
                        "/bin/sh", "-c",
                        "crontab -l | (cat; echo \"" + cron + "\") | crontab"
                };
                child = Runtime.getRuntime().exec(commandLine);
                break;

            case WINDOWS:
                commandLine = new String[] {
                        "schtasks",
                        "/Create",
                        "/ST "+when.get(Calendar.HOUR_OF_DAY) + ":" + when.get(Calendar.MINUTE),
                        "/SC ONCE",
                        "/SD "+new SimpleDateFormat("dd/MM/yyyy").format(when.getTime()), // careful with locale here! dd/MM/yyyy or MM/dd/yyyy? I'm French! :)
                        "/TR \""+alarmCL+"\"",
                        "/TN \""+getAlarmName(alarmId)+"\"",
                        "/F",
                };
                L.d("create command: "+Util.join(commandLine, " "));
                child = Runtime.getRuntime().exec(commandLine);
                break;
            }
        } catch (final IOException e) {
            L.e("Unable to schedule alarm #"+alarmId, e);
            return;
        }

        L.i("Created alarm #"+alarmId);
    }

    /**
     * Removes an alarm from the system list of scheduled tasks
     * @param alarmId
     */
    public void removeAlarm(final long alarmId) {
        L.i("Removing alarm #"+alarmId);
        String[] commandLine;
        Process child;
        try {
            switch (currentOS) {
            case MAC_OS:
                commandLine = new String[] {
                        "/bin/sh", "-c",
                        "crontab -l | (grep -v \""+ALARM_CLI_FORMAT+"\") | crontab"
                };
                child = Runtime.getRuntime().exec(commandLine);
                break;

            case WINDOWS:
                commandLine = new String[] {
                        "schtasks",
                        "/Delete",
                        "/TN \""+getAlarmName(alarmId)+"\"",
                        "/F",
                };
                child = Runtime.getRuntime().exec(commandLine);
                break;
            }
        } catch (final IOException e) {
            L.e("Unable to remove alarm #"+alarmId, e);
        }
    }

    public void triggerAlarm(final long alarmId) {
        // Do stuff
        //...
        L.i("Hi! I'm alarm #"+alarmId);

        // Remove alarm
        removeAlarm(alarmId);
    }
}

用法很简单。使用以下方法安排新警报:

final AlarmManager m = new AlarmManager();
final Calendar cal = new GregorianCalendar();
cal.add(Calendar.MINUTE, 1);
m.createAlarm(cal);

像这样触发警报:

public static void main(final String[] args) {
    if (args.length >= 2 && args[1] != null && args[1].contains(AlarmManager.ALARM_CLI_FORMAT)) {
        try {
            final long alarmId = Long.parseLong(args[1].replace(AlarmManager.ALARM_CLI_FORMAT, ""));
            final AlarmManager m = new AlarmManager();
            m.triggerAlarm(alarmId);
        } catch (final NumberFormatException e) {
            L.e("Unable to parse alarm !", e);
        }
    }
}

在 Mac OS X.6 和 Windows Vista 上测试。该类L是我的全局常量的助手System.out.printlnG保存我的全局常量(这里,我的服务器上用于启动我的应用程序的 JNLP 文件)。

于 2012-08-16T11:49:58.887 回答
1

您也可以尝试使用 Quartz http://quartz-scheduler.org/。它具有类似 CRON 的语法来安排作业。

于 2012-08-10T09:48:10.763 回答
0

我相信你的情况是正确的。由于服务是系统特定的东西,恕我直言,您不应该使用通用包来涵盖所有服务,而是为每个系统提供特定的机制。

于 2012-08-10T09:20:30.897 回答