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>\n"); builder.append("<html>\n"); builder.append("<head>\n"); builder.append("<meta charset=\"utf-8\">\n"); builder.append("<meta name=\"viewport\" content=\"initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n"); builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\""); builder.append("file:///android_asset/article/css/android.css"); builder.append("\">\n</head>\n"); 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>\n"); builder.append("</body>\n"); builder.append("</html>\n"); 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對象即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM