
- 较全的webview坑点我们在使用webview时总比想象的要困难很多,涉及到兼容,手机差异,内存泄露等,这里整理了比较全的webview坑点和解决方案
- 持续收集,保持最新这里会持续收集,不断更新最新的最全的webview坑点和解决方案
1、前言
通常我们在自己开发的 APP 中打开网页无非两种方法: 一是跳转到系统自带的浏览器,二是使用 WebView 控件加载页面。使用 WebView 控件的好处就是可以通过各种 api 接口来定制各种行为,常用的几个设置地方为 WebSettings、JavaScriptInterface、WebViewClient 和WebChromeClient。平时出现的问题都可以通过修改这些设置来解决。
2、问题总结
2.1、使用了 WebView 还是跳转到了系统自带的浏览器?
很简单的解决方法,为你的 webview 设置一个新的 WebViewClient。
1 2 3 4 5 6 7 |
webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }); |
1 2 |
// 或者直接添加,效果是一样的 webView.setWebViewClient(new WebViewClient()); |
2.2、获取网页的标题和图标
通过 WebChromeClient 可以获取到这些信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
webView.setWebChromeClient(new WebChromeClient() { @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); setTitle(title); } @Override public void onReceivedIcon(WebView view, Bitmap icon) { super.onReceivedIcon(view, icon); setIcon(icon); } }); |
但是,这里有个问题,当通过 webView.goBack()
方式返回上一级Web页面的时候不会触发这个方法,因此会导致标题无法跟随历史记录返回上一级页面。所以需要在 onPageFinished()
中对界面标题重新设置。
1 2 3 4 5 6 7 |
webView.setWebViewClient(new WebViewClient(){ @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); setTitle(String.valueOf(view.getTitle())); } }); |
2.3、返回键实现网页的后退键
在 WebView 中可以通过 goBack() 方法后退到历史记录的上一项。
1 2 3 4 5 6 7 8 |
// 在 Actvity 中监听返回键按钮 @Override public void onBackPressed() { if (webView.canGoBack()) webView.goBack(); else super.onBackPressed(); } |
2.4、设置 WebView 的 header
在 WebView 的 loadUrl()
方法中传入 Header 参数即可。
1 2 3 4 5 6 7 |
public void loadURLWithHTTPHeaders() { final String url = "http://cpacm.net"; WebView webView = new WebView(getActivity()); Map<String,String> extraHeaders = new HashMap<String, String>(); extraHeaders.put("Referer", "http://www.google.com"); webView.loadUrl(url, extraHeaders); } |
2.5、设置 WebView 的 User-Agent
不要试图在 Header 里面去修改,而是在 WebSettings 修改
1 |
webView.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0"); |
2.6、如何设置 WebView 的缓存
当需要本地缓存网页的时候就需要打开 WebViewSettings 的缓存开关,这样子当下次进到该页面无网络的情况下也能打开页面。
1 2 3 4 5 6 7 8 9 |
WebSettings settings = webView.getSettings(); settings.setAppCacheEnabled(true); //启用应用缓存 settings.setDomStorageEnabled(true); //启用或禁用DOM缓存。 settings.setDatabaseEnabled(true); //启用或禁用DOM缓存。 if (SystemUtil.isNetworkConnected()) { //判断是否联网 settings.setCacheMode(WebSettings.LOAD_DEFAULT); //默认的缓存使用模式 } else { settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY); //不从网络加载数据,只从缓存加载数据。 } |
2.7、无法下载文件?
在自己写的 WebView 下是无法直接下载文件,需要自己监听下载事件并对下载的动作进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * 当下载文件时打开系统自带的浏览器进行下载,当然也可以对捕获到的 url 进行处理在应用内下载。 **/ webView.setDownloadListener(new FileDownLoadListener()); private class FileDownLoadListener implements DownloadListener { @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } } |
2.8、无法打开文件选择器?
通过重写 WebChromeClient
来实现点击 <input type='file'>
来打开系统文件选择器。
一个完整的Activity示例
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
public class MainActivity extends AppCompatActivity { /** Android 5.0以下版本的文件选择回调 */ protected ValueCallback<Uri> mFileUploadCallbackFirst; /** Android 5.0及以上版本的文件选择回调 */ protected ValueCallback<Uri[]> mFileUploadCallbackSecond; protected static final int REQUEST_CODE_FILE_PICKER = 51426; protected String mUploadableFileTypes = "image/*"; private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initWebView(); } private void initWebView() { mWebView = (WebView) findViewById(R.id.my_webview); mWebView.loadUrl("file:///android_asset/index.html"); mWebView.setWebChromeClient(new OpenFileChromeClient()); } private class OpenFileChromeClient extends WebChromeClient { // Android 2.2 (API level 8)到Android 2.3 (API level 10)版本选择文件时会触发该隐藏方法 @SuppressWarnings("unused") public void openFileChooser(ValueCallback<Uri> uploadMsg) { openFileChooser(uploadMsg, null); } // Android 3.0 (API level 11)到 Android 4.0 (API level 15))版本选择文件时会触发,该方法为隐藏方法 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { openFileChooser(uploadMsg, acceptType, null); } // Android 4.1 (API level 16) -- Android 4.3 (API level 18)版本选择文件时会触发,该方法为隐藏方法 @SuppressWarnings("unused") public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { openFileInput(uploadMsg, null, false); } // Android 5.0 (API level 21)以上版本会触发该方法,该方法为公开方法 @SuppressWarnings("all") public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { if (Build.VERSION.SDK_INT >= 21) { final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;//是否支持多选 openFileInput(null, filePathCallback, allowMultiple); return true; } else { return false; } } } @SuppressLint("NewApi") protected void openFileInput(final ValueCallback<Uri> fileUploadCallbackFirst, final ValueCallback<Uri[]> fileUploadCallbackSecond, final boolean allowMultiple) { //Android 5.0以下版本 if (mFileUploadCallbackFirst != null) { mFileUploadCallbackFirst.onReceiveValue(null); } mFileUploadCallbackFirst = fileUploadCallbackFirst; //Android 5.0及以上版本 if (mFileUploadCallbackSecond != null) { mFileUploadCallbackSecond.onReceiveValue(null); } mFileUploadCallbackSecond = fileUploadCallbackSecond; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); if (allowMultiple) { if (Build.VERSION.SDK_INT >= 18) { i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } } i.setType(mUploadableFileTypes); startActivityForResult(Intent.createChooser(i, "选择文件"), REQUEST_CODE_FILE_PICKER); } public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == REQUEST_CODE_FILE_PICKER) { if (resultCode == Activity.RESULT_OK) { if (intent != null) { //Android 5.0以下版本 if (mFileUploadCallbackFirst != null) { mFileUploadCallbackFirst.onReceiveValue(intent.getData()); mFileUploadCallbackFirst = null; } else if (mFileUploadCallbackSecond != null) {//Android 5.0及以上版本 Uri[] dataUris = null; try { if (intent.getDataString() != null) { dataUris = new Uri[] { Uri.parse(intent.getDataString()) }; } else { if (Build.VERSION.SDK_INT >= 16) { if (intent.getClipData() != null) { final int numSelectedFiles = intent.getClipData().getItemCount(); dataUris = new Uri[numSelectedFiles]; for (int i = 0; i < numSelectedFiles; i++) { dataUris[i] = intent.getClipData().getItemAt(i).getUri(); } } } } } catch (Exception ignored) { } mFileUploadCallbackSecond.onReceiveValue(dataUris); mFileUploadCallbackSecond = null; } } } else { //这里mFileUploadCallbackFirst跟mFileUploadCallbackSecond在不同系统版本下分别持有了 //WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值 //否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效 if (mFileUploadCallbackFirst != null) { mFileUploadCallbackFirst.onReceiveValue(null); mFileUploadCallbackFirst = null; } else if (mFileUploadCallbackSecond != null) { mFileUploadCallbackSecond.onReceiveValue(null); mFileUploadCallbackSecond = null; } } } } } |
2.9、怎么为 WebView 的加载添加进度条
这里的 onPageFinished()
有个问题,不能在这里监听页面是否加载完毕(我自己测试的时候,好像在重定向和加载完 iframes 时都会调用这个方法)。
把页面加载完毕的判断放在 onProgressChanged()
里可能会更为准确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
webView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int position) { progressBar.setProgress(position); if (position == 100) { progressBar.setVisibility(View.GONE); } super.onProgressChanged(view, position); } }); webView.setWebViewClient(new WebViewClient(){ @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { progressBar.setVisibility(View.VISIBLE); super.onPageStarted(view, url, favicon); } }); |
2.10、怎样对页面进行 Js 注入?
首先你要在 WebView 开启 JavaScript,然后搭建桥梁
1 2 3 4 5 6 7 8 9 10 11 |
WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webView.addJavascriptInterface(new WebAppBridge(new WebAppBridge.OauthLoginImpl() { @Override public void getResult(String s) { //TODO } }), "oauth"); webView.loadUrl("javascript:" + getAssetsJs("autologin.js")); webView.loadUrl("javascript:adduplistener()"); |
WebAppBridge的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class WebAppBridge { private OauthLoginImpl oauthLogin; public WebAppBridge(OauthLoginImpl oauthLogin) { this.oauthLogin = oauthLogin; } @JavascriptInterface public void getResult(String str) { if (oauthLogin != null) oauthLogin.getResult(str); } public interface OauthLoginImpl { void getResult(String s); } } |
简单的说就是向网页注入一段 js, 在这段 js 里面设置回调到java中的方法 getResult()
,由 WebAppBridge.getResult 来回收。
其中js的核心代码为:
1 |
oauth.getResult(str); |
其中 oauth 这个名称要与 webView.addJavascriptInterface()
方法的第二个参数一样。
具体的代码可以参考这个项目中写的 js 注入逻辑 OauthDialog
地址:
2.11、如何手动添加 Cookie
需要获得 CookieManager 的对象并将 cookie 设置进去。
从服务器的返回头中取出 cookie 根据Http请求的客户端不同,获取 cookie 的方式也不同,请自行获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** * 将cookie设置到 WebView * @param url 要加载的 url * @param cookie 要同步的 cookie */ public static void syncCookie(String url,String cookie) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.createInstance(context); } CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); /** * cookie 设置形式 * cookieManager.setCookie(url, "key=value;" + "domain=[your domain];path=/;") **/ cookieManager.setCookie(url, cookie); } |
删除 Cookie 的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * 这个两个在 API level 21 被抛弃 * CookieManager.getInstance().removeSessionCookie(); * CookieManager.getInstance().removeAllCookie(); * * 推荐使用这两个, level 21 新加的 * CookieManager.getInstance().removeSessionCookies(); * CookieManager.getInstance().removeAllCookies(); **/ public static void removeCookies() { CookieManager cookieManager = CookieManager.getInstance(); cookieManager.removeAllCookie(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { cookieManager.flush(); } else { CookieSyncManager.createInstance(Application.getInstance()); CookieSyncManager.getInstance().sync(); } } |
2.12、如何使 HTML5 video 在 WebView 全屏显示
当网页全屏播放视频时会调用 WebChromeClient.onShowCustomView()
方法,所以可以通过将 video 播放的视图全屏达到目的。
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 |
@Override public void onShowCustomView(View view, CustomViewCallback callback) { if (view instanceof FrameLayout && fullScreenView != null) { // A video wants to be shown this.videoViewContainer = (FrameLayout) view; this.videoViewCallback = callback; fullScreenView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); fullScreenView.setVisibility(View.VISIBLE); isVideoFullscreen = true; } } @Override public void onHideCustomView() { if (isVideoFullscreen && fullScreenView != null) { // Hide the video view, remove it, and show the non-video view fullScreenView.setVisibility(View.INVISIBLE); fullScreenView.removeView(videoViewContainer); // Call back (only in API level <19, because in API level 19+ with chromium webview it crashes) if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium.")) { videoViewCallback.onCustomViewHidden(); } isVideoFullscreen = false; videoViewContainer = null; videoViewCallback = null; } } |
但是很多的手机版本在网页视频播放时是不会调用这个方法的,所以这个方法局限性很大。
另外还需要考虑全屏和非全屏时的屏幕方向:如果当前app是竖屏,全屏播放时需要设为屏幕方向为横屏;退出全屏播放时应该恢复为竖屏。如果应用本来就是横屏则可以不切换横竖屏
1 2 3 4 5 6 7 8 9 10 11 12 |
private void switchFullScreen() { //如果当前应用是横屏的就不需要做横竖屏切换了! if(mOriginalOrientation == Configuration.ORIENTATION_LANDSCAPE) { return; } if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } else { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } |
2.13、Android5.0上 WebView中Http和Https混合问题
1 2 3 4 5 6 7 8 |
/** * MIXED_CONTENT_ALWAYS_ALLOW:允许从任何来源加载内容,即使起源是不安全的; * MIXED_CONTENT_NEVER_ALLOW:不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源; * MIXED_CONTENT_COMPATIBILITY_MODE:当涉及到混合式内容时,WebView 会尝试去兼容最新Web浏览器的风格。 **/ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } |
2.14、如何避免 WebView 的内存泄露问题
- 可以将 Webview 的 Activity 新起一个进程,结束的时候直接System.exit(0);退出当前进程;
- 不在xml中定义 WebView,而是在代码中创建,使用 getApplicationgContext() 作为传递的 Conetext;
- 在 Activity 销毁的时候,将 WebView 置空
1 2 3 4 5 6 7 8 9 10 11 |
@Override protected void onDestroy() { if (webView != null) { webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webView.clearHistory(); ((ViewGroup) webView.getParent()).removeView(webView); webView.destroy(); webView = null; } super.onDestroy(); } |
方法二:
创建webview的时候把webview放在一个viewGroup里面,在destroy的时候从这个viewGroup里面移除,并通过调用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 30 |
public void destroyWebView() { // Make sure you remove the WebView from its parent view before doing anything. mWebContainer.removeAllViews(); mWebView.clearHistory(); // NOTE: clears RAM cache, if you pass true, it will also clear the disk cache. // Probably not a great idea to pass true if you have other WebViews still alive. mWebView.clearCache(true); // Loading a blank page is optional, but will ensure that the WebView isn't doing anything when you destroy it. mWebView.loadUrl("about:blank"); mWebView.onPause(); mWebView.removeAllViews(); mWebView.destroyDrawingCache(); // NOTE: This pauses JavaScript execution for ALL WebViews, // do not use if you have other WebViews still alive. // If you create another WebView after calling this, // make sure to call mWebView.resumeTimers(). mWebView.pauseTimers(); // NOTE: This can occasionally cause a segfault below API 17 (4.2) mWebView.destroy(); // Null out the reference so that you don't end up re-using it. mWebView = null; } |
2.15、如何用端接管webview状态
详细参考这篇文章:Android-webview 如何用端接管webview状态
问题背景:
- 我们用一个webview加载url时,在服务器响应远端数据之前,webview是空白的,我们期望这段时间显示一个端的loading
- webview加载出错后,页面展示不够友好,我们期望展示一个端的错误页面,包含“刷新”和“关闭”按钮
技术背景:
webview的生命周期对应以下几种方法:

可以看出webview默认没有我们想要的加载中、加载成功、加载失败的状态,如何接管这些状态呢?
webview提供了”开始加载”的起始点,我们想要拿到加载中,首先必须拿到“加载成功”和“加载失败”这两个状态,其中“加载失败”需要考虑url加载超时的场景。
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
/** * webview是否加载成功,默认为false */ private boolean mWebViewLoadSuccess = false; /** * webview是否记载错误,默认为false */ private boolean mWebViewLoadError = false; @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); //做对应的处理,弹出错误提示 onPageLoadError(false, failingUrl, errorCode, description); } @TargetApi(android.os.Build.VERSION_CODES.M) @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { super.onReceivedHttpError(view, request, errorResponse); String url = request.getUrl().toString(); int errCode = errorResponse.getStatusCode(); String errMsg = errorResponse.getReasonPhrase(); onPageLoadError(false, url, errCode, errMsg); } /** * webview onPageStart 方法回调 */ private void onPageLoadStart(WebView view, final String url, Bitmap favicon) { mWebViewLoadSuccess = false; mWebViewLoadError = false; } /** * webview onPageFinished 回调中调用此方法 */ private void onPageLoadFinish(WebView view, String url) { if(mWebViewStateCallback == null || !isValidUrl(url)) { return; } mWebViewStateCallback.onPageFinish(view, url); //设置url加载状态 if(!mWebViewLoadError) { mWebViewLoadSuccess = true; mWebViewStateCallback.onPageLoadSuccess(view, url); } mWebViewLoadError = false; } /** * 在webview 的 onReceivedError 和 onReceivedHttpError 回调方法中调用此方法,用于标记此次Url加载过程中出错 */ private void onPageLoadError(boolean isTimeout, String failingUrl, int errCode, String errMsg) { if(mWebViewStateCallback != null) { if(isTimeout) { mWebViewStateCallback.onTimeout(failingUrl); } else { mWebViewStateCallback.onLoadError(failingUrl, errCode, errMsg); } } //设置url加载状态 mWebViewLoadError = true; mWebViewLoadSuccess = false; } /** * webview onPageFinished 方法可能会回调多次,且url可能不是标准的url格式,需要过滤此种情况 */ private boolean isValidUrl(String url) { return !TextUtils.isEmpty(url) && Patterns.WEB_URL.matcher(url).matches(); } |
以上代码示例处理了webview加载成功和失败的状态的例子
2.16、页面适配手机分辨率,完整显示
- setLoadWithOverviewMode(true) 设置WebView是否以概览模式加载页面,即缩小内容以适应屏幕宽度。
- setUseWideViewPort(true) 如果为true则支持html里面的viewport标签,当设置的值为false时,不支持viewport标签,布局宽度始终设置为与设备无关(CSS)像素中的WebView控件的宽度。
想要支持viewport属性,并可以超屏自动缩放,可以如下配置:
1 2 3 |
WebSettings s = mWebView.getSettings(); s.setLoadWithOverviewMode(true); s.setUseWideViewPort(true); |
一般情况下前端会在html里面配置viewport为一个适配手机屏幕的viewport值:
1 2 3 4 5 |
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>例子</title> </head> |
2.17、获取webview的UA
2.17.1、UA的作用
1) 在手机端/pc端,可以通过UA来判断不同的设备,从而可以显示不同的排版,进而给用户提供更好的体验
例如:用手机访问百度和pc端访问的页面排版是不一样的,这些就是百度根据访问者的UA来判断的。
2) 通过UA,进行信息统计,主要是用于渠道统计
例如:在之前的工作中碰到过类似情况,在app中接入广告的sdk,当用户点击广告图片时就需要通过webview进行详细广告页面的跳转,这时候就需要在webview中进行UA的自定义设置了,这样后台广告平台的人员就可以通过UA判断是哪个客户端(安卓/苹果),哪个浏览器的客户,进而进行客户端的统计。
2.17.2、UA的结构
2.17.3、UA 获取
1 2 |
WebSettings settings = mWebView.getSettings(); String userAgentString = settings.getUserAgentString(); |
如果当前没有可用的webview实例,例如在app启动时就需要拿到webview的ua,此时我们是没有webview实例的,可以构建一个webview实例然后获取,例如:
1 2 3 4 5 6 7 8 |
public static String getUserAgent(Context ctx) { //api 19 之前 if(Build.VERSION.SDK_INT < 19){ return new WebView(ctx).getSettings().getUserAgentString(); } //api >=19 return WebSettings.getDefaultUserAgent(ctx); } |
注意:以上代码处理了4.4的兼容问题,在4.4版本及之后的版本如果直接在非主线程new webview会导致以下的崩溃:
1 2 3 4 5 6 7 8 9 10 11 12 |
java.lang.IllegalStateException: Calling View methods on another thread than the UI thread. at com.android.webview.chromium.WebViewChromium.createThreadException(WebViewChromium.java:67) at com.android.webview.chromium.WebViewChromium.checkThread(WebViewChromium.java:75) at com.android.webview.chromium.WebViewChromium.init(WebViewChromium.java:30) at android.webkit.WebView.<init>(WebView.java:636) at android.webkit.WebView.<init>(WebView.java:572) at android.webkit.WebView.<init>(WebView.java:555) at android.webkit.WebView.<init>(WebView.java:542) at android.webkit.WebView.<init>(WebView.java:532) at com.itlao5.app.getUser_agent(Utils.java:123) at com.itlao5.app.NetsUtils$1.doInBackground(NetsUtils.java:54) at com.itlao5.app.net.NetsAsyncTask$2.run(NetsAsyncTask.java:27) |
在4.4之后就不能在非主线程new Webview(ctx)了,不过官方文档中提供了处理方法,即使用WebSettings.getDefaultUserAgent(ctx):
If you need to retrieve the user agent but don’t need to store it for your app or do not want to instantiate WebView, you should use the static method, getDefaultUserAgent(). However, if you intend to override the user agent string in your WebView, you may instead want to use getUserAgentString().
原文地址:链接
2.17.4、自定义设置UA
1)在UA尾部增加字段
1 2 |
String ua = mWebView.getSettings().getUserAgentString();//原来获取的UA mWebView.getSettings().setUserAgentString(ua+ "自定义内容"); |
例如你可以增加这个字符串 “; platform/android; appversion/1.2.3”
2)替换 UA的值
1 2 |
String ua = mWebView.getSettings().getUserAgentString();//原来获取的UA mWebView.getSettings().setUserAgentString(ua.replace("Android","HFWSH_USER Android")); |
2.18、H5页面卡死问题
遇到过支付宝H5页面卡死,无法滑动、无法输入的情况。有两种可能导致这个问题:
- 调用了mWebView.pauseTimers()
- 开启了硬件加速
2.18.1、调用了mWebView.pauseTimers()
该方法面向全局整个应用程序的webview,它会暂停所有webview的layout,parsing,JavaScript Timer。当程序进入后台时,该方法的调用可以降低CPU功耗。需要检查代码里面是否正确的调用了resumeTimers().
2.18.2、开启了硬件加速
webview开启了硬件加速后会有一些奇怪的问题,关闭硬件加速可以解决此问题。如何开启和关闭,参考这篇文章:链接