如何在Android上运行JavaFX

17

我尝试在Android上运行一个简单的JavaFX应用程序。因此,我阅读了javafxports入门指南,使用gradle构建应用程序,但我卡在了某个地方。我能够使用Eclipse中gradle的installDebug任务在Android设备上构建和安装应用程序,但是当我启动应用程序时,屏幕会变黑。当我提取.apk文件时,它不包含JavaFX应用程序的jar文件。我认为apk应该包含JavaFX应用程序jar文件,但我不知道如何将其包含到.apk中。

我还尝试使用gradle构建JavaFX应用程序本身(jar文件),这很好用。然而,我不知道在哪里放置这个jar文件,以便可以将其包含到apk文件中。我读到我必须将其放置在一个dist目录中,但我认为这只适用于Netbeans对吗?我正在使用Eclipse gradle集成构建项目。

这是我尝试过的内容。由于JavaFX应用程序与HelloWorld示例应用程序一样简单,并且能够正常工作,我认为我的gradle构建文件存在配置错误。

build.gradle-用于构建apk

apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'android'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:2.5.0'
        classpath 'com.android.tools.build:gradle:1.0.1'
    }
}
repositories {
    jcenter()
}
ext {
    dalvikSdkHome = getProjectProperty('dalvikSDK')
    dalvikSdkLib = dalvikSdkHome + '/rt/lib'
}

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.1"
    dexOptions {
        preDexLibraries = false
    }
    sourceSets {
        main {
            jniLibs.srcDir file("${dalvikSdkLib}/")
            assets.srcDirs = ['assets']
        }
    }
    lintOptions {
        abortOnError false
    }
}

dependencies {
    compile files ("${dalvikSdkLib}/ext/jfxrt.jar",
                   "${dalvikSdkLib}/ext/jfxdvk.jar",
                   "${dalvikSdkLib}/ext/compat-1.0.0.jar")
}

project.tasks.withType(com.android.build.gradle.tasks.Dex) {
    additionalParameters=['--core-library']
}

String getProjectProperty(String propertyName) {
    project.hasProperty(propertyName) ? project.property(propertyName) : null
}

build.gradle - 用于构建jar包

// Declares binary plugin and its required JavaFX classpath
apply from: "http://dl.bintray.com/content/shemnon/javafx-gradle/0.4.0/javafx.plugin"

// Configures plugin
javafx {
    // Points to JDK and its JavaFX libraries, also declares target runtime JDK
    javaRuntime = getProjectProperty('javaJDKPath')

    // Application name and ID presented by target OS
    appID 'HelloWorldApp'
    appName 'Hello World Application'

    // Main class of application
    mainClass 'helloworld.HelloWorld'

    // JVM arguments, system properties, application command line arguments
    jvmArgs = ['-XX:+AggressiveOpts', '-XX:CompileThreshold=1']
    systemProperties = ['prism.disableRegionCaching':'true']
    arguments = ['-l', '--fast']

    // Keystore credentials for signing JAR
    // Generate key: keytool -genkey -alias release -keyalg RSA -keystore keystore.jks -keysize 2048
    releaseKey {
        alias = 'release'
        keyStore = file(getProjectProperty('keystoreJKSFile')) // keyStore = file("${System.properties['user.home']}/keystore/keystore.jks")
        keyPass = getProjectProperty('keyStorePassword')
        storePass = getProjectProperty('storePassword')
    }

    signingMode 'release'
    // ...
}

String getProjectProperty(String propertyName) {
    project.hasProperty(propertyName) ? project.property(propertyName) : null
}

gradle.properties

javaJDKPath=D:/Java/jdk1.8.0_20
dalvikSDK=D:/Java/dalvik-sdk-8u20b3/dalvik-sdk
keystoreJKSFile=D:/Java/jre1.8.0_20/bin/keystore.jks
keyStorePassword=password
storePassword=password

本地配置文件.properties

sdk.dir=D:/programme/Android/adt-bundle-windows-x86_64-20130917/sdk

这是我的项目结构

HelloWorld
-- src\main
    -- java\helloworld\HelloWorld.java
    -- res\
    -- AndroidManifest.xml
-- assets\
    -- javafx.platform.properties
    -- javafx.properties
-- build.gradle
-- gradle.properties
-- local.properties

我需要使用一个dist目录吗?我应该把JavaFX应用程序的jar文件放在哪里,以便它被包含在apk文件中?


1
也许这个链接可以帮助你入门:http://javafxports.org/page/home,还有这篇文章:http://www.infoq.com/articles/Building-JavaFX-Android-Apps。 - edharned
嘿,edharned,感谢您的回复。我之前已经访问了这两个网站。据我记得,第二个网站使用了旧的方法通过ant(+gradle)构建应用程序。javafxports入门指南显示可以仅使用gradle创建应用程序/apk。那就是我想要的(很抱歉没有指出)。我昨天已经解决了这个问题,并将在本周五发布源代码。 - Michael
好的,我开始用JavaFX替换Swing。下一个项目将是移植到Android项目,期待您的更新。 - edharned
@edharned,我已经发布了我的源代码,请查看下面的答案。希望它能帮助你和其他人开始在Android上使用JavaFX。 - Michael
谢谢。没有人说这会很容易。 - edharned
注意:我已编辑gradle配置(build.gradle)以使用最新版本的android插件和me.tatarka.retrolambda插件,因为它在几天前更新了。当刷新gradle依赖项时,我收到了依赖项错误。通过此更新,问题已解决。错误是:“https://jcenter.bintray.com/com/android/tools/build/gradle/maven-metadata.xml Required by: MyProjectName:unspecified > me.tatarka:gradle-retrolambda:2.4.1” - Michael
2个回答

12

现在我回答自己的问题。 我的gradle配置设置,如我发布的问题中所示,已经在运行。在我的问题中,我并没有向你展示我正在使用一个.fxml文件来设置布局。因此,我没有使它运行的原因是.fxml布局文件没有被放置在正确的位置,以便与apk文件一起打包(LogCat显示错误Location Not Found,我的设备上出现了黑屏)。

首先,这里有一个适用于HelloWorld.java的可工作示例(请参见我的问题中的结构和gradle设置等):

package helloworld;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloWorld extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

使用FXML布局在Android上运行JavaFX

如果您想要使用.fxml文件,则需要稍微改变项目的结构。所有.fxml文件、.css文件、图形等都应该放在resources\assets目录或其子目录中。这将确保 .fxml 等文件被打包进 apk。

HelloWorld
-- src\main
    -- java\helloworld\
        -- HelloWorld.java
        -- MyController.java
    -- resources\assets\
        -- sample_layout.fxml
    -- AndroidManifest.xml
-- assets\
    -- javafx.platform.properties
    -- javafx.properties
-- build.gradle
-- gradle.properties
-- local.properties

我没有检查包含dalvikVM sdk中的javafx.platform.propertiesjavafx.properties文件的assets文件夹是否仍然需要。如果我检查.apk文件的内容,apk文件将两个文件都包含了两次。似乎dalvikVM库会自动复制这些文件。

注意:如果您需要检查apk文件的内容,请提取apk文件,然后提取其中的classes.dex(请参见此帖子以获取更多详细信息

以下是使用.fxml文件的示例:

HelloWorld.java

package helloworld;

import java.io.IOException;
import java.net.URL;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;

public class HelloWorld extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        try {
            URL fxmlFile = getClass().getResource("/assets/sample_layout.fxml");
            FXMLLoader loader = new FXMLLoader(fxmlFile);
            AnchorPane page = (AnchorPane) loader.load();
            MyController controller = (MyController) loader.getController();
            Scene scene = new Scene(page);
            primaryStage.setScene(scene);
            primaryStage.setTitle("JavaFX Sample");
            primaryStage.show();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

}

MyController.java

package helloworld;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class MyController {

    @FXML
    private ResourceBundle resources;
    @FXML
    private URL location;
    @FXML
    private Label label_Counter;
    @FXML
    private Button button_IncrementCounter;
    @FXML
    private Button button_DecrementCounter;

    private static final String OUTPUT_PREFIX = "Counter: ";
    private static int counter = 0;

    @FXML
    void onIncrementButtonPressed(ActionEvent event) {
        label_Counter.setText(OUTPUT_PREFIX + ++counter);
    }

    @FXML
    void onDecrementButtonPressed(ActionEvent event) {
        label_Counter.setText(OUTPUT_PREFIX + --counter);
    }

    @FXML
    void initialize() {
        assert label_Counter != null : "fx:id=\"label_Counter\" was not injected: check your FXML file 'sample_layout.fxml'.";
        assert button_IncrementCounter != null : "fx:id=\"button_IncrementCounter\" was not injected: check your FXML file 'sample_layout.fxml'.";
        assert button_DecrementCounter != null : "fx:id=\"button_DecrementCounter\" was not injected: check your FXML file 'sample_layout.fxml'.";
        label_Counter.setText(OUTPUT_PREFIX + 0);
    }

}

样例布局.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="helloworld.MyController">
   <children>
      <VBox layoutX="332.0" layoutY="71.0" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
         <children>
            <Label text="Please click on the buttons to increment or decrement the counter:" />
            <Button fx:id="button_IncrementCounter" mnemonicParsing="false" onAction="#onIncrementButtonPressed" text="Increment Counter">
               <VBox.margin>
                  <Insets top="10.0" />
               </VBox.margin>
               <padding>
                  <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
               </padding>
            </Button>
            <Button fx:id="button_DecrementCounter" mnemonicParsing="false" onAction="#onDecrementButtonPressed" text="Decrement Counter">
               <VBox.margin>
                  <Insets top="10.0" />
               </VBox.margin>
               <padding>
                  <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
               </padding>
            </Button>
            <Label fx:id="label_Counter" text="&lt;output-placeholder&gt;">
               <VBox.margin>
                  <Insets top="20.0" />
               </VBox.margin>
            </Label>
         </children>
         <padding>
            <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
         </padding>
      </VBox>
   </children>
</AnchorPane>

希望这能帮助其他想要在Android上开始使用JavaFX的人。


3
如何在Android上运行JavaFX:
  1. 下载 dalvik sdk
  2. 进入 samples\HelloWorld\javafx 目录 - 这是一个gradle项目。
  3. 修改 local.properties 文件中 dalvik-sdk 和 android-sdk 的位置。
    例如,在我的Windows系统中:
    sdk.dir=C\:\\dev\\android-sdk javafx.dir=C\:\\dev\\dalvik-sdk
  4. 运行 gradlew installDebug 构建并安装apk到设备上。您还可以在 build 文件夹中找到输出文件。
  5. 在设备上启动应用程序。(第一次启动前我看到了黑屏超过10秒钟)
  6. 在Eclipse或Idea中作为标准gradle项目打开该项目。

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