Android WebView开发常见的坑
现在的App基本上都会使用Native+H5的方式来开发的,例如网易新闻详情页面,微信公号详情页面都会使用WebView开发。这样可以很容易实现图文排版的需求,而且混合开发的好处也是显而易见的。
AC在开发项目的时候也经常使用WebView这个控件,这个控件使用很方便,但却也有诸多问题。以下是AC在开发过程中踩过的坑,希望对使用这个控件的小伙伴们有用。
1、WebView无法显示html中的alert和confirm对话框
WebView要显示html中的alert和confirm对话框,需要实现WebViewChromClient接口。WebView只是一个承载体,各种内容的渲染需要使用WebviewChromClient去实现,所以set一个默认的基类WebChromeClient就行
mWebView.setWebChromeClient(new WebChromeClient());
用于弹起alert等,如果要定制alert,confirm对话框就必需重写onAlert和onConfirm方法
2、WebView中实现的JS方法无法调用
在实现WebView与JS交互的过程中,如果遇到点击后JS方法无响应,应该注意以下问题:
(1)WebView.addJavascriptInterface(new AndroidClick(), "app");这个方法的别名android是否与JS中的对象名称一致如
<div style="text-align:center;"><a onclick="window.app.onclick('www.115.com')" >内容已自动优化阅读,点击查看原文</a></div>
(2)如果是H5通过alert方法来提示对话框的信息的时候,WebView需要实现注册这个回调函数
mWebView.setWebChromeClient(new CustomWebChromeClient());
并实现以下alert回调方法,并可以实现自定义的对话框样式。
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
//return super.onJsAlert(view, url, message, result);
showConfirmDialog(view.getContext(), message, result, false);
return true;
}
类似的还有onJsConfirm方法等。
(3)如果发布的APP有进行混淆,那么AndroidClick这个JS 与 JAVA交互的类需要proguard.cfg文件忽略这个类的混淆,否则混淆后JS将执行不了。
-keepclassmembers class net.angrycode.js2java.AndroidClick{ *; }
(4)JS调用Native方法时,如果前端执行一些比较耗时的操作,前端代码就有可能会跑在线程里,这时候如果JS方法调用Native方法做一些逻辑操作,调用就会有问题,虽然不会Crash但是会报错。参考解决方法是(mJSInterface是通过addJavaInterface注入到WebView中的对象)
/**
* 提交创建申请单数据
*/
mJSInterface.setOnPutApplyListener(json -> {
mWebView.post(() -> {
//确保是在主线程中访问Native相关控件
});
});
3、快速打开和关闭WebView页面发生了控件空指针异常问题
这个问题可能有很多原因,但WebView加载过程中如果关闭了页面控件被回收而加载线程还在继续跑,那么数据返回时页面就有可能发生空指针异常。这个时候可以在WebViewClient以及WebViewChrome接口中的onPageStart以及onPageFinish,onProgressChange这几个回调方法中判断当前页面是否存在,若不存在则直接返回。
@Override
public void onPageFinished(WebView view, String url) {
if (getActivity() == null || getActivity().isFinishing()) {
return;
}
super.onPageFinished(view, url);
//...
}
4、WebView与JS交互引起的安全问题
4.2以下系统这里推荐一个开源项目https://github.com/pedant/safe-java-js-webview-bridge
当然WebView的安全不止这个,可以自行百度或者Google脑部相关姿势。AC也会在之后的文章中专门整理这个问题。
同时此问题在官方4.2(API Level 17)以上手机已经得到修复,使用@JavascriptInterface 注解声明addJavascriptInterface注入的方法。即只有使用@JavascriptInterface的方法才会被注入到WebView中。
5、WebView长按弹出ActionMode菜单样式问题
三星手机WebView弹出的菜单样式有可能会出现此问题,解决方法可以继承WebView重写startActionMode()方法,然后修改menu的菜单样式。
@Override
public ActionMode startActionMode(ActionMode.Callback callback) {
return super.startActionMode(new CustomCallback(getContext(), callback));
}
public static class CustomCallback implements ActionMode.Callback {
private ActionMode.Callback callback;
private Context context;
public CustomCallback(Context context, ActionMode.Callback callback) {
this.callback = callback;
this.context = context;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return callback.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
int size = menu.size();
for (int i = 0; i < size; i++) {
MenuItem menuItem = menu.getItem(i);
final Drawable moreMenuDrawable = menuItem.getIcon();
if (moreMenuDrawable != null) {
menuItem.setIcon(DrawableUtil.getThemeDrawable(context, moreMenuDrawable));
}
}
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return callback.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
callback.onDestroyActionMode(mode);
context = null;
}
}
当然如果不需要长按事件,可以注册WebView的长按事件
mWebView.setOnLongClickListener(v -> {
return true;
});
6、硬件加速问题
一般情况下,使用WebView开发都会使用硬件加速来提高WebView的渲染速度。可以在AndroidManifest.xml文件中设置
android:hardwareAccelerated="true"
也可以在页面中使用
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
但是简单的使用以上两种方法,开启硬件加速以及不开启硬件加速在一些手机上都会出现这样或者那样的问题,例如,如果一直开启了硬件加速,某些手机有可能会出现屏幕花屏的问题;还有WebView在不同厂商的手机中依然可能会出现Crash问题。而Crash的问题一般是报了WebView底层的错误。可以参考以下处理方式:
在onPageStart中开启硬件加速,在onPageFinish中关闭硬件加速。
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (getActivity() == null || getActivity().isFinishing()) {
return;
}
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
if (getActivity() == null || getActivity().isFinishing()) {
return;
}
view.setLayerType(View.LAYER_TYPE_NONE, null);
super.onPageFinished(view, url);
}
7、使用独立进程跑WebView
有一定使用WebView经验的老司机可能都把项目中的WebView模块抽取出来,并跑在独立的进程中去。例如在manifest文件中使用属性process指定独立的进程。
<!-- Web 页面 -->
<activity
android:name=".UI.CommonUI.Activity.WebBrowserActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:hardwareAccelerated="true"
android:label=""
android:process=":web"
android:screenOrientation="portrait" />
这样做的是因为WebView在以前的版本的底层实现中会发生内存泄漏,导致页面关闭但是依然没有释放内存,而在独立进程中的WebView模块就可以很好解决此问题,在关闭WebView的时候就关闭进程,这样就可以释放相关的内存了。
但是使用多进程架构,进程间数据共享就是一个问题了。例如进程A设置了cookie,同样我也要在进程B共享这个cookie。目前AC认为可行的解决方案是使用ContentProvider来共享数据。此问题AC没有写相应的Demo,希望有老司机可以带路。
8、WebView生命周期回调
WebView也有生命周期回调方法,这些方法需要在Activity或Fragment相应的生命方法中回调。主要是onResume(),onPasuse()和onDestory()(或者onDestoryView())这几个方法的回调实现。
@Override
public void onResume() {
if (mWebView != null) {
mWebView.resumeTimers();
mWebView.onResume();
}
super.onResume();
}
@Override
public void onPause() {
if (mWebView != null) {
mWebView.pauseTimers();
mWebView.onPause();
}
super.onPause();
}
@Override
public void onDestroyView() {
if (mWebView != null) {
mWebView.stopLoading();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.removeAllViews();
mWebView.setWebChromeClient(null);
mWebView.setWebViewClient(null);
unregisterForContextMenu(mWebView);
mWebView.destroy();
}
super.onDestroyView();
}
这几个方法的回调实现有利无弊,可以很好地避免翻车。例如WebView中播放声音在页面关闭之后还声音的问题,WebView页面跳转其他页面后返回显示空白不刷新的问题等等。
以上便是AngryCode在使用WebView开发过程中踩过的坑,相应解决方案纯粹是经验参考,因为使用环境以及能力的局限,如果文章出现错误,欢迎老司机留言指出。