Android WebView开发:WebView性能优化

Posted 红日666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android WebView开发:WebView性能优化相关的知识,希望对你有一定的参考价值。

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




附GitHub源码:WebViewExplore


WebView性能优化方案:

1、离线缓存:

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)
    
    

2、预加载:

1、通过 shouldInterceptRequest 方法实现资源的预加载:

有时候一个页面资源比较多,图片,CSS,js比较多,还引用了JQuery这种庞然巨兽,从加载到页面渲染完成需要比较长的时间,有一个解决方案是将这些资源打包进APK里面,然后当页面加载这些资源的时候让它从本地获取,这样可以提升加载速度也能减少服务器压力。重写WebClient类中的 shouldInterceptRequest 方法,再将这个类设置给WebView。

        /**
         * 【实现预加载】
         * 有时候一个页面资源比较多,图片,CSS,js比较多,还引用了JQuery这种庞然巨兽,
         * 从加载到页面渲染完成需要比较长的时间,有一个解决方案是将这些资源打包进APK里面,
         * 然后当页面加载这些资源的时候让它从本地获取,这样可以提升加载速度也能减少服务器压力。
         */
        @Nullable
        @Override
        public 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、智能预取- 提前化网络请求:

我们可以按照一定的策略和时机,提前从CDN中请求部分落地页html,缓存到本地,这样当用户点击查看新闻时,只需从缓存中加载即可。

根据业务场景的不同,触发时机可以自定义,也可以遵循默认的刷新、滑停、点击等时机,此外,可以对预取内容进行优先级排序(根据资源类型、触发时机),会动态的根据当前手机状态信息进行并发控制和流量控制,在一些降级场景中,server还可以通过云控的方式来控制是否预取以及预取的数量。

3、H5页面拉取优化:

1、资源拉取优先级:

有了Webview,就要开始拉取H5页面了。针对这一步,我们可以把html,css,js,image等资源预置在客户端本地,并和服务端协商好前端的版本控制和增量更新策略,如此一来Webview就可以先快速加载本地缓存页面资源,剩下的就只需要拉取那些需要更新的增量资源即可。
而针对这些需要拉取的增量资源,可以对它们进行webpack+gzip数据压缩和CDN加速处理,以提升拉取速度。并且在建立网络连接时,可以让前端请求的域名和客户端API接口域名一致,以减少DNS解析时间。
最后,对于H5页面来说,图片资源的拉取是最为耗时的,一个比较好的解决方案就是先加载并展示非图片内容,延迟这些图片的加载,以提升用户体验。
WebView有一个setBlockNetworkImage(boolean)方法,该方法的作用是是否屏蔽图片的加载。可以利用这个方法来实现图片的延迟加载:在onPageStarted时屏蔽图片加载,在onPageFinished时开启图片加载。

2、H5动态数据拉取并行:

正常的顺序是在html,css,js拉取下来之后,才开始由js发起前端的ajax请求,获取到数据后才开始进行填充。
其实我们可以把前端的ajax请求提前到和页面加载同时进行,由客户端请求数据,等到H5加载完毕,直接向客户端索要即可,如此一来,便缩短了总体的页面加载时间。

4、内存泄露:

首先针对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()方法:如:

    @Override
    protected 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性能优化的主要内容,如果未能解决你的问题,请参考以下文章

Android WebView开发:WebView性能优化

Android WebView开发:WebView性能优化

Android WebView开发:自定义WebView工具框

Android WebView开发:WebView与Native交互

Android WebView开发:WebView与Native交互

Android WebView开发:自定义WebView工具框