你好,欢迎访问我的博客!登录
当前位置:首页 - JavaFX - 正文 求知成瘾,却无作品!

JavaFX程序打包为EXE桌面应用程序

2016-07-24JavaFX攻城狮56780°c
A+ A-

我们在用JavaFX编写应用程序的时候一般情况下生成的都是可执行的jar文件,但是这种文件只有在电脑安装了JRE的情况下才可以正常运行起来,而且还依赖于特定版本的jre,所以直接将这种文件发给用户,基本上百分之八十的可能都是运行失败。

当然,为了避免这种问题,可运行的jar还可以通过JNLP协议分发,这种协议可以保证用户运行你打包好的jar文件时自动准备好合适的jre环境,基本上理论上来说成功率可达99%(没有绝对的事情,所以不是100%),但是我大概体验过几个,都是需要比较麻烦的java设置,会有各种提示,基本上需要一定计算机基础的用户才可以使用。

我这里要介绍的就是将可执行的jar文件打包为Windows系统中的exe应用程序,要达到的目的就是即便用户电脑里面没有jre,也照样可以运行你打包好的应用程序。

开发JavaFX的IDE很多,我这里不会一一例举,我本人写JavaFX程序的时候习惯用NetBeans,所以我下面的例子是以NetBeans8.1为例。

下面开始介绍打包过程:

一、创建可执行的jar文件

在NetBeans中选择“文件---新建项目”,弹出如下图所示的框,在类别中选择“样例-----JavaFX”,随便选一个项目:

QQ截图20160724023803.png

点击下一步后,填写项目名,至于项目位置,根据自己的情况选择,我这里默认不变:

QQ截图20160724024030.png

javafx最低要求是JDK8,这个需要注意,点击完成按钮就创建好了一个JavaFX项目,在新创建的项目上面点击右键,选择运行,可以看到运行效果如下图所示:

QQ截图20160724024236.png

好了,现在我们已经有一个可执行的jar文件了,但是这里为了真实一些,我需要重点说一下,在真实的项目中,除了程序代码本身,我们还会用到类似于配置文件、图片等外部资源,大部分项目都需要依赖于第三方的jar包,所以我准备修改一下代码,为项目引入一个commons-lang.jar包,并且为项目使用一个配置文件。

展开项目,在项目的“库”上面点击右键,选择“添加jar/文件夹”选项:

QQ截图20160724024907.png

然后在弹出的窗口中选择要导入的jar文件,导入后可以看到“库”中包含了commons-lang.jar,

QQ截图20160724025125.png

下面为项目添加一个配置文件,首先把左侧项目视图更改为文件视图,然后在项目名上点击右键,选择“新建---文件夹”,如下图所示:

QQ截图20160724025542.png

在弹出的框中为新建的文件夹命名,我这里命名为config,然后点击完成:

QQ截图20160724025742.png

在新建的文件夹上面点击右键,选择“新建---其他“选项

QQ截图20160724025956.png

在弹出的窗口中选择”其他---属性文件“

QQ截图20160724030120.png

然后点击下一步,在弹出的窗口中为新建的属性文件命名,我这里写为conf

QQ截图20160724030308.png

点击完成后,我们打开属性文件,在里面写点配置,让程序读取配置文件中信息

QQ截图20160724030555.png

好了,我在代码中添加了如下几行代码:

Map<String,String> map=new HashMap<>();
        primaryStage.setOnCloseRequest(e->{
            try {
                Properties pro=new Properties();
                pro.load(new FileReader("./config/conf.properties"));
                
                System.out.println("读取系统配置信息:");
                Enumeration<?> propertyNames = pro.propertyNames();
                while(propertyNames.hasMoreElements()){
                    Object nextElement = propertyNames.nextElement();
                    String get = (String)pro.get(nextElement);
                    if(StringUtils.isNotBlank(get)&&StringUtils.isNotBlank((String)nextElement));
                    map.put((String)nextElement, get);
                    
                }
                Alert alert=new Alert(AlertType.INFORMATION);
                alert.setHeaderText("读取的conf.properties配置文件的信息如下:");
                alert.setContentText(map.toString());
                alert.showAndWait();
            } catch (IOException ex) {
                Logger.getLogger(DigitalClock.class.getName()).log(Level.SEVERE, null, ex);
            }
        });

可以看到我在代码中读取了配置文件信息,并且在点击窗口右上角的关闭按钮时弹出读取到的配置信息,并且在读取的时候用引入的第三方jar包中的StringUtils类,所以说,如果后期我们打包的时候如果弄错了,那么运行的时候肯定会报错,如果打包完毕后可以正常运行,并且能够显示出读取到的配置信息,那么就代表打包成功了。下面是我当前的运行状态:

QQ截图20160724032250.png

下面是完整的代码;

package digitalclock;

import java.io.FileReader;
import java.io.IOException;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.effect.Effect;
import javafx.scene.effect.Glow;
import javafx.scene.effect.InnerShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Shear;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.apache.commons.lang.StringUtils;

/**
 * A digital clock application that demonstrates JavaFX animation, images, and
 * effects.
 *
 * @see javafx.scene.effect.Glow
 * @see javafx.scene.shape.Polygon
 * @see javafx.scene.transform.Shear
 * @resource DigitalClock-background.png
 */
public class DigitalClock extends Application {

    private Clock clock;
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Digital Clock");
        Group root = new Group();
        Scene scene = new Scene(root, 480, 412);
        // add background image
        ImageView background = new ImageView(new Image(getClass().getResourceAsStream("DigitalClock-background.png")));
        // add digital clock
        clock = new Clock(Color.ORANGERED, Color.rgb(50,50,50));
        clock.setLayoutX(45);
        clock.setLayoutY(186);
        clock.getTransforms().add(new Scale(0.83f, 0.83f, 0, 0));
        // add background and clock to sample
        root.getChildren().addAll(background, clock);
        primaryStage.setScene(scene);
        primaryStage.show();
         Map<String,String> map=new HashMap<>();
        primaryStage.setOnCloseRequest(e->{
            try {
                Properties pro=new Properties();
                pro.load(new FileReader("./config/conf.properties"));
                
                System.out.println("读取系统配置信息:");
                Enumeration<?> propertyNames = pro.propertyNames();
                while(propertyNames.hasMoreElements()){
                    Object nextElement = propertyNames.nextElement();
                    String get = (String)pro.get(nextElement);
                    if(StringUtils.isNotBlank(get)&&StringUtils.isNotBlank((String)nextElement));
                    map.put((String)nextElement, get);
                    
                }
                Alert alert=new Alert(AlertType.INFORMATION);
                alert.setHeaderText("读取的conf.properties配置文件的信息如下:");
                alert.setContentText(map.toString());
                alert.showAndWait();
            } catch (IOException ex) {
                Logger.getLogger(DigitalClock.class.getName()).log(Level.SEVERE, null, ex);
            }
        });
    }

    /**
     * Clock made of 6 of the Digit classes for hours, minutes and seconds.
     */
    public static class Clock extends Parent {
        private Calendar calendar = Calendar.getInstance();
        private Digit[] digits;
        private Timeline delayTimeline, secondTimeline;

        public Clock(Color onColor, Color offColor) {
            // create effect for on LEDs
            Glow onEffect = new Glow(1.7f);
            onEffect.setInput(new InnerShadow());
            // create effect for on dot LEDs
            Glow onDotEffect = new Glow(1.7f);
            onDotEffect.setInput(new InnerShadow(5,Color.BLACK));
            // create effect for off LEDs
            InnerShadow offEffect = new InnerShadow();
            // create digits
            digits = new Digit[7];
            for (int i = 0; i < 6; i++) {
                Digit digit = new Digit(onColor, offColor, onEffect, offEffect);
                digit.setLayoutX(i * 80 + ((i + 1) % 2) * 20);
                digits[i] = digit;
                getChildren().add(digit);
            }
            // create dots
            Group dots = new Group(
                    new Circle(80 + 54 + 20, 44, 6, onColor),
                    new Circle(80 + 54 + 17, 64, 6, onColor),
                    new Circle((80 * 3) + 54 + 20, 44, 6, onColor),
                    new Circle((80 * 3) + 54 + 17, 64, 6, onColor));
            dots.setEffect(onDotEffect);
            getChildren().add(dots);
            // update digits to current time and start timer to update every second
            refreshClocks();
            play();
        }

        private void refreshClocks() {
            calendar.setTimeInMillis(System.currentTimeMillis());
            int hours = calendar.get(Calendar.HOUR_OF_DAY);
            int minutes = calendar.get(Calendar.MINUTE);
            int seconds = calendar.get(Calendar.SECOND);
            digits[0].showNumber(hours / 10);
            digits[1].showNumber(hours % 10);
            digits[2].showNumber(minutes / 10);
            digits[3].showNumber(minutes % 10);
            digits[4].showNumber(seconds / 10);
            digits[5].showNumber(seconds % 10);
        }

        public void play() {
            // wait till start of next second then start a timeline to call refreshClocks() every second
            delayTimeline = new Timeline();
            delayTimeline.getKeyFrames().add(
                    new KeyFrame(new Duration(1000 - (System.currentTimeMillis() % 1000)), new EventHandler<ActionEvent>() {
                        @Override public void handle(ActionEvent event) {
                            if (secondTimeline != null) {
                                secondTimeline.stop();
                            }
                            secondTimeline = new Timeline();
                            secondTimeline.setCycleCount(Timeline.INDEFINITE);
                            secondTimeline.getKeyFrames().add(
                                    new KeyFrame(Duration.seconds(1), new EventHandler<ActionEvent>() {
                                        @Override public void handle(ActionEvent event) {
                                            refreshClocks();
                                        }
                                    }));
                            secondTimeline.play();
                        }
                    })
            );
            delayTimeline.play();
        }

        public void stop(){
            delayTimeline.stop();
            if (secondTimeline != null) {
                secondTimeline.stop();
            }
        }
    }

    /**
     * Simple 7 segment LED style digit. It supports the numbers 0 through 9.
     */
    public static final class Digit extends Parent {
        private static final boolean[][] DIGIT_COMBINATIONS = new boolean[][]{
                new boolean[]{true, false, true, true, true, true, true},
                new boolean[]{false, false, false, false, true, false, true},
                new boolean[]{true, true, true, false, true, true, false},
                new boolean[]{true, true, true, false, true, false, true},
                new boolean[]{false, true, false, true, true, false, true},
                new boolean[]{true, true, true, true, false, false, true},
                new boolean[]{true, true, true, true, false, true, true},
                new boolean[]{true, false, false, false, true, false, true},
                new boolean[]{true, true, true, true, true, true, true},
                new boolean[]{true, true, true, true, true, false, true}};
        private final Polygon[] polygons = new Polygon[]{
                new Polygon(2, 0, 52, 0, 42, 10, 12, 10),
                new Polygon(12, 49, 42, 49, 52, 54, 42, 59, 12f, 59f, 2f, 54f),
                new Polygon(12, 98, 42, 98, 52, 108, 2, 108),
                new Polygon(0, 2, 10, 12, 10, 47, 0, 52),
                new Polygon(44, 12, 54, 2, 54, 52, 44, 47),
                new Polygon(0, 56, 10, 61, 10, 96, 0, 106),
                new Polygon(44, 61, 54, 56, 54, 106, 44, 96)};
        private final Color onColor;
        private final Color offColor;
        private final Effect onEffect;
        private final Effect offEffect;

        public Digit(Color onColor, Color offColor, Effect onEffect, Effect offEffect) {
            this.onColor = onColor;
            this.offColor = offColor;
            this.onEffect = onEffect;
            this.offEffect = offEffect;
            getChildren().addAll(polygons);
            getTransforms().add(new Shear(-0.1,0));
            showNumber(0);
        }

        public void showNumber(Integer num) {
            if (num < 0 || num > 9) num = 0; // default to 0 for non-valid numbers
            for (int i = 0; i < 7; i++) {
                polygons[i].setFill(DIGIT_COMBINATIONS[num][i] ? onColor : offColor);
                polygons[i].setEffect(DIGIT_COMBINATIONS[num][i] ? onEffect : offEffect);
            }
        }
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX 
     * application. main() serves only as fallback in case the 
     * application can not be launched through deployment artifacts,
     * e.g., in IDEs with limited FX support. NetBeans ignores main().
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}


好了,到这里我们可执行的jar文件已经准备好了,在NetBeans的项目路径下会有一个dist文件夹,dist文件夹中就有我们刚刚运行的jar文件,我们新建的config文件夹与dist文件夹同级

QQ截图20160724032615.png

在dist文件夹中我们可以看到一个jar文件,此时你双击这个jar文件可以看到程序可以运行,但是点击关闭按钮的时候没有弹出配置文件信息,因为它现在已经脱离了集成环境,找不到配置文件了。

二、将jar文件打包为exe程序

这里我们需要一个工具:exe4j,版本需要在5.0以上,5.0以下的版本不支持jdk1.8

在D盘创建一个文件夹,命名为app,然后在app文件夹中再创建三个文件夹,分别是src1   src2   out

QQ截图20160724033340.png

将dist文件夹下的jar文件和lib文件夹拷贝到src1文件夹中

QQ截图20160724033502.png

将同dist文件夹平级的config文件夹拷贝到src2文件夹里面,并且找到你本机的jre8文件夹,拷贝到src2文件夹中

QQ截图20160724033727.png

将你程序的图标文件放到src1文件夹里面,我这里随便找个ico图标放进去,需要注意的是图标文件必须是正常的,通过更改文件后缀名得到的ico图标文件会报错。

好了,打开exe4j工具,希望在这之前你已经输入过注册码了,没有注册的exe4j打包的程序在运行前会弹框,

打开之后直接点击"next“按钮,然后选择”JAR in EXE" mode,然后点击"next“按钮,输入工程名,选择输出文件夹为src2

QQ截图20160724034257.png

点击"next"按钮,填写应用程序名称为myclock,勾选Icon File复选框,选择放在src1中的图标文件,如果没有图标文件可以不选:

QQ截图20160724034548.png

如果你刚刚复制的jre是64bit的,那么需要展开Advanced Options,选择32-bit or 64-bit选项后,在Generate 64-bit executable复选框上打钩,

QQ截图20160724034943.png

然后点击next,不用管,继续点击next,这里需要添加你的可执行jar文件,

QQ截图20160724035407.png

好了,然后点击main class后面的按钮,默认的程序一开始默认的运行类文件:

QQ截图20160724035556.png

这里,需要注意,因为程序依赖于第三方包,所以要把依赖的包文件也选进来,过程跟上面选择可执行的jar文件的过程一样,只不过这里是要选择src1里面lib中的jar文件,选择完毕后,整个窗口的配置如下图;

QQ截图20160724035941.png

点击next按钮,输入最小jre要求和最大jre要求,这里我都写1.8

QQ截图20160724040041.png

展开下面的Advanced Options,选择Search sqquence选项,将里面的所有条目都删掉(选中后点击右侧的红叉即可),

QQ截图20160724040227.png

删除完毕后,点击右侧的加号,在弹出的窗口中,将Entry type更改为Directory,然后选择为src2下的jre文件夹

QQ截图20160724040420.png

选择完毕后如下图所示:

QQ截图20160724040537.png

点击next,后面全部不用管,一直点next按钮,知道打包完毕后,可以在src2目录下看到刚刚打包好的myclock.exe文件了,双击后可以正常运行:


三、将所有文件打包成一个安装文件。

其实第二步结束后程序就已经可以在任意计算机正常运行了,但是,有一点比较蛋疼,就是你需要将整个src2文件夹发给别人才可以,单独发送myclock.exe文件却不行,所以为了达到只用一个exe文件就可以完美运行的目的,进行第三步操作。

这里我们需要使用一个工具:Inno Setup 5

打开后,选择“用脚本向导创建新的脚本文件,点击下一步,默认不管,再点击下一步,填写应用程序名、版本、公司、网站等信息

QQ截图20160724041403.png

点击下一步,不做改动,再点击下一步,将主程序选择为src2文件夹里面的myclock.exe文件,然后点击下面的“添加文件夹”,选中src2文件夹,提示你是否选中子文件夹,选择是

QQ截图20160724041531.png

点击下一步,根据需要自行勾选

QQ截图20160724041656.png

在点击下一步,如果需要许可文件,就准备个txt文件,将许可信息添加到txt中,在这里进行选择即可,没有的话,直接点击下一步,

QQ截图20160724041748.png

语言我这里选择简体中文,然后点击下一步

QQ截图20160724041944.png

自定义编译器输出文件夹选择刚刚新建的out文件夹,

编译器输出基本文件名称自行填写,

图标选择为src1文件夹中的图标,没有可以不选,

点击下一步,不管,继续下一步,点击完成。

QQ截图20160724042119.png

选择是

QQ截图20160724042142.png

选择否,

然后看到控制台开始编译了,

QQ截图20160724042219.png

直到在控制台看到编译完成的信息后,可以在out文件夹中看到编译好的exe安装程序,此文件就是一个可在其他Windows计算机上运行的桌面应用程序,不管对方有没有jre环境都会正常运行。

好了,到这里打包结束。


标签:

发表评论

必填

选填

选填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。


访客
1楼  访客 @Ta
时间:2016-11-29 16:46:58

超好的文章!成功解决了我这个菜鸟的问题。
一定要注意楼主说的[B]“打开exe4j工具,希望在这之前你已经输入过注册码了,没有注册的exe4j打包的程序在运行前会弹框”[/B] 再次感谢楼主~~~

  登录