在 Java 中编写桌面应用程序时,我们总是希望其外观和感觉能够尽量贴近原生应用程序。因为一个优秀的应用程序应该要能融入其中,为用户提供已经熟悉的体验。

Swing GUI: metal vs native

Swing GUI 的外观和感觉: Metal vs. Native

在桌面上,用户的使用旅程并不是从应用程序本身开始的,而是从安装程序开始的。这是 Java 世界过去落后的地方。但现在已不再如此。

从 Java 16 开始,JDK 附带了 jpackage 工具。该工具可以将应用程序打包成带有内置 JRE 的捆绑包,并将其包装成原生安装程序和可执行文件。

在本篇文章中,我将演示如何将 jpackage 与 JxBrowser 应用程序结合使用。我将创建一个简单的 JxBrowser 应用程序,然后使用 jpackage 将其打包为原生安装程序和可执行程序。我会提供一些代码片段,您可以将其直接复制到您的项目中。

配置 Gradle

我的目标是创建一个简单的 Pomodoro tracker(番茄工作法跟踪器),安装它,并将其作为原生操作系统应用程序启动。它将使用 Gradle 进行构建配置,使用 Swing 进行用户界面设计。

让我们从头开始创建一个空的 Gradle 项目:

$ gradle init --dsl kotlin --type basic --project-name jxbrowser-installer

打开 build.gradle.kts 文件并应用 Java 插件:

plugins {
    java
}
 
group = "com.teamdev.examples"
version = "1.0"

java {
    sourceCompatibility = JavaVersion.VERSION_16
    targetCompatibility = JavaVersion.VERSION_16
}

repositories {
    mavenCentral()
}

我们的应用程序需要两个 JxBrowser 依赖项。其中一个包含带有 Chromium 二进制文件的核心 API,另一个实现 Swing 工具包支持。

让我们使用 JxBrowser Gradle 插件来添加必要的依赖项。

plugins {
    
    id("com.teamdev.jxbrowser") version "1.0.1"
}

jxbrowser {
    version = "7.38.2"
}

dependencies {
    implementation(jxbrowser.swing)
    implementation(jxbrowser.currentPlatform)
}

在上面的代码片段中,我使用了 jxbrowser.currentPlatform 来检测当前平台并仅挑选必要的 Chromium 二进制文件。如果您正在构建一个仅适用于 Windows 的应用程序,您可以明确指定 Windows 平台的 Chromium 二进制文件:

dependencies {
    implementation(jxbrowser.swing)
    implementation(jxbrowser.win64)
}

应用程序代码

我们的应用程序非常简单。它会启动一个 JFrame 窗口,然后在该窗口中添加 BrowserView 组件,用于加载和显示包含 Pomodoro Tracker 的网页。

package com.teamdev.examples;

import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.view.swing.BrowserView;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED;
import static javax.swing.SwingConstants.CENTER;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;

/**
 * 一个 Pomodoro tracker.
 *
 * 这款应用程序会显示一个带有集成浏览器组件的窗口,
 * 该组件负责加载并显示 Pomodoro Tracker 的网页应用。
 */
public final class PomodoroTracker {

    public static final String URL = "https://teamdev.com/jxbrowser/docs/tutorials/jpackage/pomodoro.html";

    public static void main(String[] args) {
        var splash = showSplashScreen();
        showBrowser();
        splash.dispose();
    }

    private static void showBrowser() {
        var engine = Engine.newInstance(HARDWARE_ACCELERATED);
        var browser = engine.newBrowser();
        var frame = new JFrame("Pomodoro Tracker");
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                engine.close();
            }
        });
        var view = BrowserView.newInstance(browser);
        frame.add(view, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        frame.setSize(1280, 900);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        browser.navigation().loadUrl(URL);
    }

    private static JWindow showSplashScreen() {
        var splash = new JWindow();
        splash.getContentPane().add(new JLabel("加载中...", CENTER));
        splash.setBounds(500, 150, 300, 200);
        splash.setVisible(true);
        return splash;
    }
}
想要获得强大且可靠的 Java 浏览器控件。
立即体验

打包应用程序

用 Java 编写的桌面应用程序需要在运行时携带它们所需的所有必要库。通常的做法是将库与应用程序合并为一个大的 Uber JAR。这需要一些额外的配置,并且不适合模块化项目。

现在有一个更简单的选择:将一堆 JAR 文件收集到一个文件夹中,然后让 jpackage 处理其余部分。

首先,让我们来配置构建主应用程序的 JAR 文件:

val jarDirectory = file("$buildDir/jars")
tasks {
    jar {
        manifest {
            attributes["Main-Class"] = "com.teamdev.examples.PomodoroTracker"
        }
        archiveFileName.set("main.jar")
        destinationDirectory.set(jarDirectory)
    }
}

接着,让我们创建一个任务来收集依赖项的 JAR 文件:

val jarDirectory = file("$buildDir/jars")
tasks {
    
    register<Copy>("gatherDependencies") {
        from(configurations.runtimeClasspath).into(jarDirectory)
    }
}

这两个任务足以从命令行启动应用程序:

$ ./gradlew jar gatherDependencies
$ java -cp "build/jars/*" com.teamdev.examples.PomodoroTracker

现在一切准备就绪,可以配置 jpackage 了。

jpackage 是一个命令行工具。但是,我更倾向于将所有内容都保留在 Gradle 脚本中。与一堆 .sh 和 .bat 文件相比,Gradle 脚本更容易阅读和维护。

我推荐使用 org.panteleyev.jpackage 插件。 此插件会将 jpackage 的命令行 API 封装到 Gradle DSL 中。应用方法如下:

plugins {
    ...
    id("org.panteleyev.jpackageplugin") version "1.3.1"
}

以下是我如何配置它来生成安装程序的方法:

val jarDirectory = file("$buildDir/jars")
tasks {
    
    jpackage {
        // 在打包之前收集所有 JAR 文件。
        dependsOn("jar", "gatherDependencies")

        appName = "Pomodoro Tracker"
        appVersion = "${project.version}"

        // 包含 JAR 文件的目录。
        input = jarDirectory.absolutePath

        // 可启动的主要 JAR 文件的名称。
        mainJar = "main.jar"

        // 需要包含到捆绑 JRE 中的必要模块列表。
        // "java.logging"  是 JxBrowser 所必需的。
        addModules = listOf("java.base", "java.desktop", "java.logging")

        // JRE 模块的路径。
        modulePaths = listOf("${System.getProperty("java.home")}/jmods")

        // 安装程序存放的目录。
        destination = "$buildDir/dist"

        linux {
            type = org.panteleyev.jpackage.ImageType.DEB
            linuxPackageName = "pomodoro"
        }

        windows {
            type = org.panteleyev.jpackage.ImageType.MSI
            winDirChooser = true
            winMenu = true
        }

        mac {
            type = org.panteleyev.jpackage.ImageType.DMG
        }
    }
}

完成所有配置后,接下来要做的就是调用 jpackage 任务。任务完成后,我们会在 build/dist 目录下找到安装程序:

$ ./gradlew jpackage

对于 Windows,您将需要 https://wixtoolset.org/releases/

安装程序实操视频


源代码

您可以在 GitHub 存储库中找到该应用程序的源代码。

相关链接