2019-03-29 21 WebView性能优化
WebView性能优化
- WebView耗时优化
- WebView预加载, WebView池
- IO异步化(资源、网络请求与WebView初始化加载分离)
- HTML页面耗时优化
- 静态直出, CDN直接输出可展示交互HTML(HTML不包含展示内容; 动态构建DOM开销)
- 动态直出(NodeJs) + 动态缓存(Sonic动态缓存)
- 网络请求优化
- 离线预推
- diff增量推送
- DNS预解析
- 渲染采用chunk编码
- 离线预推
- 性能监控
- 监控指标
- 白屏时间
- 监控指标
Sonic源码流程解析
核心思想利用webview初始化时间进行数据请求
- 无缓存模式启动流程
...
// 1. 创建初始化Session
sonicSession = SonicEngine.getInstance().createSession(url, sessionConfigBuilder.build());
...
// 2. 设置拦截资源加载
webView.setWebViewClient(new WebViewClient() {
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if (sonicSession != null) {
// 拦截资源加载, 如果有对应的桥接流, 则返回让webview进行加载
return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url);
}
return null;
}
});
...
...
// 3. webview已初始化完成
sonicSessionClient.bindWebView(webView);
sonicSessionClient.clientReady();
- 初始化Session
// SonicEngine.java
public synchronized SonicSession createSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {
...
sonicSession = internalCreateSession(sessionId, url, sessionConfig);
...
}
// 创建Session实例, 并把sonic启动
private SonicSession internalCreateSession(String sessionId, String url, SonicSessionConfig sessionConfig) {
...
sonicSession = new QuickSonicSession(sessionId, url, sessionConfig);
...
// SonicSession#start()
sonicSession.start();
}
SonicSession#start()
, 启动加载流程
// SonicSession
public void start() {
...
SonicEngine.getInstance().getRuntime().postTaskToSessionThread(new Runnable() {
@Override
public void run() {
runSonicFlow(true);
}
});
...
}
// 首次加载, firstRequest为true.
private void runSonicFlow(boolean firstRequest) {
String cacheHtml = null;
// 获取会话数据(eTag)等
SonicDataHelper.SessionData sessionData;
sessionData = getSessionData(firstRequest);
if (firstRequest) {
// 获取本地缓存
cacheHtml = SonicCacheInterceptor.getSonicCacheData(this);
...
// 1. 参考handleFlow_LoadLocalCache实现
handleFlow_LoadLocalCache(cacheHtml); // local cache if exist before connection
}
boolean hasHtmlCache = !TextUtils.isEmpty(cacheHtml) || !firstRequest;
final SonicRuntime runtime = SonicEngine.getInstance().getRuntime();
...
// 和服务器进行连接, 获取会话数据
// 参考handleFlow_Connection实现
handleFlow_Connection(hasHtmlCache, sessionData);
...
}
SonicSession#handleFlow_LoadLocalCache()
// handleFlow_LoadLocalCache实现
protected void handleFlow_LoadLocalCache(String cacheHtml) {
// 发送CLIENT_CORE_MSG_PRE_LOAD消息
Message msg = mainHandler.obtainMessage(CLIENT_CORE_MSG_PRE_LOAD);
if (!TextUtils.isEmpty(cacheHtml)) {
msg.arg1 = PRE_LOAD_WITH_CACHE;
msg.obj = cacheHtml;
} else {
...
msg.arg1 = PRE_LOAD_NO_CACHE;
}
mainHandler.sendMessage(msg);
...
}
// 找到CLIENT_CORE_MSG_PRE_LOAD消息对应的处理逻辑
// 有缓存的情况下调用loadDataWithBaseUrlAndHeader, 让webview直接加载
// 无缓存情况下调用#loadUrl
private void handleClientCoreMessage_PreLoad(Message msg) {
switch (msg.arg1) {
case PRE_LOAD_NO_CACHE: {
...
sessionClient.loadUrl(srcUrl, null);
...
}
break;
case PRE_LOAD_WITH_CACHE: {
...
String html = (String) msg.obj;
sessionClient.loadDataWithBaseUrlAndHeader(srcUrl, html, "text/html",
SonicUtils.DEFAULT_CHARSET, srcUrl, getCacheHeaders());
...
}
break;
}
}
- 继续看
SonicSession#handleFlow_Connection()
实现流程
protected void handleFlow_Connection(boolean hasCache, SonicDataHelper.SessionData sessionData) {
...
server = new SonicServer(this, createConnectionIntent(sessionData));
// 1. 连接服务器
int responseCode = server.connect();
...
// 后端通过http response header返回的sonic-link, 即需要预加载的资源, 参考预加载子资源逻辑
String preloadLink = server.getResponseHeaderField(SonicSessionConnection.CUSTOM_HEAD_FILED_LINK);
...
handleFlow_PreloadSubResource();
...
...
// 处理304逻辑
handleFlow_NotModified();
...
// When cacheHtml is empty, run First-Load flow
if (!hasCache) {
// 无缓存
handleFlow_FirstLoad();
return;
}
...
}
QuickSonicSession#handleFlow_FirstLoad()
protected void handleFlow_FirstLoad() {
// 桥接流
pendingWebResourceStream = server.getResponseStream(wasInterceptInvoked);
...
String htmlString = server.getResponseData(false);
boolean hasCompletionData = !TextUtils.isEmpty(htmlString);
...
mainHandler.removeMessages(CLIENT_CORE_MSG_PRE_LOAD);
// 发送消息, 由QuickSonicSession#handleClientCoreMessage_FirstLoad()处理逻辑
Message msg = mainHandler.obtainMessage(CLIENT_CORE_MSG_FIRST_LOAD);
msg.obj = htmlString;
msg.arg1 = hasCompletionData ? FIRST_LOAD_WITH_DATA : FIRST_LOAD_NO_DATA;
mainHandler.sendMessage(msg);
...
// 发送保存html缓存消息, FILE_THREAD_SAVE_CACHE_ON_SESSION_FINISHED, 逻辑查看SonicSession#doSaveSonicCache()
postTaskToSaveSonicCache(htmlString);
...
}
QuickSonicSession#handleClientCoreMessage_FirstLoad()
// 服务返回数据
private void handleClientCoreMessage_FirstLoad(Message msg) {
switch (msg.arg1) {
...
case FIRST_LOAD_WITH_DATA: {
...
sessionClient.loadDataWithBaseUrlAndHeader(srcUrl, (String) msg.obj, "text/html",
getCharsetFromHeaders(), srcUrl, getHeaders());
...
}
break;
}
}
SonicSession#doSaveSonicCache()
,保存html缓存protected void doSaveSonicCache(SonicServer sonicServer, String htmlString) { ... // 保存html缓存 SonicUtils.saveSonicData(id, eTag, templateTag, newHtmlSha1, htmlSize, headers); ... }
- 拦截请求
WebViewClient#shouldInterceptRequest()
// 请求资源
public Object requestResource(String url) {
...
session.onClientRequestResource(url);
...
}
SonicSession#onClientRequestResource()
// 如果有桥接流或者子资源缓存, 返回WebResourceResponse
public final Object onClientRequestResource(String url) {
...
// 主域请求逻辑#onRequestResource(), 子域请求逻辑resourceDownloaderEngine#onRequestSubResource()
Object object = isMatchCurrentUrl(url)
? onRequestResource(url)
: (resourceDownloaderEngine != null ? resourceDownloaderEngine.onRequestSubResource(url, this) : null);
...
}
SnoicSession#onRequestResource()
protected Object onRequestResource(String url) {
...
// 首次启动时, 如果已和服务器链接, 则使用服务器的桥接流
if (null != pendingWebResourceStream) {
Object webResourceResponse;
if (!isDestroyedOrWaitingForDestroy()) {
String mime = SonicUtils.getMime(srcUrl);
webResourceResponse = SonicEngine.getInstance().getRuntime().createWebResourceResponse(mime,
getCharsetFromHeaders(), pendingWebResourceStream, getHeaders());
} else {
webResourceResponse = null;
...
}
...
return webResourceResponse;
}
...
}
SonicDownloadEngine#onRequestSubResource()