本文总结了一些开发中常见的问题,如 WebView 中图片不显示,inputfile 标签无效等问题。
H5 和 Native 交互
使用 JavaScriptInterface 注解约定的方法名称,方法名要禁止混淆。
自定义协议。Native 和 H5 互相发送消息(json 格式),解析,反射调用客户端方法。
WebView 的坑 图片显示不出来 Android 5.0 以后 url 协议和图片链接的 url 协议不一致导致的图片显示不出来,默认不允许混合模式。
1 2 3 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webSetting.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); }
MIXED_CONTENT_ALWAYS_ALLOW 允许从任何来源加载内容,即使来源是不安全的;
MIXED_CONTENT_NEVER_ALLOW 不允许混合模式,即不允许从安全的起源去加载一个不安全的资源;
MIXED_CONTENT_COMPLTIBILITY_MODE 当涉及到混合式内容时,WebView 会尝试去兼容最新 Web 浏览器的风格;
在认证证书不被 Android 所接受的情况下,我们可以通过重写WebViewClient#onReceivedSslError() 方法,设置接受所有网站的证书来解决,具体代码如下:
1 2 3 4 5 6 7 webView.setWebViewClient(new WebViewClient () { @Override public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); } });
不同版本 SDK 中的 WebChromeClient 中的回调方法做了多次修改。5.0 以下的 openFileChooser() 有几种重载方法,在 5.0 及以上该方法废弃,回调方法为onShowFileChooser()。一定要注意 ValueCallback< Uri > mFileCallback 和 ValueCallback<Uri[]> mFileCallbacks 一定要执行 onReceiveValue() 方法 ,调用完成后 mFileCallback 和 mFileCallbacks 置为 null。
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 webview.setWebChromeClient(new WebChromeClient () { public void openFileChooser (ValueCallback<Uri> valueCallback) { uploadMessage = valueCallback; openImageChooserActivity(); } public void openFileChooser (ValueCallback valueCallback, String acceptType) { uploadMessage = valueCallback; openImageChooserActivity(); } public void openFileChooser (ValueCallback<Uri> valueCallback, String acceptType, String capture) { uploadMessage = valueCallback; openImageChooserActivity(); } @Override public boolean onShowFileChooser (WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { return true ; } });
硬件加速导致大图无法显示 WebView 开启硬件加速时,加载大图无法显示。因为硬件加速中 OpenGL 对于内存是有限制的。如果遇到了这个限制,LogCat 只会报一个 Warning: Bitmap too large to be uploaded into a texture (587x7696, max=2048x2048)
硬件加速的四个级别:
Application 级别 android:hardwareAccelerated=”true”
Activity 级别 android:hardwareAccelerated=”true”
window 级别
1 2 getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View 级别
1 view.setLayerType(View.LAYER_TYPE_HARDWARE, null );
WebView 优化 内存优化 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 29 public static void destroyWebView (WebView webView, boolean clearCache) { if (webView != null ) { final ViewParent parent = webView.getParent(); if (parent != null ) { ((ViewGroup) parent).removeView(webView); } webView.clearHistory(); webView.clearCache(clearCache); webView.onPause(); webView.removeAllViews(); webView.destroyDrawingCache(); webView.destroy(); webView = null ; } }
启动优化 前端优化
降低请求量: 合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。
加快请求速度: 预解析 DNS,减少域名数,并行加载,CDN 分发。
缓存: HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage。
渲染: JS/CSS 优化,加载顺序,服务端渲染,pipeline。
等页面 finish 再加载图片 webViewSettings().setLoadsImagesAutomatically(false);//默认“true” webViewSettings().setBlockNetworkLoads(false);//有网络权限时默认“false”
1 2 3 4 5 6 7 webView.setWebViewClient(new WebViewClient (){ @Override public void onPageFinished (WebView view, String url) { if (!settings.getLoadsImagesAutomatically()) { settings.setLoadsImagesAutomatically(true ); } } });
Webview 预加载 第一步:Webview 预加载。App 启动就初始化一次 WebView。副作用是 WebView 的初始化必须位于主线程,但主线程会阻塞其他业务代码导致 ANR。
1 2 3 4 5 6 7 8 9 public class App extends Application { @Override public void onCreate () { ... WebView webView = new WebView (this ); webView.destroy(); webView = null ; } }
第二步:WebView 池。在首次后台创建 WebView 后并不销毁,而是存入备用池,当用户需要时直接取出来使用,这样可以将 WebView 初始化时间降到几乎为 0。 副作用是内存占用上,首个 WebView 会占用十几兆内存,非首个 WebView 内存占用 0.2M 左右内存。另外 Android 里 WebView 是和 Activity 进行绑定的,为了避免内存泄露,我们在预先创建的时候,借助 Context 的中间层 MutableContextWrapper,使用 MutableContextWrapper 包裹 applicationContext 的方式去提前创建 WebView,当使用时将 context 置为 activity 的即可。
1 2 3 4 5 6 MutableContextWrapper contextWrapper = new MutableContextWrapper (applicationContext);mPool[0 ] = new WebView (contextWrapper); ((MutableContextWrapper)webview.getContext()).setBaseContext(activityContext);
WebView 定制 WebSettings 设置 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 WebSettings settings = getSettings();settings.setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0" ); settings.setJavaScriptEnabled(true ); settings.setJavaScriptCanOpenWindowsAutomatically(true ); settings.setSupportZoom(true ); settings.setBuiltInZoomControls(true ); settings.setDisplayZoomControls(false ); settings.setGeolocationEnabled(true ); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } settings.setSaveFormData(true ); settings.setAppCacheEnabled(true ); settings.setDomStorageEnabled(true ); settings.setDatabaseEnabled(true ); settings.setDatabasePath("" ); if (isNetworkAvailable()) { settings.setCacheMode(WebSettings.LOAD_DEFAULT); } else { settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY); } settings.setUseWideViewPort(true ); settings.setLoadWithOverviewMode(true ); settings.setLoadsImagesAutomatically(false ); settings.setBlockNetworkLoads(false ); setHorizontalScrollBarEnabled(false ); setScrollbarFadingEnabled(true ); setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); setOverScrollMode(View.OVER_SCROLL_NEVER);
添加进度条 1 2 3 4 5 webView.setWebChromeClient(new WebChromeClient (){ @Override public void onProgressChanged (WebView view, int newProgress) { super .onProgressChanged(view, newProgress); } });
自定义错误界面 需要处理下拉刷新或返回键返回上一页面的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 webView.setWebViewClient(new WebViewClient (){ @Override public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error) { loadDataWithBaseURL(null , "" , "text/html" , "utf-8" , null ); view.setVisibility(View.VISIBLE); } @Override public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { super .onReceivedHttpError(view, request, errorResponse); } @Override public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { super .onReceivedSslError(view, handler, error); } });
判断是否滚动到页面底端
使用系统的 api 判断。
1 2 3 4 if (webView.getContentHeight() * webView.getScale() == (webView.getHeight() + webView.getScrollY())) { }
重写 WebView#onScrollChanged() 方法处理。
1 2 3 @Override protected void onScrollChanged (int l, int t, int oldl, int oldt) { super .onScrollChanged(l, t, oldl, oldt); }
判断是否存在垂直滚动条 1 2 3 public boolean existVerticalScrollbar () { return computeVerticalScrollRange() > computeVerticalScrollExtent(); }
computeVerticalScrollRange() 得到的是可滑动的最大高度,computeVerticalScrollExtent() 得到的是滚动把手自身的高,当不存在滚动条时,两者的值是相等的。当有滚动条时前者一定是大于后者的。
使用网页的标题设置标题栏 1 2 3 4 5 6 7 WebChromeClient mWebChromeClient = new WebChromeClient () { @Override public void onReceivedTitle (WebView view, String title) { super .onReceivedTitle(view, title); txtTitle.setText(title); } }; mWedView.setWebChromeClient(mWebChromeClient());
有的页面没有标题,可以设置一个默认标题;
WebView#goBack() 方法不一定调用,标题不更新。通过 HashMap 保存 url 和 title 的对应关系。
参考 [1] 腾讯浏览器服务(TBS、 x5)