zoukankan      html  css  js  c++  java
  • Android H5秒开方案调研—今日头条H5秒开方案详解

    背景

    在回家的地铁上使用自家应用H5相关功能时,可能由于网络原因导致体验较差,在使用微信、今日头条App时,感觉很流畅,基本做到了秒开,然后就想了解下业内H5秒开方案。

    问题原因

    • 文件下载耗时:包括html、css、js、图片等

    • 页面渲染耗时:页面渲染,解析js、css文件等

    • WebView创建耗时:首次创建WebView耗时大约需要500ms左右,第二次创建耗时大约需要20ms左右

    常见解决方案

    WebView缓存相关

    • 浏览器缓存机制,通过请求头控制缓存

    • Dom Storgage(Web Storage)存储机制

    • Web SQL Database 存储机制

    • Application Cache(AppCache)机制

    • Indexed Database (IndexedDB)

    可通过以下代码实现:

    WebSettings webSettings = myWebView.getSettings();
    
    webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
    
    webSettings.setDomStorageEnabled(true);
    
    webSettings.setDatabaseEnabled(true);
    final String dbPath = getApplicationContext().getDir("db", Context.MODE_PRIVATE).getPath();
    webSettings.setDatabasePath(dbPath); 
    
    webSettings.setAppCacheEnabled(true);
    final String cachePath = getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
    webSettings.setAppCachePath(cachePath);
    webSettings.setAppCacheMaxSize(5*1024*1024);
    
    webSettings.setJavaScriptEnabled(true);

    开源方案

    • CacheWebView: 通过拦截shouldInterceptRequest方法使用okhttp的缓存功能实现,使用简单可配置。

    • VasSonic:腾讯出品的一个轻量级的高性能的Hybrid框架,专注于提升页面首屏加载速度,完美支持静态直出页面和动态直出页面,支持预加载兼容离线包等方案。优点是性能好,速度快,大厂出品,缺点是配置复杂, 同时需要前后端接入。

    今日头条方案

    先来看下今日头条的效果,第二次断网打开页面做到了秒开的效果:

    今日头条针对自己平台的文章详情页做了很多优化,具体包括以下几点:

    • 内置文章详情页所需的css、js等文件,并可以控制版本

    • WebView预创建

    • 预加载包含文章详情页所需的css、js的空html

    • 在列表页预加载文章详情所需的内容使用LRU内存缓存并保存到本地数据库

    • 在文章详情页获取预创建的WebView(预加载了html),直接调用js设置页面内容

    • 通过js控制图片的显示,图片懒加载(当图片在可见区域或即将可见才会加载图片),点击加载图片等

    • Html中的图片通过ContentProvider获取使用Fresco下载的图片

    内置所需文件

    <img0.5847255369928401" data-src="http://img01.store.sogou.com/net/a/04/link?appid=100520029&url=http://img01.store.sogou.com/net/a/04/link?appid=100520029&url=https://mmbiz.qpic.cn/mmbiz/cmOLumrNib1eOO0yAWeZFdc5DGtcsYlJf81Tho9FnxDO7jYNtuIw7S3FmYibYiceptkRCMGu7puDPDUYw7j9awKWg/640?wx_fmt=other" data-type="other" data-w="419" title="" _width="419px" src="https://www.itcodemonkey.com/data/upload/portal/20190625/1561446899710621.jpg" data-fail="0">

    WebView预创建,资源预加载
    首次创建WebView要比第二次创建耗时慢很多,原因估计是WebView首次创建需要初始化一些静态资源,第二次创建时不需要初始化,所以第二次创建耗时要少很多。

    使用Context包装类MutableContextWrapper传入Application预创建WebView对象,然后预加载一个使用java代码拼接的html,提前对js、css资源进行解析。等获取预创建的WebView时再替换为Activity的context。

    public class PreloadWebView {
        private PreloadWebView(){}
    
        private static final int CACHED_WEBVIEW_MAX_NUM = 2;
        private static final Stack<WebView> mCachedWebViewStack = new Stack<>();
    
    
        public static PreloadWebView getInstance(){
            return Holder.INSTANCE;
        }
    
        private static class Holder{
            private static final PreloadWebView INSTANCE = new PreloadWebView();
        }
    
        /**
         * 创建WebView实例
         * 用了applicationContext
         */
        public void preload() {
            L.d("webview preload");
            Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
                @Override
                public boolean queueIdle() {
                    if (mCachedWebViewStack.size() < CACHED_WEBVIEW_MAX_NUM) {
                        mCachedWebViewStack.push(createWebView());
                    }
                    return false;
                }
            });
        }
    
        private WebView createWebView() {
            WebView webview = new WebView(new MutableContextWrapper(App.getApp()));
            webview.getSettings().setJavaScriptEnabled(true);
            webview.loadDataWithBaseURL("file:///android_asset/article/?item_id=0&token=0",getHtml(),"text/html","utf-8","file:///android_asset/article/?item_id=0&token=0");
            return webview;
        }
    
    
        private static String getHtml() {
            StringBuilder builder = new StringBuilder();
            builder.append("<!DOCTYPE html>
    ");
            builder.append("<html>
    ");
            builder.append("<head>
    ");
            builder.append("<meta charset="utf-8">
    ");
            builder.append("<meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
    ");
            builder.append("<link rel="stylesheet" type="text/css" href="");
            builder.append("file:///android_asset/article/css/android.css");
            builder.append("">
    </head>
    ");
            builder.append("<body class="font_m"><header></header><article></article><footer></footer>");
            builder.append("<script type="text/javascript" src="");
            builder.append("file:///android_asset/article/js/lib.js");
            builder.append(""></script>");
            builder.append("<script type="text/javascript" src="");
            builder.append("file:///android_asset/article/js/android.js");
            builder.append("" ></script>
    ");
            builder.append("</body>
    ");
            builder.append("</html>
    ");
            return builder.toString();
        }
    
        /**
         * 从缓存池中获取合适的WebView
         *
         * @param context activity context
         * @return WebView
         */
        public WebView getWebView(Context context) {
            // 为空,直接返回新实例
            if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {
                WebView web = createWebView();
                MutableContextWrapper contextWrapper = (MutableContextWrapper) web.getContext();
                contextWrapper.setBaseContext(context);
                return web;
            }
            WebView webView = mCachedWebViewStack.pop();
            // webView不为空,则开始使用预创建的WebView,并且替换Context
            MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
            contextWrapper.setBaseContext(context);
            return webView;
        }
    
    }

    本地数据库缓存

    使用数据库进行持久化。

    <img0.516" data-src="http://img01.store.sogou.com/net/a/04/link?appid=100520029&url=http://img01.store.sogou.com/net/a/04/link?appid=100520029&url=https://mmbiz.qpic.cn/mmbiz/cmOLumrNib1eOO0yAWeZFdc5DGtcsYlJfhT8kDXaSWwicwQuUp89j5bTiaNC7HyWedPhu5jKkRVM0KdTjDZFyBGwg/640?wx_fmt=other" data-type="other" data-w="1000" title="" _width="677px" src="https://www.itcodemonkey.com/data/upload/portal/20190625/1561446900162107.jpg" data-fail="0">

    广州VI设计公司https://www.houdianzi.com

    图片资源的显示

    使用ContentProvider获取图片资源:

    content://com.xposed.toutiao.provider.ImageProvider/getimage/origin/eJy1ku0KwiAUhm8l_F3qvuduJSJ0mRO2JtupiNi9Z4MoWiOa65cinMeX57xXVDda6QPKFld0bLQ9UckbJYlR-UpX3N5Smfi5x3JJ934YxWlKWZhEgbeLhBB-QNFyYUfL1s6uUQFgMkKMtwLA4gJSVwrndUWmUP8CC5xhm87izlKY7VDeTgLXZUtOlJzjkP6AxXfiR5eMYdMCB9PHneGHBzh-VzEje7AzV3ZvHYpjJV599w-uZWXvWadQR_vlAhtY_Bn2LKuzu_GGOscc1MfZ4veyTyNuuu4G1giVqQ==/6694469396007485965/3

    上面的ContentProvider的uri会调用对应ContentProvider的openFile方法,别忘了在清单文件中注册。

    public class ImageProvider extends ContentProvider {
      ...
      public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
        File file = getFile(uri);
        return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) ;
      }
      ...
    }

    中间字符串使用zip压缩,使用下面的代码解压zip数据的代码:

    static final byte[] buffer = new byte[4096];
    public static final String unzip(String str) {
        try {
            Inflater inflater = new Inflater();
            inflater.setInput(Base64.decode(str, 8));
            int size = inflater.inflate(buffer);
            inflater.end();
            String temp = new String(buffer, 0, size, "UTF-8");
            return temp;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    解压后的数据如下:

    {
        "origin": {
            "uri": "large/pgc-image/8e72c19ce0f2456880947531d5bbb230",
            "urls": ["http://p1-tt.byteimg.com/large/pgc-image/8e72c19ce0f2456880947531d5bbb230", "http://p1-tt.byteimg.com/large/pgc-image/8e72c19ce0f2456880947531d5bbb230", "http://p3-tt.byteimg.com/large/pgc-image/8e72c19ce0f2456880947531d5bbb230"]
        },
        "webp_origin": {
            "uri": "details/v0/w640/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp",
            "urls": ["http://p99.pstatp.com/details/v0/w640/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp", "http://p6-tt.byteimg.com/details/v0/w640/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp", "http://p1-tt.byteimg.com/details/v0/w640/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp"]
        },
        "thumb": {
            "uri": "thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230",
            "urls": ["http://p9-tt.byteimg.com/thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230", "http://p3-tt.byteimg.com/thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230", "http://p1-tt.byteimg.com/thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230"]
        },
        "webp_thumb": {
            "uri": "thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp",
            "urls": ["http://p1-tt.byteimg.com/thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp", "http://p3-tt.byteimg.com/thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp", "http://p6-tt.byteimg.com/thumb/pgc-image/8e72c19ce0f2456880947531d5bbb230.webp"]
        }
    }

    uri的最后两个片段表示文章id及图片索引,用于通过js通知页面图片加载完成。通过解析content的uri中的数据获取Fresco下载的缓存文件,返回一个ParcelFileDescriptor对象即可。

  • 相关阅读:
    如何让position:fixed在IE6中工作 不抖动
    【javascript基础】之【宿主环境】
    用函数式编程技术编写优美的 JavaScript
    IE6下使用滤镜后链接不能点击的BUG
    什么是内存泄漏
    Best Practices for Speeding Up Your Web Site
    Object.prototype.toString.call()
    【前端优化】IE浏览器下同一网页多图片显示的瓶颈与优化
    get username
    open file and format readin
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/14035666.html
Copyright © 2011-2022 走看看