使用WebView监控网页加载状况,PerformanceMonitor,WebViewClient生命周期
来源:互联网 发布:数据库可以存图片吗 编辑:程序博客网 时间:2024/06/03 13:18
原理:WebView加载Url完成后,注入js脚本,脚本代码使用W3C的PerformanceTimingAPI,
往js脚本传入一个Android对象(代码中为AndroidObject),在js脚本中调用AndroidObject中的接口,以此方式将结果传回到Android代码中。
可获取的信息:
坑(注意):
1、WebViewClent的onPageFinished()方法在不同的机型下会有不同的回调情况,在所测机型中魅族Pro6只会在全部网页资源加载完成以及 webView.getProgress()==100 的情况下才会回调,且只会回调一次,华为Mate7则会在所有加载网页资源尚未加载完成( webView.getProgress()<100 )时就会回调,而且即使 webView.getProgress()==100 的情况下也会回调多次,时间难以把握,此时需自己另外加判断,只有在 webView.getProgress()==100 的情况下才执行脚本,且只能执行一次。造成此种情况的原因为WebViewClent底层JNI对不同浏览器内核的适应情况较差,WebChromeClient的onProgressChanged()方法不会出现这种情况。
2、注入js脚本之后需要延时一段时间才能销毁WebView,否则将收不到js返回来的结果,不能在onPageFinished()中(主线程)做耗时操作,需要另外开启一个线程去做延时关闭,然后通过消息机制将销毁WebView的msg发送给Handler处理,在主线中才能销毁WebView。
3、在初始化WebView时是通过 WebView mWebView = new WebView(mContext); 方式,所以在销毁WebView时出现了坑,一开始是使用如下方式进行WebView销毁,但是发现run()方法不被调用,但是hasEnqueue却返回的true,查看文档发现即使在enqueue的情况下该Runnable也并不一定会调用,最后使用 mHandler =new Handler(Lopper.getMainLooper()){...} 方法解决了问题。
1 boolean hasEnqueue = mWebView.postDelayed(new Runnable() { 2 @Override 3 public void run() { 4 //didn't step into here 5 if (mWebView != null) { 6 mWebView.clearCache(true); 7 mWebView.clearHistory(); 8 mWebView.destroy(); 9 mWebView = null;10 }11 }12 }, 500);13 if(hasEnqueue){14 Logger.d("the Runnable was successfully placed in to the message queue.");15 }else{16 Logger.d("the Runnable was failed to be placed in to the message queue.");17 }
Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DESTROY: { destroyWebView(); break; } default: super.handleMessage(msg); } } };
执行结果:
可通过修改注入的js脚本获取更多详细信息。
代码:
1 final MyWebView mWebView = new MyWebView(mContext); 2 mWebView.setVisibility(View.GONE); 3 4 WebSettings setting = mWebView.getSettings(); 5 setting.setJavaScriptEnabled(true); 6 setting.setCacheMode(WebSettings.LOAD_NO_CACHE); 7 setting.setLoadsImagesAutomatically(false); 8 9 MyWebViewClient myWebViewClient = new MyWebViewClient();10 myWebViewClient.setTimeOut(timingCheck.getTimeout());11 mWebView.setWebViewClient(myWebViewClient);12 13 mWebView.setAndroidObject(new AndroidObject() {14 @Override15 public void handleError(String msg) {16 Logger.d("AndroidObject,错误信息:" + msg);17 }18 19 @Override20 public void handleResource(String jsonStr) {21 Logger.d("AndroidObject,Timing信息:" + jsonStr);22 }23 });24 mWebView.loadUrl("http:www.qq.com/");
1 import android.graphics.Bitmap; 2 import android.net.http.SslError; 3 import android.os.Handler; 4 import android.os.Looper; 5 import android.os.Message; 6 import android.webkit.SslErrorHandler; 7 import android.webkit.WebResourceError; 8 import android.webkit.WebResourceRequest; 9 import android.webkit.WebResourceResponse; 10 import android.webkit.WebView; 11 import android.webkit.WebViewClient; 12 13 import com.gomo.health.plugin.plugin.Constants; 14 import com.gomo.health.plugin.utils.Logger; 15 16 import java.util.Timer; 17 import java.util.TimerTask; 18 import java.util.concurrent.atomic.AtomicBoolean; 19 20 21 /** 22 * Created by s_x_q on 2017/4/11. 23 */ 24 public class MyWebViewClient extends WebViewClient { 25 26 private WebView mWebView; 27 private AndroidObject mAndroidObject; 28 29 /** 30 * WebView不支持修改Timeout , 这里自定义 31 */ 32 private int mTimeOut = 3000; 33 private int mJsTimeout = 500; 34 35 private Timer mTimer = new Timer(); 36 37 /** 38 * 避免重复执行mWebsiteLoadTimeoutTask 39 */ 40 private boolean isWebTimeoutTaskScheduling = false; 41 42 /** 43 * 避免重复执行mJsInjectTimeoutTask 44 */ 45 private boolean isJsTimeoutTaskScheduling = false; 46 47 /** 48 * 判断网页加载是否完成 49 */ 50 private AtomicBoolean isWebLoadFinished = new AtomicBoolean(false); 51 52 private TimerTask mWebsiteLoadTimeoutTask = new TimerTask() { 53 @Override 54 public void run() { 55 if (mWebView != null && !isWebLoadFinished.get()) { 56 sendWebsiteLoadTimeoutMsg(); 57 } 58 } 59 }; 60 61 private TimerTask mJsInjectTimeoutTask = new TimerTask() { 62 @Override 63 public void run() { 64 if (mWebView != null && mAndroidObject != null) { 65 if (!mAndroidObject.isDataReturn()) { 66 sendJsInjectTimeoutMsg(); 67 } else { 68 sendDestroyMsg(); 69 } 70 } 71 } 72 }; 73 74 final Handler handler = new Handler(Looper.getMainLooper()) { 75 @Override 76 public void handleMessage(Message msg) { 77 switch (msg.what) { 78 case Constants.HandlerMessage.MSG_DESTROY: { 79 destroyWebView(); 80 break; 81 } 82 case Constants.HandlerMessage.MSG_WEBSITE_LOAD_TIMEOUT: { 83 if (mWebView != null) { 84 Logger.d("网页加载超时 , WebView进度:" + mWebView.getProgress() + " , url:" + mWebView.getUrl()); 85 if (mWebView.getProgress() < 100) { 86 mAndroidObject.handleError("LoadUrlTimeout"); 87 destroyWebView(); 88 } 89 } 90 break; 91 } 92 case Constants.HandlerMessage.MSG_JS_INJECT_TIMEOUT: { 93 if (mWebView != null) { 94 if (mAndroidObject != null) { 95 if (!mAndroidObject.isDataReturn()) { 96 Logger.d("JS注入脚本执行超时"); 97 String format = "ExecuteJsTimeout(%dms)"; 98 mAndroidObject.handleError(String.format(format, mJsTimeout)); 99 destroyWebView();100 }101 }102 }103 break;104 }105 106 default:107 super.handleMessage(msg);108 }109 }110 };111 112 @Override113 public void onPageStarted(WebView view, String url, Bitmap favicon) {114 super.onPageStarted(view, url, favicon);115 Logger.d("网页开始加载:" + url);116 117 if (mWebView == null) {118 mWebView = view;119 if (mWebView instanceof MyWebView) {120 mAndroidObject = ((MyWebView) mWebView).getAndroidObject();121 }122 }123 setupWebLoadTimeout();124 }125 126 @Override127 public boolean shouldOverrideUrlLoading(WebView view, String url) {128 //只会重定向时回调,然后回调onPageStarted129 // Logger.d("回调旧版shouldOverrideUrlLoading , url :" + url);130 return super.shouldOverrideUrlLoading(view, url);131 }132 133 @Override134 public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {135 //只会重定向时回调,然后回调onPageStarted136 // Logger.d("回调新版shouldOverrideUrlLoading , request method :" + request.getMethod() + "\t是否为重定向: " + request.isRedirect() + "\trequest url :" + request.getUrl());137 return super.shouldOverrideUrlLoading(view, request);138 }139 140 @Override141 public WebResourceResponse shouldInterceptRequest(WebView view, String url) {142 //每次请求资源的时候都会在onLoadResource前回调,可用于拦截资源加载,修改request143 // Logger.d("回调旧版shouldInterceptRequest , url :" + url);144 return super.shouldInterceptRequest(view, url);145 }146 147 @Override148 public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {149 //每次请求资源的时候都会在onLoadResource前回调,可用于拦截资源加载,修改request150 // Logger.d("回调新版shouldInterceptRequest " );151 return super.shouldInterceptRequest(view, request);152 }153 154 @Override155 public void onLoadResource(WebView view, String url) {156 super.onLoadResource(view, url);157 // Logger.d("加载网页资源 , url:" + url + " , WebView进度:" + view.getProgress());158 }159 160 public void onPageFinished(WebView view, String url) {161 super.onPageFinished(view, url);162 163 Logger.d("网页加载完成,WebView进度:" + view.getProgress());164 165 166 //可能会在进度<100或==100的情况下出现多次onPageFinished回调167 if (view.getProgress() == 100 && !isWebLoadFinished.get()) {168 Logger.d("注入js脚本");169 //可能会回调多次170 isWebLoadFinished.set(true);171 String format = "javascript:%s.sendResource(JSON.stringify(window.performance.timing));";172 String injectJs = String.format(format, MyWebView.ANDROID_OBJECT_NAME);173 view.loadUrl(injectJs);174 175 setupJsInjectTimeout();176 }177 }178 179 @Deprecated180 @Override181 public void onReceivedError(WebView view, int errorCode,182 String description, String failingUrl) {183 super.onReceivedError(view, errorCode, description, failingUrl);184 Logger.d("回调旧版本onReceivedError():" + "错误描述:" + description + "\t错误代码:" + errorCode + "失败的Url:" + failingUrl);185 186 handleError(description);187 }188 189 @Override190 public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {191 super.onReceivedError(view, request, error);192 Logger.d("回调新版本onReceivedError:");193 }194 195 @Override196 public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {197 super.onReceivedHttpError(view, request, errorResponse);198 Logger.d("回调onRecivedHttpError:");199 handleError("onReceivedHttpError");200 }201 202 @Override203 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {204 super.onReceivedSslError(view, handler, error);205 Logger.d("回调onReceivedSslError():" + "\terror:" + error.toString());206 handleError("onReceivedSslError");207 }208 209 private void handleError(String msg) {210 isWebLoadFinished.set(true);211 sendDestroyMsg();212 if (mAndroidObject != null) {213 mAndroidObject.handleError(msg);214 }215 216 }217 218 219 public int getTimeOut() {220 return mTimeOut;221 }222 223 public void setTimeOut(int timeOut) {224 mTimeOut = timeOut;225 }226 227 /**228 * 网页加载计时229 */230 private void setupWebLoadTimeout() {231 if (!isWebTimeoutTaskScheduling) {232 isWebTimeoutTaskScheduling = true;233 mTimer.schedule(mWebsiteLoadTimeoutTask, mTimeOut);234 }235 }236 237 /**238 * 注入js脚本执行计时239 * <p>240 * 注入js之后等待一段时间,如果这段时间内js不回调AndroidObject.handleResource(),则再销毁WebView241 * 过早销毁WebView,js不回调AndroidObject.handleResource()242 */243 private void setupJsInjectTimeout() {244 if (!isJsTimeoutTaskScheduling) {245 isJsTimeoutTaskScheduling = true;246 if (mAndroidObject != null) {247 mAndroidObject.setStartTime(System.currentTimeMillis());248 }249 mTimer.schedule(mJsInjectTimeoutTask, mJsTimeout);250 }251 }252 253 private void sendDestroyMsg() {254 handler.sendEmptyMessage(Constants.HandlerMessage.MSG_DESTROY);255 }256 257 private void sendWebsiteLoadTimeoutMsg() {258 handler.sendEmptyMessage(Constants.HandlerMessage.MSG_WEBSITE_LOAD_TIMEOUT);259 }260 261 private void sendJsInjectTimeoutMsg() {262 handler.sendEmptyMessage(Constants.HandlerMessage.MSG_JS_INJECT_TIMEOUT);263 }264 265 private void destroyWebView() {266 if (mWebView != null) {267 mWebView.clearCache(true);268 mWebView.clearHistory();269 mWebView.destroy();270 mWebView = null;271 Logger.d("成功销毁WebView");272 } else {273 Logger.d("销毁失败,WebView为空");274 }275 }276 277 }
1 import android.webkit.JavascriptInterface; 2 3 4 /** 5 * Created by s_x_q on 2017/4/11. 6 */ 7 8 public abstract class AndroidObject { 9 10 11 private volatile boolean mIsDataReturn = false ;12 private long startTime ;13 private long endTime ;14 15 /**16 *用于收集Timing信息17 *18 * @param jsonStr19 */20 @JavascriptInterface21 public void sendResource(String jsonStr) {22 mIsDataReturn = true ;23 endTime = System.currentTimeMillis();24 Logger.d("js成功执行时间:" + (endTime-startTime));25 handleResource(jsonStr);26 }27 28 29 /**30 * 用于收集js的执行错误31 * @param msg32 */33 @JavascriptInterface34 public void sendError(String msg) {35 handleError(msg);36 }37 38 39 /**40 * 处理错误信息,可能会被回调多次41 * @param msg42 */43 public abstract void handleError(String msg) ;44 45 /**46 *47 * @param jsonStr48 */49 public abstract void handleResource(String jsonStr);50 51 public boolean isDataReturn() {52 return mIsDataReturn;53 }54 55 public long getStartTime() {56 return startTime;57 }58 59 public void setStartTime(long startTime) {60 this.startTime = startTime;61 }62 63 public long getEndTime() {64 return endTime;65 }66 67 public void setEndTime(long endTime) {68 this.endTime = endTime;69 }70 }
拓展:
Performance Timing API :
W3C:A Primer for Web Performance Timing APIs
Google:测量资源加载时间
Google:了解资源耗时
W3C:Resource Timing Level 3
WebView开发:
WebView开发之坑
WebView拦截过滤Url:
GoogleChrome高级WebView应用实例,官方Chromium WebView Sample
Android 拦截WebView加载URL,控制其加载CSS、JS资源,WebView缓存
Webview资源请求的拦截
Android中WebView中拦截所有请求并替换URL
- 使用WebView监控网页加载状况,PerformanceMonitor,WebViewClient生命周期
- 使用WebView, WebChromeClient和WebViewClient加载网页
- 使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- WebView的基本使用方法及 WebViewClient加载网页使用
- Android使用WebView, WebChromeClient和WebViewClient加载网页
- android:使用WebView, WebChromeClient和WebViewClient加载网页
- Android中使用WebView, WebChromeClient和WebViewClient加载网页
- android WebView, WebChromeClient和WebViewClient加载网页基本用法
- WebForm使用ajax
- MyISAM与InnoDB区别
- Cmake中的find_package功能
- Redis 配置
- npm 常用指令
- 使用WebView监控网页加载状况,PerformanceMonitor,WebViewClient生命周期
- php配置文件修改注意事项
- vue2中子组件修改父组件传入的prop,并向父组件$emit一个广播事件
- VB6编程中文件字符编码的简单转换,UTT-8和ANSI以及其它字符集互转
- Go学习笔记(五)变量,常量,运算符,条件语句,循环语句
- Unity3D之Mecanim动画系统学习笔记(一):认识Mecanim动画系统
- [ArmCompiler6--armlink]armlink使用介绍
- camel 支持htts接入概述
- js 邮箱校验