目的
开发打包一个exe软件,用于展示web项目内容。[仅供学习使用]使用 javaFX | BorderPane 布局
1.top部分 自定义窗口头部 ( icon, 标题,返回按钮,清理按钮,缩小按钮,放大按钮,关闭按钮)
2.center部分 嵌入chrome内核浏览器(jxbrowser),用于展示项目内容
开发环境
- 32位是为了打出32位的程序和安装包,使用32位是为了让32位和64位系统都可使用。
- jre1.8是javafx的运行时环境,为了能够让程序在其他没有安装jdk的电脑上安装运行,需要把32位的jre环境打包进程序中.
- 使用jxbrowser不是用自带webview的原因在于 webview卡顿严重且渲染页面会造成样式错乱.
- jdk1.8
- exe4j 5.0.1 (32位)
- inno setup 5.6.1 (32位)
- jre1.8的运行文件(32位)
jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar (需要破解才能使用【仅供学习】, 破解步骤如下)
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
代码中(静态代码块, 必须比其他代码先运行)
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(); } }
开发思路
- 利用javaFX开发外部窗口,然后嵌入chrome浏览器
- 将开发出的程序打成jar包
- 利用exe4j将jar包打成exe启动程序
- 在用inno setup将exe程序封装成一个程序安装包
代码
- 如果没有项目地址,可用browser.loadHTML("测试页面"), 加载dom节点进行页面渲染;
- 如果有项目地址,可用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的安装包,别人安装了也无法使用
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。