个人认为Android的WebView一直是一个比较难搞的东西,因为它需要和很多的Web开发打交道,如果以前没接触过Web相关的开发就会觉得有些不爽,但是现在越来越多的应用都是Hybrid的模式,HTML5定稿一年多,感觉也挺火,这也是以做内容为主的App非常需要的技术,所以还得多学学。
从Android4.4开始,WebView底层的实现从原来的Webkit变成了chromium,从而实现了对HTML5更好的支持,并且也和Chrome浏览器的一些特征越来越像。接触过WebView开发应该对WebView.setWebContentsDebuggingEnabled(true)
不会陌生,正是从4.4开始的改变才使得WebView的调试变得更加方便。
只是用来展示一个网页内容还好,如果要通过WebView执行JS脚本来和Native代码做一些通信,就要小心可能会踩到各种坑了。例如onclick事件没用,用onTap又会触发两次,4.4以上只能用loadUrl的方法执行一行js代码,还有可能会被转码,API17以上需要给Java方法添加注解,API17以下又要换一种方法保证安全性等等。在这里记录一下我自己的学习心得和踩过的坑。
Java与JS互相调用
在Android开发里面,我们说的WebView与JS互相调用,通常就是指用Java写的Native代码与JS的互相调用。所以下面我都会说Java调用JS,JS调用Java。而不是说WebView调用JS,JS调用WebView了。
1.Java调用JS
- 首先在JS中定义好即将提供给Native的方法
function javaCallJS()
- 然后在Java代码里,通过
WebView.loadUrl("javascript:javaCallJS()");
就可以调用JS的方法了。
2.JS调用Java
方法1:addJavascriptInterface:
- 首先在Java里定义一个类
WebAppInterface
,然后在Java中通过WebView.addJavascriptInterface(new WebAppInterface(), "Android");
就可以在JS中创建这个类的实例Android
对象了 - 然后在JS中可以直接使用
Android
对象和它的方法,这样就实现了JS调用Java。
方法2:iframe + CustomWebViewClient:
- 在JS代码动态添加一个iframe,将其src属性设置为JS需要传给Java的参数(例如
bridge://uncle.nought.com?arg=xxx
)。 - 在Java代码中,定义一个
CustomWebViewClient extends WebViewClient
,然后mWebView.setWebViewClient(new CustomWebViewClient())
。 - 在Java代码中的
CustomWebViewClient
中,重写shouldOverrideUrlLoading(WebView view, String url)
方法,自己处理url
参数,并return true
。 - 这时JS代码就可以把参数通过url传递给Java,Java拿到参数去执行相应的工作了。如果JS需要返回值,那么通过Java调用JS代码的形式把返回值返回给JS。
方法小结
Java调用JS的代码
其实比较简单,就是通过WebView.loadUrl("javascript:javaCallJS()")
loadUrl的形式。这里啰嗦一句,从Android4.4开始,由于chromium内核对安全性检查更加严格,所以并不是传入的所有JS代码,都能够通过loadUrl来执行它。详情可以看一下这里https://code.google.com/p/android/issues/detail?id=69969,简单来说就是4.4以上的WebView在loadUrl时会给我们的参数做一个escape,因此参数就变了,很有可能变得JS不认识,无法执行了。所以这时候需要用到WebView.evaluateJavascript(java.lang.String, android.webkit.ValueCallback<java.lang.String>)
这个方法。后面我再详细举个例子来说明这个问题。
JS调用Java的方法1:addJavascriptInterface
这种方法是比较简单的。在WebView官方的文档里面有介绍如何结合WebView来进行WebApp的开发http://developer.android.com/intl/zh-cn/guide/webapps/webview.html#AddingWebView。
这里要再特别严肃地啰嗦一句!由于4.2(API<17)版本之前的WebView,在执行WebView.addJavascriptInterface(Object obj, String interfaceName)
时存在一个漏洞,该漏洞的原因是在向JS中注入一个Java对象的时候,并没有对注册的这个Java类的方法调用做限制。导致JS代码里面可以利用发射机制,调用未注册的其他Java类。例如:
1 | // Java里面注册了injectedObj对象以后 |
1
2
3
4
### 第二步:在Java代码中调用
非常简单,直接在Java代码里调用刚才的JS Function。
mBtnJavaCallJs.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/**
* 注意参数的传递需要符合JS的语法,用单引号或者反斜杠转义
*/
String js = "javascript:javaCallJS(\"Java called JS.\")";
mWebView.loadUrl(js);
}
});
1 | 这样就实现了Java在WebView里面打印了“Java called JS.”。 |
1
2
### 第二步:自定义一个WebViewClient
public class HelloWebViewClient extends WebViewClient {
private static final String TAG = HelloWebViewClient.class.getSimpleName();
private static final String PREFIX = “bridge://uncle.nought.com”;
private static final Pattern ARG_PATTERN = Pattern.compile(PREFIX + “\?arg=(.*)”);
private MainActivity.TextViewChanger mTextViewChanger;
public HelloWebViewClient(MainActivity.TextViewChanger textViewChanger) {
mTextViewChanger = textViewChanger;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(TAG, "Get params from JS: " + url);
parseJSParams(url);
return true;
}
private void parseJSParams(String url) {
// 解析自定义参数
if (url.startsWith(PREFIX)) {
Matcher matcher = ARG_PATTERN.matcher(url);
if (matcher.matches()) {
mTextViewChanger.changeText(matcher.group(1));
}
}
}
}1
2
### 第三步:异步刷新UI
private TextViewChanger mTextChanger = new TextViewChanger() {
@Override
public void changeText(final String arg) {
/**
* 官方说明文档:
* Note: The object that is bound to your JavaScript runs in another thread and not in the thread
* in which it was constructed.
*
* mWebAppInterface虽然是在UI线程创建的,但是bind到JS以后就是在另一条线程中运行的,因此刷新UI的时候需要注意
*/
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mTextView != null) {
mTextView.append("\n" + arg);
}
}
});
}
};
1 |
|
mWebView..getSettings().setDomStorageEnabled(true);`
就好了。
待补充。。。前面说到4.4开始有些JS代码得用evaluateJavascript来执行。blabla。
PS
手头看到了篇WebView文章,Android 4.4 中 WebView 使用注意事项,可参考参考。