一、Android WebView开发(一):基础应用
二、Android WebView开发(二):WebView与Native交互
三、Android WebView开发(三):WebView性能优化
四、Android WebView开发(四):WebView独立进程解决方案
五、Android WebView开发(五):自定义WebView工具栏

附GitHub源码:WebViewExplore


WebView性能优化方案:

1、WebView预初始化:

为了减少WebView的性能损耗,我们可以在合适时机提前创建好WebView,并存入缓存池,当页面需要显示内容时,直接从缓存池获取创建好的WebView,根据性能数据显示,WebView预创建可以减少首屏渲染时间200ms+。

以新闻落地页为例,当用户进入新闻列表页时,我们会创建第一个WebView,当用户进入新闻落地页后,会从缓存池中取出来渲染H5页面,为了不影响页面的加载速度,同时保证下次进入落地页缓存池中仍然有可用的WebView组件,我们会在每次页面加载完成(pageFinish)或者back退出落地页的时机,去触发预创建WebView的逻辑。

由于WebView的初始化需要和context进行绑定,若想实现预创建的逻辑,需要保证context的一致性,常规做法我们考虑可以用fragment来实现承载H5页面的容器,这样context可以用外层的activity实例,但Fragment本身的切换流畅度存在一定问题,并且这样做限定了WebView预创建适用的场景。为此,我们找到了一种更加完美的替代方案,即:MutableContextWrapper【新的context包装类,允许外部修改它的baseContext,并且所有ContextWrapper调用的方法都会代理到baseContext来执行】

下面是一段预创建WebView的代码:


/*** 创建WebView实例* 用了applicationContext*/@DebugTracepublic void prepareNewWebView() {if (mCachedWebViewStack.size() < CACHED_WEBVIEW_MAX_NUM) {mCachedWebViewStack.push(new WebView(new MutableContextWrapper(getAppContext())));}}/*** 从缓存池中获取合适的WebView* * @param context activity context* @return WebView*/private WebView acquireWebViewInternal(Context context) {// 为空,直接返回新实例if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {return new WebView(context);}WebView webView = mCachedWebViewStack.pop();// webView不为空,则开始使用预创建的WebView,并且替换ContextMutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();contextWrapper.setBaseContext(context);return webView;}

2、关联的Native组件懒加载:

WebView初始化完成,可以立刻loadUrl,无需等待框架onCreate或者OnResume结束。另外WebView初始完成后到页面首页绘制完成之间,尽量减少UI线程的其他操作,繁忙的UI线程会拖慢WebView.loadUrl的速度。

具体到我们新闻落地页场景,由于我们的落地页包含两部分,WebView+Native评论组件,正常流程会在WebView初始化结束后,开始评论组件的初始化及评论数据的获取。由于此时评论的初始化仍处在onCreate的UI消息处理中,会严重延迟内核加载主文档的逻辑。考虑到用户进入落地页的时候,评论组件对用户来说并不可见,所以将评论组件的初始化延迟到页面的pageFinish时或首屏渲染完成时。

3、离线缓存:

WebSettings settings = webView.getSettings();
settings.setAppCacheEnabled(true);
settings.setDatabaseEnabled(true);
settings.setDomStorageEnabled(true);//开启DOM缓存,关闭的话H5自身的一些操作是无效的
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
settings.setJavaScriptEnabled(true);

这边我们通过setCacheMode方法来设置WebView的缓存策略,WebSettings.LOAD_DEFAULT是默认的缓存策略,它在缓存可获取并且没有过期的情况下加载缓存,否则通过网络获取资源。这样的话可以减少页面的网络请求次数,那我们如何在离线的情况下也能打开页面呢,这里我们在加载页面的时候可以通过判断网络状态,在无网络的情况下更改webview的缓存策略

ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if(info.isAvailable())
{settings.setCacheMode(WebSettings.LOAD_DEFAULT);
}else
{settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);//不使用网络,只加载缓存
}

这样我们就可以使我们的混合应用在没有网络的情况下也能使用一部分的功能,不至于什么都显示不了了,当然如果我们将缓存做的更好一些,在网络好的时候,比如说在WIFI状态下,去后台加载一些网页缓存起来,这样处理的话,即使在无网络情况下第一次打开某些页面的时候,也能将该页面显示出来。
当然缓存资源后随之会带来一个问题,那就是资源无法及时更新,WebSettings.LOAD_DEFAULT中的页面中的缓存版本好像不是很起作用,所以我们这边可能需要自己做一个缓存版本控制。这个缓存版本控制可以放在APP版本更新中。

if (upgrade.cacheControl > cacheControl)
{webView.clearCache(true);//删除DOM缓存VersionUtils.clearCache(mContext.getCacheDir());//删除APP缓存try{mContext.deleteDatabase("webview.db");//删除数据库缓存mContext.deleteDatabase("webviewCache.db");}catch (Exception e){}
}

4、资源预(异步)加载:

1、通过shouldInterceptRequest(预)异步加载:

有时候一个页面资源比较多,图片,CSS,js比较多,还引用了JQuery这种庞然巨兽,从加载到页面渲染完成需要比较长的时间,解决方案就是可以按照一定的策略和时机,提前从CDN中请求部分落地页html缓存到本地,或者可以把html,css,js,image等资源预置在客户端本地,并和服务端协商好前端的版本控制和增量更新策略,如此一来Webview就可以先快速加载本地缓存页面资源,加载完成后,返回给WebView。剩下的就只需要拉取那些需要更新的增量资源即可。这样可以提升加载速度也能减少服务器压力。重写WebClient类中的 shouldInterceptRequest 【运行在子线程】方法,再将这个类设置给WebView。

        /*** 【实现预加载】* 有时候一个页面资源比较多,图片,CSS,js比较多,还引用了JQuery这种庞然巨兽,* 从加载到页面渲染完成需要比较长的时间,有一个解决方案是将这些资源打包进APK里面,* 然后当页面加载这些资源的时候让它从本地获取,这样可以提升加载速度也能减少服务器压力。*/@Nullable@Overridepublic WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {if (request == null) {return null;}String url = request.getUrl().toString();Log.d(TAG, "shouldInterceptRequest---> " + url);return getWebResourceResponse(url);}protected WebResourceResponse getWebResourceResponse(String url) {//此处[tag]等需要跟服务端协商好,再处理if (url.contains("[tag]")) {try {String localPath = url.replaceFirst("^http.*[tag]\\]", "");InputStream is = getContext().getAssets().open(localPath);Log.d(TAG, "shouldInterceptRequest: localPath " + localPath);String mimeType = "text/javascript";if (localPath.endsWith("css")) {mimeType = "text/css";}return new WebResourceResponse(mimeType, "UTF-8", is);} catch (IOException e) {e.printStackTrace();return null;}} else {return null;}}

以常用的图片加载来说,此方案在满足图片渲染速度的同时,解耦了客户端和前端代码,客户端充当server角色,对图片进行请求和缓存控制,保证前端和客户端可以共用图片缓存。

2、H5页面拉取优化:

而针对这些需要拉取的增量资源,可以对它们进行webpack+gzip数据压缩和CDN加速处理,以提升拉取速度。并且在建立网络连接时,可以让前端请求的域名和客户端API接口域名一致,以减少DNS解析时间。
最后,对于H5页面来说,图片资源的拉取是最为耗时的,一个比较好的解决方案就是先加载并展示非图片内容,延迟这些图片的加载,以提升用户体验。WebView有一个setBlockNetworkImage(boolean)方法,该方法的作用是是否屏蔽图片的加载。可以利用这个方法来实现图片的延迟加载:在onPageStarted时屏蔽图片加载,在onPageFinished时开启图片加载。

5、内存泄露:

首先针对WebView内存泄漏的问题,可用AS的Profiler来进行检测:

Android 如何用profiler检查内存泄漏【这里需要注意一点的是,点击垃圾回收按钮,手动gc时,需要多点几次,要不然不一定会进行正常的垃圾回收】。

另外之前自己曾多次尝试检测WebView内存泄漏,但是"遗憾"是发现并不会发生内存泄漏。试了下多台手机都没有发生。后来查了下相关文章【Android-WebView还会存在内存泄漏吗?】,了解到WebView内存泄漏其实只是早期内核版本的一个漏洞,现早已修复。所以包括5.0以上的大部分设备不会产生所谓的内存泄漏,低版本 Android 系统(Android 5以上)上搭载的 Chromium 内核一般来说也不会太旧,所以出现内存泄漏的概率应该是比较小的。如果仍需要兼容这很小的一部分机型,可以采用如下的这种两种方式:

1、避免在xml布局文件中直接嵌套webview控件,而是采用addview的方式new一个webview并加载到布局中,上下文变量使用applicationContext:

webView = new WebView(getApplicationContext());
webView.getSettings().setJavaScriptEnabled(true);
framelayout.addView(webView);
webView.loadUrl(url);

2、当activity生命周期结束时及时销毁/释放资源:

webview引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。

org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;系统会在attach和detach处进行注册和反注册component callback;
在onDetachedFromWindow() 方法的第一行中:

if (isDestroyed()) return;, 

如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作;我们的activity退出的时候,都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;destroy()的执行时间又在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。
然后解决方法就是:让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉。

ViewParent parent = mWebView.getParent();
if (parent != null) {((ViewGroup) parent).removeView(mWebView);
}mWebView.destroy();

完整的activity的onDestroy()方法:如:

    @Overrideprotected void onDestroy() {if (mWebView != null) {mWebView.destroy();ViewParent parent = mWebView.getParent();if (parent != null) {((ViewGroup) parent).removeView(mWebView);}mWebView.stopLoading();// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错mWebView.getSettings().setJavaScriptEnabled(false);mWebView.clearHistory();mWebView.clearView();mWebView.removeAllViews();mWebView.destroy();}super.onDestroy();}

3、WebView独立进程解决方案:

解决WebView的内存泄漏及OOM的另一种解决方案是通过开辟独立进程来实现,具体可参考:

WebView独立进程解决方案

参考:

WebView内存泄漏--解决方法小结 - 简书

今日头条品质优化 - 图文详情页秒开实践

WebView性能、体验分析与优化 - 美团技术团队

百度APP-Android H5首屏优化实践

Android WebView开发(三):WebView性能优化相关推荐

  1. WebView开发(三):WebView性能优化

    一.Android WebView开发(一):基础应用 二.Android WebView开发(二):WebView与Native交互 三.Android WebView开发(三):WebView性能 ...

  2. Android MediaScanner MediaProvider流程以及性能优化,音视频扫描

    Android MediaScanner MediaProvider流程以及性能优化,音视频扫描 快速扫描 一.源码解析 github链接 MediaScanner时序图 MediaSacannerR ...

  3. 【性能优化方法论系列】三、性能优化的核心思想(3)

    性能优化方法论系列目录 <一.性能优化的本质> <二.性能优化方法论的思想源泉> <三.性能优化的核心思想(1)> <三.性能优化的核心思想(2)> & ...

  4. Android NDK开发(三)——常见错误集锦以及LOG使用,androidndk

    Android NDK开发(三)--常见错误集锦以及LOG使用,androidndk 转载请注明出处:http://blog.csdn.net/allen315410/article/details/ ...

  5. 【性能优化方法论系列】三、性能优化的核心思想(1)

    性能优化方法论系列目录 <一.性能优化的本质> <二.性能优化方法论的思想源泉> <三.性能优化的核心思想(1)> <三.性能优化的核心思想(2)> & ...

  6. Android手机内存管理与性能优化

    Android手机内存管理与性能优化&JNI.NDK高级编程(JNI.Dalvik.内存监测) 课程分类:Android 适合人群:中级 课时数量:34小节课时 用到技术:Dalvik,DDM ...

  7. Android 蓝牙开发(三) -- 低功耗蓝牙开发

    Android 蓝牙开发(一) – 传统蓝牙聊天室 Android 蓝牙开发(三) – 低功耗蓝牙开发 项目工程BluetoothDemo 前面已经学习了经典蓝牙开发,学习了蓝牙的配对连接和通信,又通 ...

  8. webview加载的页面和浏览器渲染的页面不一致_QQ音乐Android客户端Web页面通用性能优化实践...

    QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化.本文将介绍 QQ 音乐 Android 客户端在进行 Web ...

  9. Android开发中的性能优化(摘录:陈彧堃演讲实录)

    大家好!我是陈彧堃,其实HTML5我之前也做过一些相关的研究,包括现在我们也在做一些调研.我个人一年前在G3上利用HTML5开发一些东西的时候,我发现原生的对HTML不支持,开始踏踏实实做Androi ...

  10. 【安卓开发 】Android初级开发(八)WebView网页

    1.网页的基本组成 2.WebView的常用方法 WebView webView = findViewById(R.id.webvv);//加载线上网页webView.loadUrl("ht ...

最新文章

  1. Xamarin.Forms教程下载安装Visual Studio 2015
  2. 服务器装系统无显示屏,服务器系统安装后没有桌面
  3. C语言与C++ 中bool关键字的矛盾解决
  4. 计算机一级汉字录入在线联系,计算机一级考试指导:汉字录入题的操作
  5. java程序设计基础29_java程序设计基础实验29
  6. MFC CListCtrl
  7. SQL-ALTER-change和modify区别
  8. 四轴码垛机器人配件_四轴码垛机器人的应用范围
  9. 记录——《C Primer Plus (第五版)》第九章编程练习第六题
  10. 超过10%的 Firebase 数据库易受攻击并暴露数据
  11. 2、ES5的严格模式use strict
  12. 【python】拉普拉斯和sobel对图像处理
  13. c#ftp操作全解:创建删除目录,上传下载文件,删除移动文件,文件改名,文件目录查询
  14. [Linux] PHP程序员玩转Linux系列-自动备份与SVN
  15. redhat7挂载光盘
  16. 财务人员必备的5个Excel技能,学会工资高出同事3倍!
  17. 使用ember-simple-auth实现Ember.js应用的权限控制
  18. UE4C++吃豆子游戏
  19. halcon 条形码识别(持续更新)
  20. Delphi10.3.3 部署android 开发环境

热门文章

  1. 学习笔记-网络安全(二)
  2. esp8266WIFI模块教程:正点原子ATK-ESP8266进行网络通信,单片机与电脑,单片机与手机发送数据
  3. Java静态代理和动态代理(代理模式)详解
  4. [硬件选型] 工业相机之参数和选型
  5. linux mingw32安装,在Linux上安装mingw
  6. VS2022开发Arduino(提供Visual.Micro.Processing.Sketch.dll)
  7. mysql更改数据库密码
  8. 低代码开发平台的行业前景
  9. Flash Builder 4 正式版破解注册方法(flex4)
  10. 字体文件夹在哪?xp\win7\win8\win10系统字体文件夹位置