Android WebView加载优化

待兔 等级 703 0 0
标签: webview移动端

1.前言

最近几年关于原生WebView与H5混合开发的项目越来越多,这种开发带来了很多便利,但也会有一些缺点,比如说通过WebView加载H5会有一定的卡顿现象,会影响用户体验。下面本文就此问题一一展开讨论。

2. 场景

根据日常需求一般是通过webView.loadUrl()方法加载指定的网页,其大概流程如下:

Android WebView加载优化

image.png

  • 创建WebView:通过Java代码创建WebView,设置相关的参数和属性
  • 发起请求:通过底层内核会先检查缓存,然后决定是否创建对应的请求,建立对应的http请求。
  • 页面解析:下载对应的DOM树和相关资源。
  • 内容处理:根据上面的资源和树形结构,进行渲染展示。
    以上是整个网页请求的大概过程,优化也是在这几个过程中分别作出细致对应的方案。

2.1 创建WebView

WebView创建的时机会对整个流程是有影响的,Webview的启动相对来说比较耗时,因此这个时候我们可以采用提前启动Webview,这样就可以在加载网页的时候已经准备好。比如我在做Browser的时候发现有Native页面点击网页icon,启动相对来说比较慢,那么采用的策略就是进入Native页面后,一段时间后提前创建Webview。一开始本以为这样就可以,但是浏览器在tab页面可以新建多个网页加载器,也就是会有多个webview。显然光是提前创建也不太好,后面采用创建一个WebView的池子来管理这些Webview

/**
 * webview 复用池
 */
public class WebViewPool {
    private static final String DEMO_URL = "https://www.baidu.com";
    private static final String APP_CACAHE_DIRNAME = "webCache";
    private static List<WebView> available = new ArrayList<>();
    private static List<WebView> inUse = new ArrayList<>();
    private static final byte[] lock = new byte[]{};
    private static int maxSize = 2;
    private int currentSize = 0;
    private static long startTimes = 0;
    private static volatile WebViewPool instance = null;

    public static WebViewPool getInstance() {
        if (instance == null) {
            synchronized (WebViewPool.class) {
                if (instance == null) {
                    instance = new WebViewPool();
                }
            }
        }
        return instance;
    }

    /**
     * Webview 初始化
     * 最好放在application oncreate里
     */
    public static void init(Context context) {
        for (int i = 0; i < maxSize; i++) {
            WebView webView = new WebView(context);
            initWebSeting(context, webView);
            //webView.loadUrl(DEMO_URL);
            available.add(webView);
        }
    }

    /**
     * 获取webview
     */
    public WebView getWebView(Context context) {
        synchronized (lock) {
            WebView webView;
            if (available.size() > 0) {
                webView = available.get(0);
                available.remove(0);
                currentSize++;
                inUse.add(webView);
            } else {
                webView = new WebView(context);
                initWebSeting(context, webView);
                inUse.add(webView);
                currentSize++;
            }
            return webView;
        }
    }

    /**
     * 回收webview ,不解绑
     *
     * @param webView 需要被回收的webview
     */
    public void removeWebView(WebView webView) {
        webView.loadUrl("");
        webView.stopLoading();
        webView.setWebChromeClient(null);
        webView.setWebViewClient(null);
        webView.clearCache(true);
        webView.clearHistory();
        synchronized (lock) {
            inUse.remove(webView);
            if (available.size() < maxSize) {
                available.add(webView);
            } else {
                webView = null;
            }
            currentSize--;
        }
    }

    /**
     * 回收webview ,解绑
     *
     * @param webView 需要被回收的webview
     */
    public void removeWebView(ViewGroup viewGroup, WebView webView) {
        viewGroup.removeView(webView);
        webView.loadUrl("");
        webView.stopLoading();
        webView.setWebChromeClient(null);
        webView.setWebViewClient(null);
        webView.clearCache(true);
        webView.clearHistory();
        synchronized (lock) {
            inUse.remove(webView);
            if (available.size() < maxSize) {
                available.add(webView);
            } else {
                webView = null;
            }
            currentSize--;
        }
    }

    /**
     * 设置webview池个数
     *
     * @param size webview池个数
     */
    public void setMaxPoolSize(int size) {
        synchronized (lock) {
            maxSize = size;
        }
    }

    private static void initWebSeting(Context context, WebView webView) {
        ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        webView.setLayoutParams(params);
        WebSettings webSettings = webView.getSettings();
        //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
        webSettings.setJavaScriptEnabled(true);
        // 若加载的 html 里有JS 在执行动画等操作,会造成资源浪费(CPU、电量)
        // 在 onStop 和 onResume 里分别把 setJavaScriptEnabled() 给设置成 false 和 true 即可

        //支持插件
        //        webSettings.setPluginsEnabled(true);

        //设置自适应屏幕,两者合用
        webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
        webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小

        //缩放操作
        webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。
        webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放
        webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件

        //其他细节操作
        webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); //关闭webview中缓存
        webSettings.setAllowFileAccess(true); //设置可以访问文件
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
        webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
        webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式

        webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能
        webSettings.setDatabaseEnabled(true);   //开启 database storage API 功能
        webSettings.setAppCacheEnabled(true);//开启 Application Caches 功能

        String cacheDirPath = context.getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
        webSettings.setAppCachePath(cacheDirPath); //设置  Application Caches 缓存目录

        //页面白屏问题
        webView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent));
        webView.setBackgroundResource(R.color.white);
    }
} 

需要Webview的时候直接在此pool中取即可。

2.2 发起请求

在Webview真正发起请求的之前,Webview会进行缓存检查,而对于Webview来说可以可以设置缓存webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
在DOM树和相关图片资源下载的时候,这个其实是相对来说比较耗时的,那么一般有以下几个优化的思路

  • DOM树形成固定的模板,通过http请求出具体数据,填充到模板
  • 资源拦截:通过shouldInterceptRequest拦截一些图片和css资源,通过本地返回。
  • 图片加载: 在Webview进行初始化的时候设置默认不可以加载图片,在pagefinished方法中设置图片自动加载。

2.3 内容处理

关于内容处理这个其实需要服务器配合,简单来说当用户通过WebView来加载某个网页A,此时网页会出现一些内容,当用户点击此网页中内容的时候会进入下一页B。那么在用户刚进入A的时候,此时服务器是可以预知到下一个页面B的内容,因为B的链接是知道的,那么此时服务器可以通过预加载的方式请求到相关内容,内容回来之后无论是置于CDN还是推到客户端都可以随业务来定,这样相对来说是会提高加载速度的。

总结

整体来说WebView的加载优化大概以上几个方向:

  • 活用缓存
  • 提前加载
  • 尝试拦截
    这是Webview这块可以处理的,当然如果可以自己处理内核,那么可以优化的空间会更多,在此就不做展开。
收藏
评论区

相关推荐

js动态生成二维码
需求:项目需要根据链接实时生成二维码,当检测终端是PC时,将当前项目链接生成二维码供用户手机端使用 判断终端是否为mobile function isMobile () { let flag navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile
Android WebView加载优化
1.前言 最近几年关于原生WebView与H5混合开发的项目越来越多,这种开发带来了很多便利,但也会有一些缺点,比如说通过WebView加载H5会有一定的卡顿现象,会影响用户体验。下面本文就此问题一一展开讨论。 2. 场景 根据日常需求一般是通过webView.loadUrl()方法加载指定的网页,其大概流程如下: (https://i
Android webview 与 js(Vue) 交互
js 与原生交互分为两种情况:js 调用原生方法,原生调用 js 方法。 本文将对这两种情况分别讲解,H5 端用 vue 实现。 一、前期准备(Vue项目准备) 本文的 H5 端用Vue 实现,所以在正式开始前先把 Vue 项目环境准备好。 项目写好后,执行 npm run serve 命令启动项目,启动成功后会在命令
Android深入浅出之Binder机制
Android深入浅出之Binder机制 一 说明 Android系统最常见也是初学者最难搞明白的就是Binder了,很多很多的Service就是通过Binder机制来和客户端通讯交互的。所以搞明白Binder的话,在很大程度上就能理解程序运行的流程。 我们这里将以MediaService的例子来分析Binder的使用: ServiceMan
移动端H5开发常用技巧总结
html 篇 常用的meta属性设置 meta对于移动端的一些特殊属性,可根据需要自行设置 <meta name"screenorientation" content"portrait" //Android 禁止屏幕旋转 <meta name"fullscreen" content"yes"             //全屏显示
Android开发 常见异常和解决办法(一)
Android Studio是Android开发的理想工具,但是由于版本的更新和配置的差异,会出现很多问题,下面是以《第一行代码 第二版》为基础进行开发学习可能遇见的一些问题及其解决办法。 1.Android Studio 3.0及以上版本找不到Android Device Monitor: 解决办法: (1)在Android Studio中打开终端,如下
【Flutter 实战】移动开发技术简介
1.1 移动开发技术简介本节将主要介绍一下移动开发技术的进化历程,主要是想让读者知道Flutter技术出现的背景。笔者认为,了解一门新技术出现的背景是非常重要的,因为只有了解之前是什么样的,才能理解为什么会是现在这样。 1.1.1 原生开发与跨平台技术 原生开发原生应用程序是指某一个移动平台(比如iOS或安卓)所特有的应用,使用相应平台支持的开
记住几种出现内存泄漏的点
Android 内存优化——常见内存泄露及优化方案如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这中情况就是内存泄露。在Android 开发中,一些不好的编程习惯会导致我们的开发的app 存在内存泄露的情况。下面介绍一些在Android 开发中常见的内存泄
Django+Vue开发生鲜电商平台之4.Restful API和Vue介绍
也许今天你是最好的,但未必明天还最好;今天也许你是最差的,但社会给了你很多的机会,只要你把握,只要努力,总会有机会。 ——马云Github和Gitee代码同步更新:;。后端架构搭建好之后,需要搭建前端架构。 一、Restful API介绍 1.前后端分离优缺点近年来,随着多种平台类型(PC端、Android端、Mac端、iPhone端、P
小程序 - 开发指南之性能优化
代码层面通过代码细节提升性能,而且在这方面,空间是非常大的。对于比较早期的小程序项目,由于代码细节方面没有过多的考虑,也导致了开发出的小程序非常的消耗性能。下面将提到的一些点,不论是正在开发的项目,还是在维护的项目,都会有一定的帮助。 👇· 拆分组件对于小程序的项目,由于野蛮式开发,不会太多的考虑组件拆分。当然对于关注组件开发的公司,肯定会在早
Flutter 跨平台演进及架构开篇
版权声明: 本站所有博文内容均为原创,转载请务必注明作者与原文链接,且不得篡改原文内容。一、移动跨平台技术演进 1\. 引言移动互联网发展十余年,伴随着 Android、iOS 等智能手机的不断普及,移动端已逐步取代 PC 端,成为兵家必争之地。正所谓“得移动端者得天下”,移动端已成为互联网领域最大的流量分发入口,一
Android AOSP基础(一)VirtualBox 安装 Ubuntu
AOSP基础 Android框架层本文首发于微信公众号「刘望舒」 前言在Android进阶三部曲第二部《Android进阶解密》的第一章,我介绍了两种阅读源码的方式,其中一种是从百度网盘:https://pan.baidu.com/s/1ngsZs 将源码下载下来,然后用SouceInsight来查看,这种方式很便捷,适合去阅读源码,但是有两个弊端,一个是无
Python桌面图形程序美化的方法论
很多人都吐槽,使用 Tkinter、PyQt5等工具制作出来的图形界面程序太丑了。既然觉得它丑,我们来想想,它为什么会那么丑。 功能性是开发的第一要务每一个 Python 图形界面库都有它自有的功能特性和界面特性。一般来说,这些库的开发者着重要考虑的是功能性的实现。比如、列表框、拖拽框、悬浮框、自定义控件、webview等。一个图形界面库,受不受开发者的欢
Python桌面图形程序美化的方法论
很多人都吐槽,使用 Tkinter、PyQt5等工具制作出来的图形界面程序太丑了。既然觉得它丑,我们来想想,它为什么会那么丑。 功能性是开发的第一要务每一个 Python 图形界面库都有它自有的功能特性和界面特性。一般来说,这些库的开发者着重要考虑的是功能性的实现。比如、列表框、拖拽框、悬浮框、自定义控件、webview等。一个图形界面库,受不受开发者的欢
阿里官方推荐:有了这些中高端面试专题-大厂还会远吗
大佬带你走进Android开发的世界,掌握了这些知识点,学习Android也可以很轻松。 核心分析内容对于怎么学习Android,主要解决的是3个问题:学什么、怎么学 & 怎么用。具体如下:下面,我将带着上述几个问题,详细讲解自身学习Android的方法和Android学习路径;最后,还会结合前面内容,给出综合的具体执行学习Android的建议。 面经分享