后台线程从JavaScript转向JavaFX

时间:2017-01-09 01:49:28

标签: javascript multithreading javafx webview

将JRE版本1.8.0_66迁移到1.8.0_111后,我遇到了从JavaScript到JavaFX的调用问题。

长话短说:虽然有一个正在运行的后台线程,但WebView / WebEngine拒绝进行JS-to-Java调用。

我使用WebView呈现从域数据模型(DM)生成的HTML内容。内容包含分配了处理程序的元素,如下所示:    

 <a href='#' onclick='explainHeadWord(this)'>some_word</a>

JS部分看起来像:

function explainHeadWord(hwElement) {
    jsBridge.jsHandleQuery(hwElement.innerHTML);
}

function testBridge() {
    jsBridge.jsTest();
}

其中jsBridge是Controller的内部Java类

public class JSBridge {

    public void jsHandleQuery(String headWord) {
        log("jsBridge: jsHandleQuery: requested %s", headWord);
          handleQuery(headWord);
      }

      public void jsTest() {
        log("jsBridge: jsTest: test succeeded ");
      }
 }

注入如下:

engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
    if (newValue == Worker.State.SUCCEEDED) {
        engine.setJavaScriptEnabled(true);
          JSObject window = (JSObject) engine.executeScript("window");
          window.setMember("jsBridge", new JSBridge());
        //engine.executeScript("jsTest()");
        //engine.executeScript("explainHeadWord(document.getElementsByTagName('a')[0])");
        //engine.executeScript("jsBridge.jsHandleQuery(document.getElementsByTagName('a')[0])");
}

除了主DM之外,我还有一个交叉引用索引,它是从DM构建的Map<String, Collection<String>>,以及每次DM改变时在后台重建索引的触发器方法。第一种方法(在1.8.0_66版本上运行良好)基于ExecutorService:

private ExecutorService executor = Executors.newCachedThreadPool();
private Future<Boolean> indexer = executor.submit(() -> false);

...
private void rebuildIndex() {   
    executor.submit(() -> {
        indexer.cancel(true);
        indexer = executor.submit(() -> {
              fullSearchIndex = getIndex();
              if (isIndexingAborted()) return false;
              return true;          
        });             
        try {
              if (indexer.get()) {
                  log("resetIndex: Done");
                updateTableView();
               }            
        } catch (InterruptedException e) {
        ...             
        }
      });
 } 

正如所料,单击WebView中的锚点导致JS调用jsBridge.jsHandleQuery(hwElement.innerHTML)并最终在Controller中实现handleQuery(headWord)方法调用。但是在将JRE迁移到版本1.8.0_111之后,WebView停止响应锚点击。

我调查了日志,发现注入jsBridge成功,并执行了行window.setMember()下面的代码中注释的测试脚本。单击<a>元素导致什么都没有。但是,如果没有运行测试脚本(注释),日志中会出现记录:

<record>
    <date>2017-01-09T02:00:47</date>
    <millis>1483920047169</millis>
    <sequence>160</sequence>
    <logger>com.sun.webkit.WebPage</logger>
    <level>FINE</level>
    <class>com.sun.webkit.WebPage</class>
    <method>fwkAddMessageToConsole</method>
    <thread>11</thread>
    <message>fwkAddMessageToConsole(): message = TypeError: jsBridge.jsHandleQuery is not a function. (In 'jsBridge.jsHandleQuery(hwElement.innerHTML)', 'jsBridge.jsHandleQuery' is undefined), lineNumber = 26, sourceId = jar:file:/.../jar.jar!/view.js</message>
</record>

片刻之后,后台(索引)线程完成,WebView中的内容重新加载,点击<a>元素开始再次响应 - jsBridge.jsHandleQuery被执行。 索引线程执行遍历DM的getIndex()方法,并将收集的数据返回到DM中的Map数据。没有与FX应用程序线程的任何交互,也没有WebView依赖于索引。替换fullSearchIndex = getMockIndex();

private Map<String, Collection<String>> getMockIndex() {
    try {           
        Thread.sleep(20000);
    } catch (InterruptedException e) { }        
    return Collections.emptyMap();
}
后台线程中的

不会改变<a>的行为。

下一步是通过利用将后台线程重构为FX风格 javafx.concurrent.Service但结果是一样的。

我会欣赏指出我做错了什么以及如何解决这个问题。

1 个答案:

答案 0 :(得分:0)

尝试在侦听器之外实例化Bridge,即

final JSBridge bridge = new JSBridge();
engine.setJavaScriptEnabled(true);
engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
    if (newValue == Worker.State.SUCCEEDED) {
      JSObject window = (JSObject) engine.executeScript("window");
      window.setMember("jsBridge", bridge);
    }
});

(为我工作从1.8.0_91迁移到1.8.0_121)。