JavaFX程序何时调用start方法?

3

我正在学习JavaFX。在主方法中,我只能看到一个launch(args)方法。当我调试进入launch方法时,我看不到任何调用start()方法的语句。那么JavaFX程序什么时候调用start方法呢?以下是launch(args)源代码。

    public static void launch(String... args) {
    // Figure out the right class to call
    StackTraceElement[] cause = Thread.currentThread().getStackTrace();

    boolean foundThisMethod = false;
    String callingClassName = null;
    for (StackTraceElement se : cause) {
        // Skip entries until we get to the entry for this class
        String className = se.getClassName();
        String methodName = se.getMethodName();
        if (foundThisMethod) {
            callingClassName = className;
            break;
        } else if (Application.class.getName().equals(className)
                && "launch".equals(methodName)) {

            foundThisMethod = true;
        }
    }

    if (callingClassName == null) {
        throw new RuntimeException("Error: unable to determine Application class");
    }

    try {
        Class theClass = Class.forName(callingClassName, true,
                           Thread.currentThread().getContextClassLoader());
        if (Application.class.isAssignableFrom(theClass)) {
            Class<? extends Application> appClass = theClass;
            LauncherImpl.launchApplication(appClass, args);
        } else {
            throw new RuntimeException("Error: " + theClass
                    + " is not a subclass of javafx.application.Application");
        }
    } catch (RuntimeException ex) {
        throw ex;
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

我对JavaFX只有一点点了解,但我猜测launch方法会将事件放到事件队列中,以便调用start方法,并确保start方法在其GUI线程的上下文中被调用,无论JavaFX如何称呼它。 - MadProgrammer
你有没有看过 LauncherImpl.launchApplication() 函数中的 try/catch 块? - awksp
是的,我尝试过,但它停在了launchLatch.await();。 - hidemyname
@deathlee 这意味着还有另一个线程在某个地方执行这项工作... 不知道那是哪个线程。 - awksp
5个回答

7
LauncherImpl通过PlatformImpl.runAndWait将实际调用放置到JavaFX事件队列中来调用Application#start。在启动Preloader之后执行此操作。 Application#launch调用LauncherImpl.launchApplication,它创建一个Thread并调用launchApplication1launchApplication等待此Thread终止,通过CountDownLatch实现。
然后,该Thread调用LauncherImpl.launchApplication1,如果指定了,则启动Preloader,然后根据有关Preloader状态的多个决策调用Application#start,包装在runAndWait调用中,以确保start在JavaFX的GUI /事件队列线程的上下文中被调用......
这是基于Java 8的 更新...LauncherImpl.launcherApplication1调用的theApp.start是您的Application的实例。 Application通过遍历StackTrace查找类名。
StackTraceElement[] cause = Thread.currentThread().getStackTrace();

boolean foundThisMethod = false;
String callingClassName = null;
for (StackTraceElement se : cause) {
    // Skip entries until we get to the entry for this class
    String className = se.getClassName();
    String methodName = se.getMethodName();
    if (foundThisMethod) {
        callingClassName = className;
        break;
    } else if (Application.class.getName().equals(className)
            && "launch".equals(methodName)) {

        foundThisMethod = true;
    }
}

这个操作获取你的类名,然后使用 Class.forName 创建一个 Class 实例,并将其传递给 LauncherImpl...

launcherApplication1 然后构造了该类的新实例,并将其分配给引用变量 theApp,它是你的 Application 的一个实例。

PlatformImpl.runAndWait(new Runnable() {
        @Override public void run() {
            try {
                Constructor<? extends Application> c = appClass.getConstructor();
                app.set(c.newInstance());
                // Set startup parameters
                ParametersImpl.registerParameters(app.get(), new ParametersImpl(args));
                PlatformImpl.setApplicationName(appClass);
            } catch (Throwable t) {
                System.err.println("Exception in Application constructor");
                constructorError = t;
                error = true;
            }
        }
    });
}
final Application theApp = app.get();

接下来它调用theApp.start,这将调用您的Applicationstart方法... 看起来很奇怪,但就是这样


4
JavaFX程序不需要main()方法,也不需要调用Application.launch()方法。您可以从应用程序中删除main()方法,然后java应用程序启动器将直接在应用程序上调用init()(在启动线程上)和start()(在JavaFX应用程序线程上)方法。这样做时,完全跳过了在其他线程中讨论的整个LauncherImpl进程(以及它通过StackTrace查找要启动的类的奇怪决定)。请参见相关的Java Enhancement Proposal JEP 153:“增强Java命令行启动器以启动JavaFX应用程序。”及其附带的问题跟踪链接JDK-8001533“java launcher must launch javafx applications”。当然,如果您除了start()方法之外还有一个main()方法,则代码当前将沿着MadProgrammer在答案中概述的路径执行。编写JavaFX程序时,最好假设main()从不被调用(尽管实际上,如果您不作为applet部署,则很可能会调用main(),以防止人们完全困惑)。这个过程也在Application javadoc中描述:Java启动器在JavaFX应用程序线程上加载和初始化指定的Application类。如果Application类中没有主方法,或者主方法调用Application.launch(),则在JavaFX应用程序线程上构造一个Application的实例。

2
在发布时,LauncherImpl的源代码在这里,第837行调用了start(...)方法。在源代码中深入到那么深层次的位置看起来相当令人沮丧...

是的,这很严峻,我的老师告诉我不要在意它。但我只是好奇... - hidemyname
1
你的老师是对的。有一个点,你要假设他们知道自己在做什么,并且已经正确地实现了它... :) - James_D
它也出现在755行,但这取决于Preloader的使用和状态... - MadProgrammer
是的,我看到了。在绝大多数情况下,没有预加载程序(这是一个好主意,假设我们会弄清楚整个Web部署的事情并使其工作...),因此除了少数情况外,所有代码都被跳过了。 - James_D

1
我将为您翻译以下内容:

补充MadProgrammer的答案:

Application.class,第241行:

public static void launch(String... args) {
    //...
        if (Application.class.isAssignableFrom(theClass)) {
            Class<? extends Application> appClass = theClass;
            LauncherImpl.launchApplication(appClass, args); // <-- This is where the application is launched

    //...
}

这里指的是LauncherImpl.class的第118行:

public static void launchApplication(final Class<? extends Application> appClass,
        final String[] args) {
    launchApplication(appClass, savedPreloaderClass, args);
}

这句话的意思是:“哪个放在这里(第158行):””。
public static void launchApplication(final Class<? extends Application> appClass,
        final Class<? extends Preloader> preloaderClass,
        final String[] args) {
    //.......
    Thread launcherThread = new Thread(new Runnable() {
        @Override public void run() {
            try {
                launchApplication1(appClass, preloaderClass, args); // <-- go here
            } catch (RuntimeException rte) {
    //....
}

这里涉及到第674行的一个非常长的方法:

private static void launchApplication1(final Class<? extends Application> appClass,
        final Class<? extends Preloader> preloaderClass,
        final String[] args) throws Exception {
    //... Lots of stuff 
    // Eventually, on line 755:
    currentPreloader.start(primaryStage);

    // More things for cases where the previous start() call isn't appropriate
    // Line 773:
    final AtomicReference<Application> app = new AtomicReference<>();
    // Line 790/791:
    Constructor<? extends Application> c = appClass.getConstructor(); // Gets constructor for your class
    app.set(c.newInstance()); // Creates a new instance of your class
    // Line 803:
    final Application theApp = app.get();
    // Line 837:
    theApp.start(primaryStage);
}

因为你的JavaFX项目扩展了Application,所以appClass应该是你的JavaFX项目的类对象,因此你定义的start()方法将被theApp.start()调用。
跳过了很多事情,因为源代码太长了,无法在这里发布,但这就是基本的调用链。

我刚刚看到了这个语句,并发现语句"theApp.start(primaryStage);"调用的是Application中的start()方法。但我仍然不知道程序何时会调用我自己类中的start()方法。很抱歉这么晚才问,因为我刚刚才发现它。 - hidemyname
@deathlee 哦,我明白了。我会再深入挖掘一下。 - awksp
1
@deathlee launchApplication1,构造一个新的Application类实例并将其分配给theApp,因此,theApp.start是JavaFX调用您的应用程序的start方法的时候... - MadProgrammer
@deathlee 我更新了回答,加入了更多细节,但MadProgrammer已经基本涵盖了它。你的类扩展了Application,所以你定义的start()方法将被调用。 - awksp

0

我也在学习JavaFx,我的印象是它只是通过类初始化,因为它是在主参数之上的一个方法。我目前正在使用一个简单的类,只包含一个主要参数和start方法,没有其他命令,而且似乎在编译时始终执行start方法,但它应该被调用。

launch(args);

这是我主要论点的全部内容,也是 start 方法如何对我执行的方式


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接