開發打包一個exe軟件,用於展現web項目內容。[僅供學習使用]使用 javaFX | BorderPane 佈局css
1.top部分 自定義窗口頭部 ( icon, 標題,返回按鈕,清理按鈕,縮小按鈕,放大按鈕,關閉按鈕)java
2.center部分 嵌入chrome內核瀏覽器(jxbrowser),用於展現項目內容linux
- 32位是爲了打出32位的程序和安裝包,使用32位是爲了讓32位和64位系統均可使用。
- jre1.8是javafx的運行時環境,爲了可以讓程序在其餘沒有安裝jdk的電腦上安裝運行,須要把32位的jre環境打包進程序中.
- 使用jxbrowser不是用自帶webview的緣由在於 webview卡頓嚴重且渲染頁面會形成樣式錯亂.
jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar (須要破解才能使用【僅供學習】, 破解步驟以下)web
teamdev.licenses (放在打jar包生成的 META-INF 中)chrome
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(); } }
- 若是沒有項目地址,可用browser.loadHTML("測試頁面"), 加載dom節點進行頁面渲染;
- 若是有項目地址,可用browser.loadURL(testUrl); 直接獲取項目頁面
或者可用第三方網站地址 browser.loadURL(「https://www.baidu.com」)獲取內容查看緩存
其餘細節看代碼註釋app
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); } }
1.引入第三方包 jxbrowser-6.22.1.jar 和 jxbrowser-win32-6.22.1.jar (將第三方包的export勾選起來), 這樣能夠把第三方的包最後全都打包打主jar包中
dom
2.artifacts構建jar包
jsp
3.build --> build artifacts --> rebuild
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.其餘的就順序一個個走下去,基本都是默認的就能夠了
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的安裝包,別人安裝了也沒法使用