当我将 H2 数据库用作嵌入式时,Gluon 应用程序无法在 ios 上加载 FXML

Gluon Application can't load FXML on ios when I using H2 database as embedded

我已经创建了使用 H2 数据库作为嵌入式模式的 Gluon 移动应用程序。它在 Window、Mac、Android 上运行良好。但不幸的是它不能在 iOS 模拟器上工作(fxml 无法加载)。

如果我注释加载驱动程序的代码,UI 将正确加载。

代码加载驱动程序

//load the driver class
        try {
            Class.forName(DBCONFIG.DB_DRIVER).newInstance();
        } catch (ClassNotFoundException e) {
            System.out.println("class not found");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            System.out.println("IllegalAccessException");
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.out.println("InstantiationException");
            e.printStackTrace();
        }

我也查看了 this group 上的讨论,但没有运气。

我的代码:

private void createDB() {
        status.getItems().add("Creating a Database with H2");
        //Get the private storage directory from each platform to save database files;
        private_dir = GluonServices.getPrivateDirectory();
        System.out.println("path="+private_dir.toString());
        //load the driver class
        try {
            Class.forName(DBCONFIG.DB_DRIVER).newInstance();
        } catch (ClassNotFoundException e) {
            System.out.println("class not found");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            System.out.println("IllegalAccessException");
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.out.println("InstantiationException");
            e.printStackTrace();
        }
        //Build Database connection string to the correct file that we create depending on each platform;
        dbUrl = String.format(dbUrl,private_dir.toString(),DBCONFIG.DB_NAME);
        System.out.println("dbUrl="+dbUrl);

        List<Contact> list = new ArrayList<>();
        try {
            //Connect to database from the giving db url: If file doesn't exist H2 will automatically create it.
            System.out.println("getConnection=");
            connection = DriverManager.getConnection(dbUrl);
            status.getItems().add("Connection established: " + dbUrl);

            //create the statement object
            stmt = connection.createStatement();
            stmt.setQueryTimeout(30);
            String sql="DROP TABLE IF EXISTS contact;\n" +
                    "CREATE TABLE contact(\n" +
                    "        Id INT NOT NULL AUTO_INCREMENT,\n" +
                    "        Name VARCHAR(255) NOT NULL,\n" +
                    "        Email VARCHAR(255) NOT NULL UNIQUE,\n" +
                    "        PRIMARY KEY (ID)\n" +
                    ");";

            //execute query
            stmt.executeUpdate(sql);
            status.getItems().add("Starting Adding testing data and read it from database");
            stmt.executeUpdate("INSERT INTO CONTACT(NAME,EMAIL) VALUES('Sovandara LENG','lengsovandara@gmail.com')");
            stmt.executeUpdate("INSERT INTO CONTACT(NAME,EMAIL) VALUES('Chamroeun PANG','Chamroeun.p@gmail.com')");
            stmt.executeUpdate("INSERT INTO CONTACT(NAME,EMAIL) VALUES('Sovanarith LENG','lengsovanarith@gmail.com')");

            status.getItems().add("Retrieving records from table 'Contact'...");
            rs = stmt.executeQuery("SELECT * FROM CONTACT");
            while (rs.next()) {
                int id = rs.getInt("Id");
                String name = rs.getString("NAME");
                String email = rs.getString("Email");
                status.getItems().add(String.format("Record Added:{id: %s, name: %s, email: %s}",id,name,email));
                list.add(new Contact(id,name, email));
            }
            status.getItems().add("End creating table and retrieving records");
            if(list.size()>0)
                status.getItems().add("Record has beed added");

            System.out.println("Table Created");
            status.getItems().add("Table Created");

        } catch (Exception e) {
            status.getItems().add("SQL error  " + e.getMessage());
            e.printStackTrace();
        }finally {
            try {
                if (rs != null) {
                    rs.close();
                }
            } catch (SQLException ex) {
                status.getItems().add("SQL error " + ex.getSQLState());
            }
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException ex) {
                status.getItems().add("SQL error " + ex.getSQLState());
            }
        }
    }

Build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.javafxports:jfxmobile-plugin:1.3.12'
        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.0'
    }
}

apply plugin: 'org.javafxports.jfxmobile'
apply plugin: 'com.github.johnrengelman.shadow'

repositories {
    jcenter()
    maven {
        url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
    }
}

mainClassName = 'com.khmerdev.GluonApplication'

dependencies {
    compile 'com.gluonhq:charm:5.0.0'
    compile files('libs/h2-1.4.196.jar')
}
// Add desktop dependencies to the task
shadowJar {
    configurations = [project.configurations.desktopRuntime]
}

jfxmobile {
    downConfig {
        version = '3.8.0'
        // Do not edit the line below. Use Gluon Mobile Settings in your project context menu instead
        plugins 'display', 'lifecycle', 'statusbar', 'storage'
    }
    android {
        manifest = 'src/android/AndroidManifest.xml'
    }
    ios {
        infoPList = file('src/ios/Default-Info.plist')
        forceLinkClasses = [
                'com.khmerdev.**.*',
                'com.gluonhq.**.*',
                'javax.annotations.**.*',
                'javax.inject.**.*',
                'javax.json.**.*',
                'org.glassfish.json.**.*'
        ]
    }
}

日志

3:43:02 PM: Executing task 'launchIPadSimulator'...

The CompileOptions.bootClasspath property has been deprecated and is scheduled to be removed in Gradle 5.0. Please use the CompileOptions.bootstrapClasspath property instead.
Note: /Users/sovandara/Documents/KhmerDEV/Project/cashmag/source_code/contactapp/ContactApp/src/main/java/com/khmerdev/sample/H2app.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/sovandara/Documents/KhmerDEV/Project/cashmag/source_code/contactapp/ContactApp/src/main/java/com/khmerdev/sample/H2app.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
:ContactApp:compileJava
:ContactApp:processResources UP-TO-DATE
:ContactApp:classes
:ContactApp:createDefaultIOSLauncher UP-TO-DATE
:ContactApp:compileIosJava UP-TO-DATE
:ContactApp:processIosResources NO-SOURCE
:ContactApp:iosClasses UP-TO-DATE
:ContactApp:iosExtractNativeLibs UP-TO-DATE
:ContactApp:launchIPadSimulator
Root pattern javax.annotations.**.* matches no classes
Root pattern javax.inject.**.* matches no classes
javax.xml.stream.XMLEventFactory=com.sun.xml.stream.events.ZephyrEvent...
java.vendor.url=http://www.robovm.org/
java.ext.dirs=
line.separator=

file.encoding=UTF-8
java.runtime.version=0.9
user.name=mobile
java.compiler=
prism.static.libraries=true
android.icu.unicode.version=6.2
javax.xml.stream.XMLOutputFactory=com.sun.xml.stream.ZephyrWriterFactory
java.version=0
android.icu.library.version=51.1.0.1
os.arch=x86_64
java.io.tmpdir=/Users/sovandara/Library/Developer/Co...
glass.platform=ios
android.zlib.version=1.2.8
user.language=en
java.vm.version=2.3.1-ios11
com.sun.javafx.isEmbedded=true
javax.xml.stream.XMLInputFactory=com.sun.xml.stream.ZephyrParserFactory
path.separator=:
java.runtime.name=RoboVM Runtime
java.specification.version=0.9
user.dir=/Users/sovandara/Library/Developer/Co...
java.vm.specification.vendor=RoboVM
java.vm.name=RoboVM
java.vm.specification.version=0.9
prism.useNativeIIO=false
user.home=/Users/sovandara/Library/Developer/Co...
jfxmedia.platforms=IOSPlatform
java.specification.name=RoboVM Core Library
file.separator=/
user.variant=
os.version=10.13.5
java.boot.class.path=/Users/sovandara/Library/Developer/Co...
java.vm.specification.name=RoboVM Virtual Machine Specification
javafx.platform=ios
user.region=US
os.name=iOS Simulator
java.class.path=/Users/sovandara/Library/Developer/Co...
prism.verbose=true
java.specification.vendor=RoboVM
java.vm.vendor=RoboVM
prism.mintexturesize=16
prism.allowhidpi=true
java.vendor=RoboVM
android.icu.cldr.version=23.1
android.openssl.version=OpenSSL 1.0.1e 11 Feb 2013
java.home=/Users/sovandara/Library/Developer/Co...
java.vm.vendor.url=http://www.robovm.org/
java.class.version=50.0
Prism pipeline init order: es2 
Using native-based Pisces rasterizer
Using dirty region optimizations
Using system sized mask for primitives
Not forcing power of 2 sizes for textures
Using hardware CLAMP_TO_ZERO mode
Opting in for HiDPI pixel scaling
Prism pipeline name = com.sun.prism.es2.ES2Pipeline
Loading ES2 native library ... prism_es2
    succeeded.
GLFactory using com.sun.prism.es2.IOSGLFactory
IOSWindowSystemInterface : share 0 view 0 pf otready 
GL_VERSION string = OpenGL ES 2.0 APPLE-16.4.2
GL_VERSION (major.minor) = 0.0
  initialize() returns 140384845988192
(X) Got class = class com.sun.prism.es2.ES2Pipeline
Initialized prism pipeline: com.sun.prism.es2.ES2Pipeline
IOSWindowSystemInterface : share 271e0 view 0 pf otready 
GL_VERSION string = OpenGL ES 2.0 APPLE-16.4.2
GL_VERSION (major.minor) = 0.0
CTXINFO vendor
CTXINFO renderer
CTXINFO glExtensions
CTXINFO GL_ARB_pixel_buffer_object
CTXINFO allocate the structure
CTXINFO set function pointers
Attributes = onScreen: trueredSize : 8, greenSize : 8, blueSize : 8, alphaSize : 8, depthSize : 24, doubleBuffer : true
  initialize() returns 140384843884416
IOSWindowSystemInterface : share 271e0 view 0 pf otready 
GL_VERSION string = OpenGL ES 2.0 APPLE-16.4.2
GL_VERSION (major.minor) = 0.0
CTXINFO vendor
CTXINFO renderer
CTXINFO glExtensions
CTXINFO GL_ARB_pixel_buffer_object
CTXINFO allocate the structure
CTXINFO set function pointers
Attributes = onScreen: trueredSize : 8, greenSize : 8, blueSize : 8, alphaSize : 8, depthSize : 24, doubleBuffer : true
  initialize() returns 140384847044992
Maximum supported texture size: 4096
Non power of two texture support = false
Maximum number of vertex attributes = 16
Maximum number of uniform vertex components = 512
Maximum number of uniform fragment components = 256
Maximum number of varying components = 32
Maximum number of texture units usable in a vertex shader = 8
Maximum number of texture units usable in a fragment shader = 8
Graphics Vendor: Apple Inc.
       Renderer: Apple Software Renderer
        Version: OpenGL ES 2.0 APPLE-16.4.2
 vsync: true vpipe: true
[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.font.t2k.T2KFactory'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.font.t2k.T2KFactory</pattern></forceLinkClasses> to your robovm.xml file to link it in.
2018-07-12 15:44:22.410 ContactApp[12971:469616] Done creating private storage /Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Data/Application/A69488DB-53AA-4833-9BA8-E07EDDF44C46/Library/gluon
[WARN] java.lang.Class: Class.forName() failed to load 'javax.xml.stream.FactoryFinder$ClassLoaderFinderConcrete'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>javax.xml.stream.FactoryFinder$ClassLoaderFinderConcrete</pattern></forceLinkClasses> to your robovm.xml file to link it in.
Jul 12, 2018 3:44:22 PM java.util.logging.LoggingProxyImpl log
WARNING: Loading FXML document with JavaFX API of version 10.0.1 by JavaFX runtime of version 8.0.72-ea
path=/Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Data/Application/A69488DB-53AA-4833-9BA8-E07EDDF44C46/Library/gluon
dbUrl=jdbc:h2:/Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Data/Application/A69488DB-53AA-4833-9BA8-E07EDDF44C46/Library/gluon/CONTACT;FILE_LOCK=FS;PAGE_SIZE=1024;CACHE_SIZE=8192
getConnection=
[WARN] java.lang.Class: Class.forName() failed to load 'org.h2.upgrade.v1_1.Driver'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>org.h2.upgrade.v1_1.Driver</pattern></forceLinkClasses> to your robovm.xml file to link it in.
IOException: javafx.fxml.LoadException: 
file:/Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Bundle/Application/AA0E7D96-ED31-4898-B6D8-05E7DD037568/ContactApp.app/lib/classes28.jar!/com/khmerdev/views/contact.fxml

[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.tk.quantum.QuantumMessagesBundle_en_US'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.tk.quantum.QuantumMessagesBundle_en_US</pattern></forceLinkClasses> to your robovm.xml file to link it in.
[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.tk.quantum.QuantumMessagesBundle_en'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.tk.quantum.QuantumMessagesBundle_en</pattern></forceLinkClasses> to your robovm.xml file to link it in.
[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.tk.quantum.QuantumMessagesBundle'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.tk.quantum.QuantumMessagesBundle</pattern></forceLinkClasses> to your robovm.xml file to link it in.
[WARN] java.lang.Class: Class.forName() failed to load 'com.oracle.jrockit.jfr.FlightRecorder'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.oracle.jrockit.jfr.FlightRecorder</pattern></forceLinkClasses> to your robovm.xml file to link it in.
setSwapInterval(1)
max rectangle texture cell size = 89
wrap rectangle texture = 2 x 2
ES2ResourceFactory: Prism - createStockShader: AlphaTexture_Color.frag
PPSRenderer: scenario.effect - createShader: LinearConvolveShadow_20
ES2ResourceFactory: Prism - createStockShader: Solid_TextureRGB.frag
Growing pool ES2 Vram Pool target to 73,400,324
Growing pool ES2 Vram Pool target to 88,080,388
ES2ResourceFactory: Prism - createStockShader: Texture_Color.frag
Growing pool ES2 Vram Pool target to 97,533,956
PPSRenderer: scenario.effect - createShader: LinearConvolveShadow_64
Growing pool ES2 Vram Pool target to 105,922,564
setSwapInterval(0)
setSwapInterval(1)
setSwapInterval(0)
setSwapInterval(1)
Growing pool ES2 Vram Pool target to 117,211,140
PPSRenderer: scenario.effect - createShader: LinearConvolve_16

请查看以下截图

Window

Tablet

Mac

iPad

当你在 iOS 上 运行 H2 时,问题与 forceLinkClasses 无关(无论如何它可能是必需的),但与 [=15= 的缺失实现有关] 包,正如您将在日志中看到的那样:

java.lang.NoClassDefFoundError: java/lang/management/ManagementFactory

这意味着 Java 包的 RoboVM 实现没有添加 java.lang.management/ javax.management 类.

由于 H2 是开源的,您可以删除 JMX 引用并使用您的自定义构建,或者我们可以尝试绕过 iOS 上的问题,提供缺失 类 的虚拟实现.

我只测试了 GluonSQLite 示例(但使用 H2 而不是 SQLite),添加了这两个 类:

java.lang.management.ManagementFactory

package java.lang.management;

import java.util.ArrayList;
import java.util.List;
import javax.management.MBeanServer;
import javax.management.ThreadMXBean;
import javax.management.GarbageCollectorMXBean;

public class ManagementFactory {

    public static synchronized MBeanServer getPlatformMBeanServer() {
        return new MBeanServer();
    }

    public static ThreadMXBean getThreadMXBean() {
        return new ThreadMXBean();
    }

    public static List<GarbageCollectorMXBean> getGarbageCollectorMXBeans() {
        return new ArrayList<>();
    }

    public static OperatingSystemMXBean getOperatingSystemMXBean() {
        return new OperatingSystemMXBean();
    }
}

java.lang.managementOperatingSystemMXBean

package java.lang.management;

public class OperatingSystemMXBean {

    public String getName() {
        return "";
    }

    public String getArch() {
        return "";
    }

    public String getVersion() {
        return "";
    }

    public int getAvailableProcessors() {
        return 0;
    }

    public double getSystemLoadAverage() {
        return 0;
    }
}

这三个空类:

package javax.management;
public class GarbageCollectorMXBean { }

package javax.management;
public class MBeanServer { }

package javax.management;
public class ThreadMXBean { }

并包括:'org.h2.**.*''javax.management.**.*' 以在 build.gradle 文件上强制使用 LinkClasses,示例在 iOS 上成功运行(H2 连接、数据库创建、插入、select, ...).