作为誓死效忠大安卓帝国的程序狗,我一般不写技术类文章。
你们翻翻我的文章就会发现,我还真不像技术流的。
不过最近搞一个安卓的坑,搞得非常蛋疼,于是打算纪录下来。
坑是这样的:我有一个WebView,里面有一个写字区域,然后我要在写字的同时呼叫里面的JS。
在iOS上这事挺容易整的,比如这样(obj-C为例):
NSString *hasRange =
[self stringByEvaluatingJavaScriptFromString:@"MobileWriter.hasRange()"];
但在大安卓帝国,这事就有点蛋疼了。
在KitKat上,WebView有个接口名叫evaluateJavascript,从而事情是这样的:
evaluateJavascript("MobileWriter.hasRange();", resultCallback);
这货看上去和iOS上差不多,但已经有点讨厌了:它是Callback机制的,不像iOS上直接拿结果。
但,这还算好的,如果不是KitKat,也就是4.4之前的Android,你连这个接口都没有,于是只能这样:
loadUrl("javascript:MobileWriter.hasRange();");
这个就很蛋疼了,因为没有callback,你必须在JS运行结束后,让JS去调用一个指定的对象,从而通过这个对象来获得回调,比如下面这样:
(In Java)
addJavascriptInterface(new JavascriptDelegate () {
@JavascriptInterface
public void jsCallback (String result) {
Log.i("Editor", "Blablabla...");
}
}, "AndroidHost");
(In Javascript)
MobileWriter.hasRange = function () {
"Blablabla..."
if (AndroidHost && AndroidHost.jsCallback) {
AndroidHost.jsCallback('Mission Complete.');
}
};
看着是不是就很蛋疼?
但,这根本不算事,挺Easy的,只要J-J两端协议定好,这都不叫事儿。
麻烦的是下面这个问题:
每次你在Java端使用loadUrl的时候,在4.4以下的安卓上都会引发WebView的页面重新载入事件(而且这个你还没法通过重载WebViewClient的shouldOverrideUrlLoading方法来阻止),从而引发系统的clearHelpers,这货则会调用clearTextEntry并最终调用到hideSoftKeyboard。
这个貌似看上去没什么,但实际上却很糟糕,因为这会导致两个问题:
- SoftKeyboard会自动消失(hideSoftKeyboard);
- Contextual Menu和相关选区会自动消失(clearTextEntry)。
也就是说,如果你在输入的时候就要调用JS的话,那么只要你调用了JS,输入状态就自动消失,键盘没了,选区没了,你得重新开始选择。
这事就很蛋疼了。
4.4为什么通过调用loadUrl来调用JS不会有这个问题?因为如果你调用的是javascript协议从而也就是调用js函数的话,其实4.4下面走的是上面提到的evaluateJavascrpt,当然安全了。
解决这个问题的方法,一个是用反射调用android.webkit.BrowserFrame.stringByEvaluatingJavaScriptFromString(),这个比较蛋疼。
另一个,则是设法通知JS我Java端有事件了,然后让JS调用JavascriptDelegate插入的Delegate对象,并从这个对象获取当前要做的事件,并执行。
第一个方法比较霸气,直接用反射,相当犀利,但我不确定能否通过安检(不过国内App反正也没啥检查,应该不慌。GoogleAppStore是否允许我这么玩我就不知道了)。
第二个方法比较温和,没这么霸气,但缺点是你得加一个同步锁,避免操作不同步导致问题——WebView中的JS是跑在另一根线程上的,这种频繁的线程间相互调用回调的方法安全性是个问题。
至于说通知JS应该要召唤Delegate的方法嘛,当然不能傻傻地用loadUrl了。你可以小小地微调一下WebView的尺寸,引发JS端的window.onresize事件,然后就可以让JS端去调用Java端了。
或者另一个比较蛋疼的方法是JS端开一个Timeout甚至Interval,这个有点网站开发早期的轮询了,但个人不建议这么做,毕竟是手机端,毕竟是轮询,还是要考虑资源消耗的。
至于说有没有别的通知手段,这个暂时没想到。。。