IT干货程序员代码改变世界

Android WebView呼叫Javascript填坑记

2015-01-20  本文已影响1954人  LostAbaddon

作为誓死效忠大安卓帝国的程序狗,我一般不写技术类文章。
  你们翻翻我的文章就会发现,我还真不像技术流的。
  不过最近搞一个安卓的坑,搞得非常蛋疼,于是打算纪录下来。

坑是这样的:我有一个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。
  这个貌似看上去没什么,但实际上却很糟糕,因为这会导致两个问题:

  1. SoftKeyboard会自动消失(hideSoftKeyboard);
  2. 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,这个有点网站开发早期的轮询了,但个人不建议这么做,毕竟是手机端,毕竟是轮询,还是要考虑资源消耗的。
  至于说有没有别的通知手段,这个暂时没想到。。。

上一篇下一篇

猜你喜欢

热点阅读