关于javafx:javafx开发exe程序内嵌web项目

29次阅读

共计 13601 个字符,预计需要花费 35 分钟才能阅读完成。

目标

开发打包一个 exe 软件,用于展现 web 我的项目内容。[仅供学习应用]

应用 javaFX | BorderPane 布局

1.top 局部 自定义窗口头部 (icon, 题目,返回按钮,清理按钮,放大按钮,放大按钮,敞开按钮)

2.center 局部 嵌入 chrome 内核浏览器(jxbrowser), 用于展现我的项目内容

开发环境

  1. 32 位是为了打出 32 位的程序和安装包,应用 32 位是为了让 32 位和 64 位零碎都可应用。
  2. jre1.8 是 javafx 的运行时环境,为了可能让程序在其余没有装置 jdk 的电脑上装置运行,须要把 32 位的 jre 环境打包进程序中.
  3. 应用 jxbrowser 不是用自带 webview 的起因在于 webview 卡顿重大且渲染页面会造成款式错乱.
  1. jdk1.8
  2. exe4j 5.0.1(32 位)
  3. inno setup 5.6.1(32 位)
  4. jre1.8 的运行文件(32 位)
  5. jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar(须要破解能力应用【仅供学习】,破解步骤如下)

    1. teamdev.licenses(放在打 jar 包生成的 META-INF 中)

      Product: JxBrowser
      Version: 6.x
      Licensed to: Kagura.me
      License type: Enterprise
      License info: JxBrowser License
      Expiration date: 01-01-9999
      Support expiration date: NO SUPPORT
      Generation date: 01-01-1970
      Platforms: win32/x86;win32/x64;mac/x86;mac/x64;linux/x86;linux/x64
      Company name: TeamDev Ltd.
      SigB: 1
      SigA: 1
    2. 代码中(动态代码块,必须比其余代码先运行)

      static {
       try {Field e = bb.class.getDeclaredField("e");
       e.setAccessible(true);
       Field f = bb.class.getDeclaredField("f");
       f.setAccessible(true);
       Field modifersField = Field.class.getDeclaredField("modifiers");
       modifersField.setAccessible(true);
       modifersField.setInt(e, e.getModifiers() & ~Modifier.FINAL);
       modifersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
       e.set(null, new BigInteger("1"));
       f.set(null, new BigInteger("1"));
       modifersField.setAccessible(false);
       } catch (Exception e1) {e1.printStackTrace();
       }
      }

开发思路

  1. 利用 javaFX 开发内部窗口,而后嵌入 chrome 浏览器
  2. 将开发出的程序打成 jar 包
  3. 利用 exe4j 将 jar 包打成 exe 启动程序
  4. 在用 inno setup 将 exe 程序封装成一个程序安装包

代码

  1. 如果没有我的项目地址,可用 browser.loadHTML(“ 测试页面 ”), 加载 dom 节点进行页面渲染;
  2. 如果有我的项目地址,可用 browser.loadURL(testUrl); 间接获取我的项目页面

    或者可用第三方网站地址 browser.loadURL(“https://www.baidu.com”) 获取内容查看

其余细节看代码正文

package com.yemin.inspect;

import com.teamdev.jxbrowser.chromium.*;
import com.teamdev.jxbrowser.chromium.javafx.BrowserView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Modality;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.UUID;


public class Main extends Application {// 破解代码, 用于破解 jxbrowser 包 ( 仅供学习应用)
    static {
        try {Field e = bb.class.getDeclaredField("e");
            e.setAccessible(true);
            Field f = bb.class.getDeclaredField("f");
            f.setAccessible(true);
            Field modifersField = Field.class.getDeclaredField("modifiers");
            modifersField.setAccessible(true);
            modifersField.setInt(e, e.getModifiers() & ~Modifier.FINAL);
            modifersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
            e.set(null, new BigInteger("1"));
            f.set(null, new BigInteger("1"));
            modifersField.setAccessible(false);
        } catch (Exception e1) {e1.printStackTrace();
        }
    }

    private final boolean production = false;// 是否生产
    private final String url = "<h1>hello world</h1>";// 生产地址
    private HashMap<String, String> testEnvironmentsUrls;// 测试地址

    private double x = 0.00;
    private double y = 0.00;
    private double width = 0.00;
    private double height = 0.00;
    private boolean isMax = false;
    private boolean isRight;// 是否处于右边界调整窗口状态
    private boolean isBottomRight;// 是否处于右下角调整窗口状态
    private boolean isBottom;// 是否处于下边界调整窗口状态
    private double RESIZE_WIDTH = 5.00;
    private double MIN_WIDTH = 400.00;
    private double MIN_HEIGHT = 300.00;
    private double xOffset = 0, yOffset = 0;// 自定义 dialog 挪动横纵坐标

     /**
     * testEnvironmentsUrls 是 production 变量为 false(测试环境下),点击头部 icon 可
     * 弹出窗口进行抉择拜访环境的地址
     * @throws Exception
     */
    @Override
    public void init() throws Exception {super.init();
        testEnvironmentsUrls = new HashMap<String, String>();
        testEnvironmentsUrls.put("测试 1", "<h1> 测试 1 </h1>");
        testEnvironmentsUrls.put("测试 2", "<h1> 测试 2 </h1>");
    }
    
     /**
     * 1. Stage 是程序窗口 ---》舞台
     * 2. Scene 是程序页面 ----》场景(可舞台固定只切换场景)* 3. 其余的按钮之类的货色是 放在 scene 上,而后 scene 在放入 stage
     * 布局指的是在 scene 内布局 (常见布局请查阅相干材料)
     * 4 . 依据须要对窗口,页面,元素增加对应的监听代码
     * @param primaryStage
     * @throws Exception
     */
    @Override
    public void start(Stage primaryStage) throws Exception {System.out.println("以后拜访页面:" + url);
        primaryStage.initStyle(StageStyle.TRANSPARENT);
        BorderPane root = new BorderPane();
        //------- 设置头部 bar--------
        GridPane gpTitle = new GridPane();
        gpTitle.setAlignment(Pos.CENTER_LEFT);
        gpTitle.setPadding(new Insets(8));
        String title = "javaFX 开发打包测试";
        Label lbTitle = new Label(title);
        lbTitle.setTextFill(Color.web("#ccc"));
        lbTitle.setFont(new Font("Arial", 14));

        ImageView imageView = new ImageView("/img/icon.png");
        imageView.setFitWidth(20);
        imageView.setFitHeight(20);
        lbTitle.setGraphic(imageView);
        Button btnMin = new Button();
        btnMin.setId("minButton");
        btnMin.setPrefSize(20, 20);

        Button btnMax = new Button();
        btnMax.setId("maxButton");
        btnMax.setPrefSize(20, 20);

        Button btnClose = new Button();
        btnClose.setId("closeButton");
        btnClose.setPrefSize(20, 20);

        Button btnBack = new Button();
        btnBack.setId("backButton");
        btnBack.setPrefSize(20, 20);

        Button btnClean = new Button();
        btnClean.setId("btnClean");
        btnClean.setPrefSize(18, 18);

        gpTitle.add(lbTitle, 0, 0);
        gpTitle.add(btnBack, 1, 0);
        gpTitle.add(btnClean, 2, 0);
        gpTitle.add(btnMin, 3, 0);
        gpTitle.add(btnMax, 4, 0);
        gpTitle.add(btnClose, 5, 0);

        gpTitle.setStyle("-fx-background-color: black;");
        gpTitle.setPrefHeight(20);
        gpTitle.setMaxHeight(20);
        GridPane.setHgrow(lbTitle, Priority.ALWAYS);
        GridPane.setMargin(btnBack, new Insets(0, 6, 0, 0));
        GridPane.setMargin(btnClean, new Insets(0, 6, 0, 0));
        GridPane.setMargin(btnMin, new Insets(0, 6, 0, 0));
        GridPane.setMargin(btnMax, new Insets(0, 6, 0, 0));
        GridPane.setMargin(btnClose, new Insets(0, 6, 0, 0));

        root.setTop(gpTitle);

        //------- 设置内容局部 --------
        BrowserContext context = new BrowserContext(new BrowserContextParams("/tmp/" + UUID.randomUUID().toString()));
        Browser browser = new Browser(BrowserType.LIGHTWEIGHT, context);
        BrowserView browserView = new BrowserView(browser);
        if (production) {browser.loadHTML(url);
        } else {String testUrl = testEnvironmentsUrls.get("测试 1");
            browser.loadHTML(testUrl);
        }
        root.setCenter(browserView);
        root.getCenter().setStyle("-fx-background-color: white;visibility: visible");
        //----------- 按钮事件监听 --------------
        btnMin.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {primaryStage.setIconified(true);
            }
        });
        btnMax.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {Rectangle2D rectangle2d = Screen.getPrimary().getVisualBounds();
                isMax = !isMax;
                if (isMax) {
                    // 最大化
                    primaryStage.setX(rectangle2d.getMinX());
                    primaryStage.setY(rectangle2d.getMinY());
                    primaryStage.setWidth(rectangle2d.getWidth());
                    primaryStage.setHeight(rectangle2d.getHeight());
                } else {
                    // 缩放回原来的大小
                    primaryStage.setX(x);
                    primaryStage.setY(y);
                    primaryStage.setWidth(width);
                    primaryStage.setHeight(height);
                }
            }
        });
        btnClose.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {clearData(browser);
                browser.stop();
                primaryStage.close();
                Platform.exit();
                System.exit(0);
            }
        });
        btnBack.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {String nowWebViewUrl = browser.getURL();
                if (nowWebViewUrl.contains("menunav.jsp")) {browser.executeJavaScript("parent.document.getElementById('main-iframe').contentWindow.history.go(-1);");
                }
            }
        });
        btnClean.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {System.out.println("革除缓存,跳转页面:" + browser.getURL());
                redirectUrl(browser, browser.getURL());
            }
        });
        // 窗口大小地位事件监听
        primaryStage.xProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {if (newValue != null && !isMax) {x = newValue.doubleValue();
                }
            }
        });
        primaryStage.yProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {if (newValue != null && !isMax) {y = newValue.doubleValue();
                }
            }
        });
        primaryStage.widthProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {if (newValue != null && !isMax) {width = newValue.doubleValue();
                }
            }
        });
        primaryStage.heightProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {if (newValue != null && !isMax) {height = newValue.doubleValue();
                }
            }
        });
        // 鼠标挪动事件监听
        root.setOnMouseMoved((MouseEvent event) -> {event.consume();
            double x = event.getSceneX();
            double y = event.getSceneY();
            double width = primaryStage.getWidth();
            double height = primaryStage.getHeight();
            // 鼠标光标初始为默认类型,若未进入调整窗口状态,放弃默认类型
            Cursor cursorType = Cursor.DEFAULT;
            // 先将所有调整窗口状态重置
            isRight = isBottomRight = isBottom = false;
            if (y >= height - RESIZE_WIDTH) {if (x <= RESIZE_WIDTH) {
                    // 左下角调整窗口状态
                    // 不解决
                } else if (x >= width - RESIZE_WIDTH) {
                    // 右下角调整窗口状态
                    isBottomRight = true;
                    cursorType = Cursor.SE_RESIZE;
                } else {
                    // 下边界调整窗口状态
                    isBottom = true;
                    cursorType = Cursor.S_RESIZE;
                }
            } else if (x >= width - RESIZE_WIDTH) {// 右边界调整窗口状态
                isRight = true;
                cursorType = Cursor.E_RESIZE;
            }
            // 最初扭转鼠标光标
            root.setCursor(cursorType);
        });
        // 鼠标拖拽事件
        root.setOnMouseDragged((MouseEvent event) -> {
            // 依据鼠标的横纵坐标挪动 dialog 地位
            event.consume();
            if (yOffset != 0) {primaryStage.setX(event.getScreenX() - xOffset);
                if (event.getScreenY() - yOffset < 0) {primaryStage.setY(0);
                } else {primaryStage.setY(event.getScreenY() - yOffset);
                }
            }
            double x = event.getSceneX();
            double y = event.getSceneY();
            // 保留窗口扭转后的 x、y 坐标和宽度、高度,用于预判是否会小于最小宽度、最小高度
            double nextX = primaryStage.getX();
            double nextY = primaryStage.getY();
            double nextWidth = primaryStage.getWidth();
            double nextHeight = primaryStage.getHeight();
            // 所有左边调整窗口状态
            if (isRight || isBottomRight) {nextWidth = x;}
            // 所有下边调整窗口状态
            if (isBottomRight || isBottom) {nextHeight = y;}
            // 如果窗口扭转后的宽度小于最小宽度,则宽度调整到最小宽度
            if (nextWidth <= MIN_WIDTH) {nextWidth = MIN_WIDTH;}
            // 如果窗口扭转后的高度小于最小高度,则高度调整到最小高度
            if (nextHeight <= MIN_HEIGHT) {nextHeight = MIN_HEIGHT;}
            // 最初对立扭转窗口的 x、y 坐标和宽度、高度,能够避免刷新频繁呈现的屏闪状况
            primaryStage.setX(nextX);
            primaryStage.setY(nextY);
            primaryStage.setWidth(nextWidth);
            primaryStage.setHeight(nextHeight);
        });
        // 鼠标点击获取横纵坐标
        root.setOnMousePressed(event -> {event.consume();
            xOffset = event.getSceneX();
            if (event.getSceneY() > 46) {yOffset = 0;} else {yOffset = event.getSceneY();
            }
        });
        // 非生产可抉择环境
        if (!production) {
            lbTitle.setOnMouseClicked(event -> {chooseEnvironment(browser);
            });
        }
        //------------ 启动构建 ---------------
        Scene scene = new Scene(root);
        scene.setUserAgentStylesheet("/css/mainStage.css");
        primaryStage.setScene(scene);
        primaryStage.setTitle(title);
        primaryStage.getIcons().add(new Image("/img/icon.png"));
        primaryStage.show();
        btnMax.fire();// 触发最大化按钮}

    public void chooseEnvironment(Browser browser) {Stage window = new Stage();
        window.setTitle("抉择环境");
        window.initModality(Modality.APPLICATION_MODAL);
        window.setHeight(150);
        window.setWidth(300);
        Label label = new Label("请抉择环境");
        VBox layout = new VBox(10);
        layout.getChildren().add(label);
        for (String key : testEnvironmentsUrls.keySet()) {Button button = new Button(key);
            button.setOnAction(e -> {redirectUrl(browser, testEnvironmentsUrls.get(key));
                window.close();});
            layout.getChildren().add(button);
        }
        layout.setAlignment(Pos.CENTER);
        Scene scene = new Scene(layout);
        window.setScene(scene);
        window.showAndWait();}

    private void redirectUrl(Browser browser, String url) {System.out.println("切换地址:" + url);
        clearData(browser);
        browser.loadHTML(url);
    }

    private void clearData(Browser browser){browser.getCacheStorage().clearCache();
        browser.getCookieStorage().deleteAll();
        browser.getLocalWebStorage().clear();
        browser.getSessionWebStorage().clear();
    }

    public static void main(String[] args) {launch(args);
    }

}

我的项目编译成 JAR 包

1. 引入第三方包 jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar(将第三方包的 export 勾选起来), 这样能够把第三方的包最初全都打包打主 jar 包中

2.artifacts 构建 jar 包

3.build –> build artifacts –> rebuild

JAR 包 转 EXE(6,7 两步是重点)

exe4j 注册码:A-XVK258563F-1p4lv7mg7sav

1.change license 弄一个能够用的注册码

2. 咱们是 jar 包转 exe,抉择第二个

3. 配置 exe 的名字和输入到哪个文件夹

4.Icon File 能够配置利用图标

5. 看你要打几位的 exe 文件本人按须要抉择

6. 这一步是重点,你要把之前的打出的 jar 包放进来,而后抉择 main class

7. 配置程序的运行时 jre 环境的地位. 这边我只配置为相邻的 jre 文件夹,所以编译输入的 exe 文件要与 jre 文件相邻,能力启动运行。如果须要拿到别的电脑运行,要把 jre 和 exe 都复制过来

8. 其余的就程序一个个走上来,根本都是默认的就能够了

EXE 打成安装包(第 4 步是重点)

1. 启动 inno setup,新建一个文件

2. 安装包名称版本之类的信息,本人依据要求输出

3. 按默认即可

4. 这步是重点!!把之前编译的 exe 文件放到主执行文件中,而后 jre 的文件夹放到,上面的其余应用程序文件文件中

5. 默认或依据需要抉择

6. 输入文件夹,文件名称,安装包图标

7. 其余默认

8. 最初会生成一个编译脚本,有编译按钮(构建安装包)和启动按钮(安装程序)


; 脚本由 Inno Setup 脚本向导 生成!; 无关创立 Inno Setup 脚本文件的详细资料请查阅帮忙文档!​
#define MyAppName "这个是 app 名称"
#define MyAppVersion "app 版本"
#define MyAppPublisher "app 发布者"
#define MyAppURL "app url 地址"
#define MyAppExeName "app 执行名称.exe"
​
[Setup]
; 注: AppId 的值为独自标识该应用程序。; 不要为其余安装程序应用雷同的 AppId 值。; (生成新的 GUID,点击 工具 | 在 IDE 中生成 GUID。)
AppId={{0B52F1C0-A71F-48DE-8C2E-940B29412328}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}{#MyAppName}
DisableProgramGroupPage=yes
OutputDir=C:UsersAdministratorDesktopxxSystem 安装包名称 setup
OutputBaseFilename= 安装包名称 setup
SetupIconFile=C:UsersAdministratorDesktopxxSystemprojecticon.ico
Compression=lzma
SolidCompression=yes
​
[Languages]
Name: "chinesesimp"; MessagesFile: "compiler:Default.isl"
​
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone; OnlyBelowVersion:0,6.3
​
[Files]
Source: "C:UsersAdministratorDesktopxxSystemprojectoutputapp 启动程序名称.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:UsersAdministratorDesktopxxSystemprojectjre*"; DestDir: "{app}jre"; Flags: ignoreversion recursesubdirs createallsubdirs
; 留神: 不要在任何共享系统文件上应用“Flags: ignoreversion”​
[Icons]
Name: "{commonprograms}{#MyAppName}"; Filename: "{app}{#MyAppExeName}"
Name: "{commondesktop}{#MyAppName}"; Filename: "{app}{#MyAppExeName}"; Tasks: desktopicon
​
[Run]
Filename: "{app}{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName,'&','&&')}}"; Flags: nowait postinstall skipifsilent

总结

1. 开发中的重点在于布局和嵌入浏览器

2. 前面编译的重点在于 jre 环境程序,肯定要打进去,不然不带 jre 的安装包,他人装置了也无奈应用

正文完
 0