內容有點無腦,先把結論寫在前面:
外部 JS(官方文件稱為「handwritten JS」,其實不同 GWT Module 就滿足這個條件)要呼叫 Java 的 static method,必須先透過 JSNI 設定
$wnd.methodName = @fooPackage.FooClass::javaMethodName(*)
,後續使用$wnd.methodName()
來達到目的。關鍵在於 JSNI 中:
javaMethodName()
後頭不需再加()
。javaMethodName()
的參數某些情況下可以省略 field descriptor,直接用*
代替。
update:感謝 darkk6(ptt.cc)提醒,讓我發現我不但死腦筋,而且還少測了一種寫法… 所以文章就要重新翻修了 (艸
前天被問到一個問題:「同一個 host page 上兩個不同 GWT Module 要怎麼溝通?」我原本以為這不會是問題,結果回到家實際測了一下才發現根本不是這麼一回事 [死]。一言以蔽之,可以用這個 stackoverflow 來解釋。上頭發問的內容大抵上就是我原本的想法:用 event bus 來解,結果是不可行的,不過那不是這篇文章的重點 XD
為了保險起見實際測試了一下解答的 code,然後就發現根本行不通:
private native static exportMyJavaMethod() /*-{
$wnd.myJavaMethod = @my.package.module1.MyClass1::myJavaMethod;
}-*/;
那個 myJavaMethod
沒給參數的 field descriptor,GPE 就報 syntax error、development mode 也一樣炸。回頭去確認官方文件,沒想到官方文件的程式碼一樣微妙……
package mypackage;
public MyUtilityClass{
public static int computeLoanInterest(int amt, float interestRate, int term) { ... }
public static native void exportStaticMethod() /*-{
$wnd.computeLoanInterest = $entry(
@mypackage.MyUtilityClass::computeLoanInterest(IFI)
);
}-*/;
}
我不太確定那個 IFI
是啥意思 or 啥縮寫…… ==”
好,不管這些,自己重新寫一個程式測試這個部份:
package foo.client;
//import 略
public class FooEP implements EntryPoint {
@Override
public void onModuleLoad() {
exportJsMethod();
callFooMethod("...");
}
static void javaMethod(String arg1) {
Window.alert("javaMethod : " + arg1);
}
native static void exportJsMethod() /*-{
$wnd.fooMethod = @foo.client.FooEP::javaMethod(Ljava/lang/String;)();
}-*/;
native static void callFooMethod(String message) /*-{
$wnd.fooMethod(message);
}-*/;
}
結果在 development mode 執行就炸錯誤,主要錯誤訊息是
com.google.gwt.core.client.JavaScriptException: (TypeError) @foo.client.FooEP::callFooMethod()([]): undefined is not a function
其實在這錯誤訊息之前就有兩個地方散發出怪味道:
$wnd.fooMethod = @foo.client.FooEP::javaMethod(Ljava/lang/String;)();
,為什麼沒有實際給javaMethod()
參數?等等,這個時間點給他什麼都不對啊?- 忽略 1,為什麼會先跳出 alert 視窗顯示
javaMethod : null
,然後才炸錯誤?
如果也在 callFooMethod()
裡頭先卡一行 $wnd.alert("WTF?")
,會發現顯示 WTF?
的 alert 視窗還是會出現,表示 callFooMethod()
有正確被執行到,炸的是 $wnd.fooMethod()
。
整個看起來,事情好像就有頭緒了。其實 exportJsMethod()
的 $wnd.fooMethod
根本沒有正確 assign 成 FooEP.javaMethod()
,而是 assign 成 FooEP.javaMethod()
的回傳值──根本沒這玩意,所以到 callFooMethod()
的時候當然就炸了。
原先我一直死腦筋用 Java 的角度去看,後來換成 JS 的角度去看其實這樣結果很正常。在 JS 當中一個變數可以代表一個數值、也可以代表一個 function / method,如果程式中要執行該 function / method,那就是加上 ()
,例如在 Chrome console 輸入 alert
會得到 function alert() { [native code] }
,而 alert("WTF")
才會真正跳出 alert 視窗。在上頭的 case 當中,我只是要把 $wnd.fooMethod 指定為一段程式碼,根本沒有要執行它的意思,所以加上 ()
根本就是多餘且錯誤阿…
所以拿掉後頭的空括號,一切就正常了(之前怎麼沒想過阿阿阿阿 [核爆]),官方文件的那個 IFI
,darkk6 猜測是 int
、float
、int
的縮寫,如今(修正完之後)回頭看也很合理了 [死]。
最後不小心看到另一個 stackoverflow,發現還有一招裏技可以用,就是直接給 *
號,省去打一堆 field descriptor:
native static void exportJsMethod() /*-{
$wnd.fooMethod = @foo.client.FooEP::javaMethod(*);
}-*/;
這有個前提,就是 FooEP
中只能有一個 javaMethod()
,否則就會炸錯誤:也就是說,如果你有兩個以上同名的 method,那還是得乖乖寫 field descriptor。另外,在 JSNI 中呼叫 $wnd.fooMethod()
時已經是純粹 JS 了,所以即使你多傳參數、或是少傳參數也不會怎麼樣…… 至少 GPE 跟 browser 都沒有特別反應。
native static void callFooMethod(String message) /*-{
$wnd.fooMethod();
$wnd.fooMethod(message, "又多餘了");
}-*/;
好了,事實證明 GWT 文件也沒寫得那麼好,除了上次那個很隱晦不想讓人知道的 AutoBean 之外,又遇到了一個謎樣裏技。還是說其實官方文件有,只是我沒找到呢…… [淚目]
沒有留言:
張貼留言