목차
JavaFX란?
크로스 플랫폼에서 실행하는 Rich Client Application을 개발하기 위한 그래픽과 미디어 패키지다.
자바7부터 JDK에 포함되어 있음. 자바7 이후 버전을 사용한다면 (대부분 그렇겠지만) 별도 SDK 설치 없이 바로 사용 가능하다.
JavaFX 어플리케이션에서 UI 생성, 이벤트 처리, 멀티미디어 재생, 웹 뷰 등과 같은 기능은 JavaFX API로 개발하고, 그 이외 기능은 자바 표준 API를 활용해서 개발한다.
JavaFX는 화면 레이아웃 스타일과 어플리케이션 로직을 분리할 수 있다. 디자이너와 개발자들이 분업/협업할 수 있다는 것은 정말 큰 장점이다. 그래프 디자이너가 CSS로 작업할 동안 개발자들은 로직에 좀 더 집중할 수 있으니까.
(심지어 내 회사에서는 CSS파일을 별도 작성하지않는다. 상용화 툴도 아니고, UI가 좀 투박하면 어떠냐.)
자바 코드에서 레이아웃(디자인 등)은 FXML파일로 작성하고, 로직은 자바 소스코드로 작성하면 된다.
JavaFX 애플리케이션 개발의 시작.
늘 그렇듯이 메인 클래스를 작성하는 것 부터 해야한다.
이 메인 클래스는 javafx.application.Application이란 추상 클래스를 상속는데, 다음 두 가지를 수행한다..
- .start() 메소드를 재정의
- .launch() 메소드를 호출
※ JavaFX를 하다보면 Stage타입이 계속 등장하는데, 이것은 윈도우를 말한다. JavaFX는 윈도우를 무대, javafx.stage.Stage라고 표현한다.
import javafx.application.Application;
import javafx.stage.Stage;
public class AppMain extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.show(); //윈도우 보여주기
}
public static void main(String[] args) {
launch(args);
// 메인 클래스(AppMain) 객체를 생성하고
// 메인 윈도우(무대, Stage)를 생성한 다음
// start() 메소드를 호출한다.
}
}
-launch 메소드 (접은 글)-
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는 Application.launch()부터 시작해서 다음과 같이 진행된다.
- 메인클래스(AppMain) 기본 생성자 호출해서 객체 생성하고
- 메인클래스 실행 매개값으로 init() 메소드는 어플리케이션 초기화 셋업하고
- 이어서 start()메소드가 메인 윈도우를 실행.
- 이제 사용자가 어플리케이션을 사용할 수 있다.
JavaFX는 다음의 경우에 종료된다.
- 마우스로 마지막 윈도우 우측 상단 닫기 버튼을 눌렀을 때
- 자바 코드로 마지막 윈도우(Stage)의 close메소드를 호출했을 때
- 자바 코드로 Platform.exit()또는 System.exit(0)를 호출했을 때
JavaFX는 종료직전 stop()메소드를 호출하는데, 이 메소드는 자원 정리 (파일닫기, 네트워크끊기)를 할 기회를 준다.
init()과 stop()은 필요시 재정의해서 사용해도 된다.
주목할 점은 라이프사이클의 각 단계에서 호출되는 메소든는 서로 다른 스레드상에서 실행된다는 것이다.
JVM이 만든 main 스레드가 Application.launch()를 실행하면, launch() 메소드는 다음 2개 스레드를 생성하고 시작한다.
- JavaFX-Launcher : init()실행
- JavaFX Application Thread : 메인클래스 기본 생성자 start() 및 stop()실행.
그래서 init()메소드에서 UI를 생성하는 코드를 작성하면 안 된다. init() 메소드는 JavaFX-Launcher 스레드에서 실행되니까. UI를 생성/수정하는 코드는 JavaFx Application 스레드에서 맡아야 한다. 윈도우를 비롯한 UI 생성/수정/이벤트처리 등은 모두 여기에!
JavaFX API는 스레드에 안전하지 않아서 멀티 스레드가 동시에 UI를 생성 수정하면 문제가 발생하니 조심하자.
JavaFX Application Thread 외 다른 스레드가 UI에 접근하면 예외가 발생할 것이다.
import javafx.application.Application;
import javafx.stage.Stage;
public class AppMain extends Application {
public AppMain() {
System.out.println(Thread.currentThread().getName()+": AppMain() 호출");
}
@Override
public void init() throws Exception {
System.out.println(Thread.currentThread().getName()+": init() 호출");
}
@Override
public void start(Stage primaryStage) throws Exception {
System.out.println(Thread.currentThread().getName()+": start() 호출");
primaryStage.show();
}
@Override
public void stop() throws Exception {
System.out.println(Thread.currentThread().getName()+": stop() 호출");
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+": main() 호출");
launch(args);
}
}
결과:
main: main() 호출
JavaFX Application Thread: AppMain() 호출
JavaFX-Launcher: init() 호출
JavaFX Application Thread: start() 호출