Android今日头条方案,Android H5秒开方案调研—今日头条H5秒开方案详解

本文对Android H5秒开方案进行了调研,对今日头条App的秒开方案进行了详细分析。

《子夜吴歌·秋歌》

长安一片月,万户捣衣声。

秋风吹不尽,总是玉关情。

何日平胡虏,良人罢远征。

—唐,李白

背景

在回家的地铁上使用自家应用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框架,专注于提升页面首屏加载速度,完美支持静态直出页面和动态直出页面,支持预加载兼容离线包等方案。优点是性能好,速度快,大厂出品,缺点是配置复杂, 同时需要前后端接入。

今日头条方案

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

85e4f982cbdf

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

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

WebView预创建

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

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

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

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

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

内置所需文件

85e4f982cbdf

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 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("\n");

builder.append("\n");

builder.append("

\n");

builder.append("\n");

builder.append("\n");

builder.append("

builder.append("file:///android_asset/article/css/android.css");

builder.append("\">\n\n");

builder.append("

");

builder.append("

builder.append("\">");

builder.append("

builder.append("\" >\n");

builder.append("\n");

builder.append("\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;

}

}

本地数据库缓存

使用数据库进行持久化。

85e4f982cbdf

图片资源的显示

使用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对象即可。效果如下图所示:

85e4f982cbdf

通过以上优化点实现的最终效果如下:

85e4f982cbdf

85e4f982cbdf

总结

通过对今日头条app的分析,针对平台特有文章可以采用类似头条方案对数据预加载以提升用户体验。由于时间关系,就不再对微信进行分析,猜测也是采用了类似方案实现。通过WebView提供的缓存功能和拦截资源方法进行缓存体验上还是不尽如人意。也许等5G普及会好许多。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值