在Android中使用WindVane所遇到的坑
在Android中使用WindVane所遇到的坑
在Android中使用WindVane所遇到的坑_麦田里的守望者-Jiang的博客-CSDN博客_window.windvane
最近做了一个需求,需要在Android中显示H5页面,且H5与Native之间有很多的交互,所以采用了WindVane。
1.如何使用WindVane JSBridge
简介:
WindVane JSBridge 提供了 JS 与 Native 之间的通信链路,基于这个通信基础,Native 可以暴露出一些服务 API 提供给 JS 调用,并通过方法返回值或事件将数据返回给 JS。
JS 端会使用 ClassName.HandlerName 来调用 JSBridge,WindVane 会根据 ClassName 与 HandlerName,来找到对应的 Native 方法。
注册JSBridge:
JSBridge 需要注册给 WindVane,才能够让 JS 正确的调用到对应的 Native 方法
Android只提供了静态注册方法:
public class MyJSBridge extends WVApiPlugin {
@Override
public boolean execute(String action, String params, WVCallBackContext wvCallBackContext) {
if ("showToast".equals(action)) {
showToast(params, wvCallBackContext);
return true;
}
return false;
}
private void showToast(String params, WVCallBackContext wvCallBackContext) {
if (params != null && params.length() > 0) {
try {
JSONObject jsonObject = new JSONObject(params);
String content = jsonObject.optString("content");
Toast.makeText(mContext, content, Toast.LENGTH_SHORT).show();
wvCallBackContext.success();
} catch (JSONException e) {
e.printStackTrace();
wvCallBackContext.error();
}
} else {
wvCallBackContext.error();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
注册具体的类:
WVPluginManager.registerPlugin("MyJSBridge", MyJSBridge.class);
注意:第一参数为类名,必须是”MyJSBridge”具体字符串的类名,不能是像”MyJSBridge.class.getSimpleName()”这样动态获取的类名,因为在Release包中,类会进行混淆,获取到的类名就不是”MyJSBridge”了。这样容易在Debug包中调试正确(因为没有进行类混淆),但在Release就发生错误。
在js代码中调用:
var params = {
'content': 'Something to Native',
};
window.WindVane.call('MyJSBridge', 'showToast', params, function(e) {
alert('success' + JSON.stringify(e));
}, function(e) {
alert('failure' + JSON.stringify(e));
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
另外,除了上面H5通过JSBridge调用Native的方法外,WindVane还提供了标准的事件机制。
其中H5 向 Native 发送事件:
var params = {
event: 'eventName',
param: {
// 事件要传递的数据。
}
};
window.WindVane.call('WVStandardEventCenter','postNotificationToNative', params, function(e) {
alert('success');
}, function(e) {
alert('failure: ' + JSON.stringify(e));
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
在Android中需要实现WVEventListener接口:
@import android.taobao.windvane.service;
@import android.taobao.windvane.standardmodal
// 首先,需要自己实现监听用的 Listener。
public class JsEventListener implements WVEventListener {
@Override
public WVEventResult onEvent(int id, WVEventContext ctx, Object... obj) {
// 所有事件均派发到这里,WVEventId.H5TONATIVE_EVENT 表示 H5 发送过来的事件。
if (id == WVEventId.H5TONATIVE_EVENT) {
if (obj[0] instanceof String) {
String params = (String)obj[0];
// 这里的 params 是包含 event 和 param 的 JSON String,需要自行反序列化。
}
}
return null;
}
}
// 然后,注册监听器。
WVEventService.getInstance().addEventListener(new JsEventListener());
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
这上面的两种方法,H5都可以和Native进行交互。第一种方法,主要用于H5获取一些Native的一些信息,比如设备信息,用户信息等。第二种方法,主要H5传递一些数据通知Native进行UI改变。
2.关于访问Https的链接
当访问Https链接的时候,由于证书验证问题,通常会发生SSL验证错误,导致页面加载失败。在WebViewClient类中有一个方法:
/**
* Notify the host application that an SSL error occurred while loading a
* resource. The host application must call either handler.cancel() or
* handler.proceed(). Note that the decision may be retained for use in
* response to future SSL errors. The default behavior is to cancel the
* load.
*
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's
* response.
* @param error The SSL error object.
*/
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
handler.cancel();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
上面说,当发生SSL验证错误的时候,默认会取消掉请求。这就导致WebView加载了H5页面但是H5无法显示。所以,如果想不管SSL错误,让WebView继续加载H5页面,则需要覆盖WebViewClient类中的onReceivedSslError方法:
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// 不要使用super,否则有些手机访问不了,因为包含了一条 handler.cancel()
// 接受所有网站的证书,忽略SSL错误,执行访问网页
handler.proceed();
}
- 1
- 2
- 3
- 4
- 5
- 6
3.关于混淆导致找不到类和类方法
在发布Release包的时候,都会进行类和类方法的混淆,那么使用第一种静态注册方法时,H5通知Native就会发生错误。有一种简单的方式就是实现Serializable接口,反序列化。
public class MyJSBridge extends WVApiPlugin implements Serializable
- 1
但是这种方法的前提是在proguard-rules.pro文件中配制了实现Serializable接口的类和类的方法不被混淆。配置的规则如下:
-keep class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
除了这种方法外,同理,还可以在项目的proguard-rules.pro文件中配置不混淆的类和类方法。关于配置的一些规则和例子,请查看ProGuard manual。
但这样不混淆之后,容易被反编译查看到源代码,所以编写代码时尽量注意代码的安全性。
4.WebView内存泄漏
在使用WindVane中的WVWebView时,文档中有一句提示:
你也可以直接使用WVWebView,不过,直接使用WVWebView的时候需要注意webview在对应生命周期中得处理,特别是销毁的时候。一定要先将该webview从夫view中移除,然后再调用的webview的destroy操作。
其实,不管是使用WVWebView还是单独的使用WebView都要注意内存泄漏,因此在Activity或者Fragment中一定要销毁WebView。
@Override
protected void onDestroy() {
super.onDestroy();
if (mWebView != null) {
// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearCache(true);
mWebView.onPause();
mWebView.removeAllViews();
mWebView.destroyDrawingCache();
mWebView.destroy();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
5.使用小技巧
隐藏滚动条:
webView.setVerticalScrollBarEnabled(false);
webView.setHorizontalScrollBarEnabled(false);
- 1
- 2
禁止滚动:
webView.setFocusable(false);
webView.setFocusableInTouchMode(false);
- 1
- 2
加速渲染:
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// 不要使用super,否则有些手机访问不了,因为包含了一条 handler.cancel()
// super.onReceivedSslError(view, handler, error);
// 接受所有网站的证书,忽略SSL错误,执行访问网页
handler.proceed();
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
访问JavaScript接口:
webView.getSettings().setJavaScriptEnabled(true);
- 1
白屏,需支持DOM storage:
webView.getSettings().setDomStorageEnabled(true);
- 1
参考:
1. Android 5.1 WebView内存泄漏分析
2. Disable scrolling in webview?