8

Java デスクトップ アプリケーションでアラームを生成したい :

  • 5 分または 5 か月の特定の日時で設定されたアラーム
  • アラームがトリガーされたときに SWT アプリケーションを作成できるようにする必要があります
  • どのOSでも動作できるようにするためにこれが必要です。ソフトウェア ユーザーはおそらく Windows (90%) を使用し、残りは Mac OS (私を含む) を使用します。
  • ソフトウェア ライセンスは、商用プログラムで使用することを許可する必要があり、オープン ソースにする必要はありません (したがって、GPL はありません)。
  • ユーザーに Cygwin のインストールを要求することはできないため、実装は Windows および Unix にネイティブである必要があります。

私は Java、Eclipse、SWT を使用して開発を行っており、アプリケーションは Java Web Start を使用してサーバーからデプロイされています。開発には Mac OS X.6 を使用しています。


いくつかのオプションがあると思います:

  1. 起動時にアプリケーションを実行し、すべてを自分で処理します。
  2. システムサービスをご利用ください。
  3. Unix では cron テーブルを使用し、Windows ではスケジュールされたタスクを使用します。

起動時に実行

私はこのソリューションがあまり好きではありません。もっとエレガントなものを望んでいます。
参照: Mac OS/Windows のシステム スタートアップで Java プログラムを実行したいと考えています。これどうやってするの?

システムサービス

これをシステム サービスとして実行すると、OS によってソフトウェアが次のように保証されるため、この恩恵を受けることができます。

  • 常に実行されています
  • GUI を持っていない/必要としない
  • 失敗時に再起動

使用できるリソースをいくつか調査しました。

  • run4j — CPL — Windows のみで動作し、有効な候補のようです
  • jsvc — Apache 2.0 — Unix のみ、有効な候補のようです
  • Java Service Wrapper — さまざま — 有料のライセンスを買う余裕はありません。無料のライセンスは GPL です。したがって、私はこれを使用したくない/使用できません

システム サービス オプションに関する私の質問は次のとおりです。

  1. 他のオプションはありますか?
  2. 私の計画した実装は正しいですか:

    • アプリ起動時にサービスの有無を確認
    • インストールされていない場合:
      • ユーザーをエスカレートしてサービスをインストールします (Unix では root、Windows では UAC)。
      • ホスト OS が Windows の場合、run4j を使用してサービスを登録します。
      • ホスト OS が Unix の場合、jsvc を使用してサービスを登録します。
    • 実行されていない場合は、開始します

したがって、最初の実行時に、アプリケーションはサービスをインストールして開始します。アプリケーションが閉じられても、サービスはまだ実行されており、登録が解除された場合を除いて、アプリケーションが再び必要になることはありません。
ただし、「起動時に実行」機能がまだ欠けていると思います。

私は正しいですか?何か不足していますか?

cron / タスク スケジューラ

Unix では、アプリケーションがユーザーを root にエスカレートする必要なく、cron テーブルを簡単に使用できます。再起動やシステム日付の変更などを処理する必要はありません。いいですね。

Windows では、AtまたはSchTasksを使用するコマンド ラインでもTask Schedulerを使用できます。これは良さそうに見えますが、XP から 7 まで互換性を持たせるにはこれが必要であり、これを簡単にテストすることはできません。


それで、あなたは何をしますか?私は何か見落としてますか?最善かつ最もエレガントなソリューションを選択するのに役立つアドバイスはありますか?

4

5 に答える 5

3

Bicou: ソリューションを共有していただき、ありがとうございます。

「schtasks.exe」にはローカライズの問題があることに注意してください。それを使用して毎日のトリガーを作成する場合、英語の Windows では「毎日」を使用する必要があり、ドイツ語の Windows では (たとえば)、代わりに「täglich」を使用します。

この問題を解決するためschtasks.exeに、/xml-option を使用して呼び出しを実装し、テンプレートで作成する一時的な xml ファイルを提供しました。

このようなテンプレートを作成する最も簡単な方法は、タスクを「手動で」作成し、タスク管理 GUI ツールの「エクスポート」機能を使用することです。

于 2012-10-01T11:22:27.347 に答える
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);
        }
    }
}

MacOSX.6およびWindowsVistaでテスト済み。このクラスは、私のグローバル定数(ここでは、アプリケーションの起動に使用されるサーバー上のJNLPファイル)LのヘルパーでSystem.out.printlnあり、保持しています。G

于 2012-08-16T11:49:58.887 に答える
2

リストした利用可能なオプションの中で、IMHOオプション3の方が優れています。アプリケーションを実行するための外部トリガーのみを探しているため、CRONまたはスケジュールされたタスクは、リストした他のオプションよりも優れたソリューションです。このようにして、アプリケーションから複雑さを取り除き、アプリケーションを常に実行する必要もありません。外部でトリガーされる可能性があり、実行が終了すると、アプリケーションは停止します。したがって、不要なリソースの消費が回避されます。

于 2012-08-10T09:44:21.593 に答える
1

Quartzhttp: //quartz-scheduler.org/を使用することもできます。ジョブをスケジュールするためのCRONのような構文があります。

于 2012-08-10T09:48:10.763 に答える
0

あなたのシナリオは正しいと思います。サービスはシステム固有のものであるため、私見では、それらすべてをカバーするために汎用パッケージを使用するのではなく、すべてのシステムに固有のメカニズムを用意する必要があります。

于 2012-08-10T09:20:30.897 に答える