Quando escrevemos aplicações de ambiente de trabalho em Java, queremos que tenham um aspecto e uma sensação nativos. Uma boa aplicação mistura-se. Ela proporciona uma experiência já familiar para o usuário.

Swing GUI: metal vs nativo

Aspecto e sensação da GUI do Swing: Metal vs. Nativo

No ambiente de trabalho, o percurso do usuário não começa na própria aplicação. Ela começa com um instalador. É aqui que o mundo Java costumava ficar para trás. Mas não mais.

A partir do Java 16, o JDK vem com jpackage. Esta ferramenta empacota uma aplicação num pacote com um JRE incorporado e envolve-o num instalador e executável nativos.

Neste artigo, demonstrarei como usar o jpackage com um aplicativo JxBrowser. Eu vou criar uma aplicação JxBrowser simples, envolvê-la num instalador nativo e num programa executável. Vou fornecer-lhe trechos de código que pode copiar diretamente para o seu projeto.

Configurando o Gradle 

O meu objetivo aqui é criar um simples Pomodoro tracker, instalá-lo e iniciá-lo como uma aplicação nativa do SO. Ele utilizará o Gradle para a configuração de construção e o Swing para a interface do usuário.

Vamos começar do zero, criando um projeto Gradle vazio:

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

Abra build.gradle.kts e aplique o plug-in Java:

plugins {
    java
}

group = "com.teamdev.examples"
version = "1.0"

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

repositories {
    mavenCentral()
}

A nossa aplicação requer duas dependências do JxBrowser. Uma contém a API principal com os binários do Chromium. E outra implementa o suporte do kit de ferramentas Swing.

Vamos usar o JxBrowser Gradle Plug-in para adicionar as dependências necessárias.

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

jxbrowser {
    version = "8.2.1"
}

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

No trecho de código acima, utilizo jxbrowser.currentPlatform que detecta a plataforma atual e escolhe apenas os binários Chromium necessários. Se você estiver construindo, digamos, uma aplicação apenas para Windows, você pode especificar os binários Chromium explicitamente para Windows:

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

Código de aplicação 

A nossa aplicação é muito simples. Ela abre um JFrame ``, adiciona o componente BrowserView que carrega e exibe uma página web com o 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;

/**
 * Um Pomodoro tracker.
 *
 * Esta aplicação apresenta uma janela com a componente de integração do browser que carrega e apresenta
 * a aplicação web Pomodoro Tracker.
 */
public final class PomodoroTracker {

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

    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("Loading...", CENTER));
        splash.setBounds(500, 150, 300, 200);
        splash.setVisible(true);
        return splash;
    }
}
Obtenha um controle poderoso para navegador Java
Comece agora

Empacotar a aplicação 

As aplicações de ambiente de trabalho escritas em Java precisam transportar todas as bibliotecas necessárias que requerem em runtime. A abordagem habitual é juntar as bibliotecas com uma aplicação num grande JAR Uber. Ela requer um pouco mais de configuração e não é adequado para projetos modulares.

Agora existe uma opção mais simples: juntar um monte de arquivos JAR numa pasta e deixar que o jpackage trate do resto.

Primeiro, vamos configurar a construção do JAR da aplicação principal:

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

Em seguida, vamos criar uma tarefa para reunir os arquivos JAR das dependências:

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

Estas duas tarefas são suficientes para lançar a aplicação a partir da linha de comandos:

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

Agora tudo está pronto para configurar o jpackage.

Esta é uma ferramenta de linha de comando. No entanto, eu prefiro manter tudo nos scripts do Gradle. São mais fáceis de ler e manter do que um monte de arquivos .sh e .bat.

Recomendo o plug-in org.panteleyev.jpackage. Este plug-in envolve a API de linha de comando do jpackage no Gradle DSL. Eis como aplicá-lo:

plugins {
    ...
    id("org.panteleyev.jpackageplugin") versão "1.3.1"
}

E aqui está como eu o configurei para gerar os instaladores:

val jarDirectory = file("$buildDir/jars")
tasks {
    
    jpackage {
        // Reúne todos os arquivos JAR antes de os empacotar.
        dependsOn("jar", "gatherDependencies")

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

        // A pasta com JARs.
        input = jarDirectory.absolutePath

        // O nome do JAR principal e lançável.
        mainJar = "main.jar"

        // A lista de módulos necessários para incluir em um JRE empacotado. 
        // O "java.logging" é exigido pelo JxBrowser.
        addModules = listOf("java.base", "java.desktop", "java.logging")

        // O caminho para os módulos JRE.
        modulePaths = listOf("${System.getProperty("java.home")}/jmods")

        // A pasta onde colocar os instaladores.
        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
        }
    }
}

Uma vez que tudo esteja configurado, a única coisa que resta a fazer é chamar a tarefa jpackage. Quando a tarefa estiver concluída, encontraremos os instaladores no directório build/dist:

$ ./gradlew jpackage

Para o Windows, vai precisar de https://wixtoolset.org/releases/

Instalador em acção 


Código fonte 

Pode encontrar o código-fonte da aplicação no repositório do GitHub.