网页加载缓慢,白屏,使用卡顿。css
1.调用loadUrl()方法的时候,才会开始网页加载流程 2.js臃肿问题 3.加载图片太多 4.webview自己问题html
webview初始化->DOM下载→DOM解析→CSS请求+下载→CSS解析→渲染→绘制→合成前端
1.webview自己优化java
public class App extends Application {
private WebView mWebView ;
@Override
public void onCreate() {
super.onCreate();
mWebView = new WebView(new MutableContextWrapper(this));
}
}
复制代码
效果:见下图 android
public class WebPools {
private final Queue<WebView> mWebViews;
private Object lock = new Object();
private static WebPools mWebPools = null;
private static final AtomicReference<WebPools> mAtomicReference = new AtomicReference<>();
private static final String TAG=WebPools.class.getSimpleName();
private WebPools() {
mWebViews = new LinkedBlockingQueue<>();
}
public static WebPools getInstance() {
for (; ; ) {
if (mWebPools != null)
return mWebPools;
if (mAtomicReference.compareAndSet(null, new WebPools()))
return mWebPools=mAtomicReference.get();
}
}
public void recycle(WebView webView) {
recycleInternal(webView);
}
public WebView acquireWebView(Activity activity) {
return acquireWebViewInternal(activity);
}
private WebView acquireWebViewInternal(Activity activity) {
WebView mWebView = mWebViews.poll();
LogUtils.i(TAG,"acquireWebViewInternal webview:"+mWebView);
if (mWebView == null) {
synchronized (lock) {
return new WebView(new MutableContextWrapper(activity));
}
} else {
MutableContextWrapper mMutableContextWrapper = (MutableContextWrapper) mWebView.getContext();
mMutableContextWrapper.setBaseContext(activity);
return mWebView;
}
}
private void recycleInternal(WebView webView) {
try {
if (webView.getContext() instanceof MutableContextWrapper) {
MutableContextWrapper mContext = (MutableContextWrapper) webView.getContext();
mContext.setBaseContext(mContext.getApplicationContext());
LogUtils.i(TAG,"enqueue webview:"+webView);
mWebViews.offer(webView);
}
if(webView.getContext() instanceof Activity){
//throw new RuntimeException("leaked");
LogUtils.i(TAG,"Abandon this webview , It will cause leak if enqueue !");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
复制代码
带来的问题:内存泄漏 使用预先建立以及复用池后的效果 git
<service
android:name=".PreWebService"
android:process=":web"/>
<activity
android:name=".WebActivity"
android:process=":web"/>
复制代码
启动webview页面前,先启动PreWebService把[web]进程建立了,当启动WebActivity时,系统发发现[web]进程已经存在了,就不须要花费时间Fork出新的[web]进程了。github
2.加载资源时的优化 这种优化多使用第三方,下面有介绍web
3.网页端的优化 由网页的前端工程师优化网页,或者说是和移动端一块儿,将网页实现增量更新,动态更新。app内置css,js文件并控制版本数据库
注意:若是你寄但愿于只经过webview的setting来加速网页的加载速度,那你就要失望了。只修改设置,能作的提高很是少。因此本文就着重分析比较下,如今可使用的第三方webview框架的优缺点。后端
如今大厂的方法有如下几种:
参考文章: blog.csdn.net/tencent__op… 接入方法: STEP1:
//导入 Tencent/VasSonic
implementation 'com.tencent.sonic:sdk:3.1.0'
复制代码
STEP2:
//建立一个类继承SonicRuntime
//SonicRuntime类主要提供sonic运行时环境,包括Context、用户UA、ID(用户惟一标识,存放数据时惟一标识对应用户)等等信息。如下代码展现了SonicRuntime的几个方法。
public class TTPRuntime extends SonicRuntime {
//初始化
public TTPRuntime( Context context ) {
super(context);
}
@Override
public void log( String tag , int level , String message ) {
//log设置
}
//获取cookie
@Override
public String getCookie( String url ) {
return null;
}
//设置cookid
@Override
public boolean setCookie( String url , List<String> cookies ) {
return false;
}
//获取用户UA信息
@Override
public String getUserAgent() {
return "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Mobile Safari/537.36";
}
//获取用户ID信息
@Override
public String getCurrentUserAccount() {
return "ttpp";
}
//是否使用Sonic加速
@Override
public boolean isSonicUrl( String url ) {
return true;
}
//建立web资源请求
@Override
public Object createWebResourceResponse( String mimeType , String encoding , InputStream data , Map<String, String> headers ) {
return null;
}
//网络属否容许
@Override
public boolean isNetworkValid() {
return true;
}
@Override
public void showToast( CharSequence text , int duration ) { }
@Override
public void postTaskToThread( Runnable task , long delayMillis ) { }
@Override
public void notifyError( SonicSessionClient client , String url , int errorCode ) { }
//设置Sonic缓存地址
@Override
public File getSonicCacheDir() {
return super.getSonicCacheDir();
}
}
复制代码
STEP3:
//建立一个类继承SonicSessionClien
//SonicSessionClient主要负责跟webView的通讯,好比调用webView的loadUrl、loadDataWithBaseUrl等方法。
public class WebSessionClientImpl extends SonicSessionClient {
private WebView webView;
//绑定webview
public void bindWebView(WebView webView) {
this.webView = webView;
}
//加载网页
@Override
public void loadUrl(String url, Bundle extraData) {
webView.loadUrl(url);
}
//加载网页
@Override
public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding, String historyUrl) {
webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
//加载网页
@Override
public void loadDataWithBaseUrlAndHeader( String baseUrl , String data , String mimeType , String encoding , String historyUrl , HashMap<String, String> headers ) {
if( headers.isEmpty() )
{
webView.loadDataWithBaseURL( baseUrl, data, mimeType, encoding, historyUrl );
}
else
{
webView.loadUrl( baseUrl,headers );
}
}
}
复制代码
STEP4:
//建立activity
public class WebActivity extends AppCompatActivity {
private String url = "http://www.baidu.com";
private SonicSession sonicSession;
@Override
protected void onCreate( @Nullable Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_web);
initView();
}
private void initView() {
getWindow().addFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
//初始化 可放在Activity或者Application的onCreate方法中
if( !SonicEngine.isGetInstanceAllowed() )
{
SonicEngine.createInstance( new TTPRuntime( getApplication() ),new SonicConfig.Builder().build() );
}
//设置预加载
SonicSessionConfig config = new SonicSessionConfig.Builder().build();
SonicEngine.getInstance().preCreateSession( url,config );
WebSessionClientImpl client = null;
//SonicSessionConfig 设置超时时间、缓存大小等相关参数。
//建立一个SonicSession对象,同时为session绑定client。session建立以后sonic就会异步加载数据了
sonicSession = SonicEngine.getInstance().createSession( url,config );
if( null!= sonicSession )
{
sonicSession.bindClient( client = new WebSessionClientImpl() );
}
//获取webview
WebView webView = (WebView)findViewById( R.id.webview_act );
webView.setWebViewClient( new WebViewClient()
{
@Override
public void onPageFinished( WebView view , String url ) {
super.onPageFinished( view , url );
if( sonicSession != null )
{
sonicSession.getSessionClient().pageFinish( url );
}
}
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest( WebView view , WebResourceRequest request ) {
return shouldInterceptRequest( view, request.getUrl().toString() );
}
//为clinet绑定webview,在webView准备发起loadUrl的时候经过SonicSession的onClientReady方法通知sonicSession: webView ready能够开始loadUrl了。这时sonic内部就会根据本地的数据状况执行webView相应的逻辑(执行loadUrl或者loadData等)
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest( WebView view , String url ) {
if( sonicSession != null )
{
return (WebResourceResponse)sonicSession.getSessionClient().requestResource( url );
}
return null;
}
});
//webview设置
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webView.removeJavascriptInterface("searchBoxJavaBridge_");
//webView.addJavascriptInterface(new SonicJavaScriptInterface(sonicSessionClient, intent), "sonic");
webSettings.setAllowContentAccess(true);
webSettings.setDatabaseEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setSavePassword(false);
webSettings.setSaveFormData(false);
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
//为clinet绑定webview,在webView准备发起loadUrl的时候经过SonicSession的onClientReady方法通知sonicSession: webView ready能够开始loadUrl了。这时sonic内部就会根据本地的数据状况执行webView相应的逻辑(执行loadUrl或者loadData等)。
if( client != null )
{
client.bindWebView( webView );
client.clientReady();
}
else
{
webView.loadUrl( url );
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
@Override
protected void onDestroy() {
if( null != sonicSession )
{
sonicSession.destroy();
sonicSession = null;
}
super.onDestroy();
}
}
复制代码
简单分析下它的核心思想: 并行,充分利用webview初始化的时间进行一些数据的处理。在包含webview的activity启动时会一边进行webview的初始化逻辑,一边并行的执行sonic的逻辑。这个sonic逻辑就是网页的预加载 原理:
左边的webview流程:webview初始化后调用SonicSession的onClientReady方法,告知 webview已经初始化完毕。
client.clientReady();
复制代码
右边的sonic流程:
2.有缓存模式 彻底缓存流程: 左边webview的流程跟无缓存一致,右边sonic的流程会经过SonicCacheInterceptor获取本地数据是否为空,不为空就会发生CLIENT_CORE_MSG_PRE_LOAD消息,以后webview就会使用loadDataWithBaseUrl加载网页进行渲染了
集成方法,请按照官网的来操做便可。这里直接放上使用后的效果图吧
来看下百度app对webview处理的方案
2.智能预取-提早化网络请求 提早从网络中获取部分落地页html,缓存到本地,当用户点击查看时,只须要从缓存中加载便可。
3.通用拦截-缓存共享、请求并行 直出解决了文字展示的速度问题,可是图片加载渲染速度还不理想。 借由内核的shouldInterceptRequest回调,拦截落地页图片请求,由客户端调用图片下载框架进行下载,并以管道方式填充到内核的WebResourceResponse中。就是说在shouldInterceptRequest拦截全部URL,以后只针对后缀是.PNG/.JPG等图片资源,使用第三方图片下载工具相似于Fresco进行下载并返回一个InputStream。
总结:
那今日头条是怎么处理的呢? 1.assets文件夹内预置了文章详情页面的css/js等文件,而且能进行版本控制 2.webview预建立的同时,预先加载一个使用JAVA代码拼接的html,提早对js/css资源进行解析。 3.文章详情页面使用预建立的webview,这个webview已经预加载了html,以后就调用js来设置页面内容 3.对于图片资源,使用ContentProvider来获取,而图片则是使用Fresco来下载的
content://com.xposed.toutiao.provider.ImageProvider/getimage/origin/eJy1ku0KwiAUhm8l_F3qvuduJSJ0mRO2JtupiNi9Z4MoWiOa65cinMeX57xXVDda6QPKFld0bLQ9UckbJYlR-UpX3N5Smfi5x3JJ934YxWlKWZhEgbeLhBB-QNFyYUfL1s6uUQFgMkKMtwLA4gJSVwrndUWmUP8CC5xhm87izlKY7VDeTgLXZUtOlJzjkP6AxXfiR5eMYdMCB9PHneGHBzh-VzEje7AzV3ZvHYpjJV599w-uZWXvWadQR_vlAhtY_Bn2LKuzu_GGOscc1MfZ4veyTyNuuu4G1giVqQ==/6694469396007485965/3
复制代码
整理下这几个大厂的思路 目的:网页秒开 策略:
本身的想法:
修复白屏现象:系统处理view绘制的时候,有一个属性setDrawDuringWindowsAnimating,这个属性是用来控制window作动画的过程当中是否能够正常绘制,而刚好在Android 4.2到Android N之间,系统为了组件切换的流程性考虑,该字段为false,咱们能够利用反射的方式去手动修改这个属性
/**
* 让 activity transition 动画过程当中能够正常渲染页面
*/
private void setDrawDuringWindowsAnimating(View view) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// 1 android n以上 & android 4.1如下不存在此问题,无须处理
return;
}
// 4.2不存在setDrawDuringWindowsAnimating,须要特殊处理
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
handleDispatchDoneAnimating(view);
return;
}
try {
// 4.3及以上,反射setDrawDuringWindowsAnimating来实现动画过程当中渲染
ViewParent rootParent = view.getRootView().getParent();
Method method = rootParent.getClass()
.getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
method.setAccessible(true);
method.invoke(rootParent, true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* android4.2能够反射handleDispatchDoneAnimating来解决
*/
private void handleDispatchDoneAnimating(View paramView) {
try {
ViewParent localViewParent = paramView.getRootView().getParent();
Class localClass = localViewParent.getClass();
Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
localMethod.setAccessible(true);
localMethod.invoke(localViewParent);
} catch (Exception localException) {
localException.printStackTrace();
}
}
复制代码
前文已经说明了sonic的主体思想以及主要的缓存逻辑流程,下面就结合源码一块儿来看看它是怎么运做预加载这个功能的吧。
SonicSessionConfig.Builder sessionConfigBuilder = new SonicSessionConfig.Builder();
sessionConfigBuilder.setSupportLocalServer(true);
// 预先加载
boolean preloadSuccess = SonicEngine.getInstance().preCreateSession(DEMO_URL, sessionConfigBuilder.build());
复制代码
进入preCreateSession
方法看看
public synchronized boolean preCreateSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {
//数据库是否准备好
if (isSonicAvailable()) {
//根据url以及RunTime中设置的帐号,生成惟一的sessionId
String sessionId = makeSessionId(url, sessionConfig.IS_ACCOUNT_RELATED);
if (!TextUtils.isEmpty(sessionId)) {
SonicSession sonicSession = lookupSession(sessionConfig, sessionId, false);
if (null != sonicSession) {
runtime.log(TAG, Log.ERROR, "preCreateSession:sessionId(" + sessionId + ") is already in preload pool.");
return false;
}
//判断预载池是否满了
if (preloadSessionPool.size() < config.MAX_PRELOAD_SESSION_COUNT) {
//网络判断
if (isSessionAvailable(sessionId) && runtime.isNetworkValid()) {
//建立sonicSession去进行预载
sonicSession = internalCreateSession(sessionId, url, sessionConfig);
if (null != sonicSession) {
//放到池子里
preloadSessionPool.put(sessionId, sonicSession);
return true;
}
}
} else {
runtime.log(TAG, Log.ERROR, "create id(" + sessionId + ") fail for preload size is bigger than " + config.MAX_PRELOAD_SESSION_COUNT + ".");
}
}
} else {
runtime.log(TAG, Log.ERROR, "preCreateSession fail for sonic service is unavailable!");
}
return false;
}
复制代码
分析:这个方法只要是作了sonic session的建立工做。可是只有知足预载池(preloadSessionPool
)的大小小于MAX_PRELOAD_SESSION_COUNT
时才会建立。 咱们继续进入下一个方法internalCreateSession
去看看是怎么建立的
private SonicSession internalCreateSession(String sessionId, String url, SonicSessionConfig sessionConfig) {
//预载的sessionId不在已经运行的Session的map中
if (!runningSessionHashMap.containsKey(sessionId)) {
SonicSession sonicSession;
//设置缓存类型
if (sessionConfig.sessionMode == SonicConstants.SESSION_MODE_QUICK) {
//快速类型
sonicSession = new QuickSonicSession(sessionId, url, sessionConfig);
} else {
//标准类型
sonicSession = new StandardSonicSession(sessionId, url, sessionConfig);
}
//session状态变化监听
sonicSession.addSessionStateChangedCallback(sessionCallback);
//默认为true启动session
if (sessionConfig.AUTO_START_WHEN_CREATE) {
sonicSession.start();
}
return sonicSession;
}
if (runtime.shouldLog(Log.ERROR)) {
runtime.log(TAG, Log.ERROR, "internalCreateSession error:sessionId(" + sessionId + ") is running now.");
}
return null;
}
复制代码
这个方法就是根据sessionConfig中的sessionMode类型,来建立不一样的缓存类型session。 QuickSonicSession
以及StandardSonicSession
类型。最后再启动session进行预载工做。咱们从sonicSession.start()
继续看下去。
public void start() {
...
for (WeakReference<SonicSessionCallback> ref : sessionCallbackList) {
SonicSessionCallback callback = ref.get();
if (callback != null) {
//回调启动状态
callback.onSonicSessionStart();
}
}
...
//在session线程中运行预载网页方法
SonicEngine.getInstance().getRuntime().postTaskToSessionThread(new Runnable() {
@Override
public void run() {
runSonicFlow(true);
}
});
...
}
复制代码
其中最主要的方法就是runSonicFlow(true)
这个方法在sonic的专门的线程池中执行网络请求操做。
private void runSonicFlow(boolean firstRequest) {
...
//首次请求
if (firstRequest) {
//获取html缓存 首次为空
cacheHtml = SonicCacheInterceptor.getSonicCacheData(this);
statistics.cacheVerifyTime = System.currentTimeMillis();
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") runSonicFlow verify cache cost " + (statistics.cacheVerifyTime - statistics.sonicFlowStartTime) + " ms");
//发送消息CLIENT_CORE_MSG_PRE_LOAD arg1:PRE_LOAD_NO_CACHE
handleFlow_LoadLocalCache(cacheHtml);
}
boolean hasHtmlCache = !TextUtils.isEmpty(cacheHtml) || !firstRequest;
final SonicRuntime runtime = SonicEngine.getInstance().getRuntime();
if (!runtime.isNetworkValid()) {
//网络不存在
if (hasHtmlCache && !TextUtils.isEmpty(config.USE_SONIC_CACHE_IN_BAD_NETWORK_TOAST)) {
runtime.postTaskToMainThread(new Runnable() {
@Override
public void run() {
if (clientIsReady.get() && !isDestroyedOrWaitingForDestroy()) {
runtime.showToast(config.USE_SONIC_CACHE_IN_BAD_NETWORK_TOAST, Toast.LENGTH_LONG);
}
}
}, 1500);
}
SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") runSonicFlow error:network is not valid!");
} else {
//开始请求
handleFlow_Connection(hasHtmlCache, sessionData);
statistics.connectionFlowFinishTime = System.currentTimeMillis();
}
...
}
复制代码
分析:在首次请求的时候,调用handleFlow_LoadLocalCache
方法实际是调用以前建立的QuickSonicSession
或者StandardSonicSession
的handleFlow_LoadLocalCache
主要做用是发送一则消息CLIENT_CORE_MSG_PRE_LOAD以及是否含有cache。以后网络存在的状况下调用handleFlow_Connection(hasHtmlCache, sessionData)
方法进行请求工做。 接下来进入handleFlow_Connection
方法看下是如何创建链接的。
protected void handleFlow_Connection(boolean hasCache, SonicDataHelper.SessionData sessionData) {
...
//建立网络请求
server = new SonicServer(this, createConnectionIntent(sessionData));
...
}
复制代码
方法很长咱们一部分一部分看,首先这个建立SonicServer对象,其中经过SonicSessionConnection
建立URLConnection
public SonicServer(SonicSession session, Intent requestIntent) {
this.session = session;
this.requestIntent = requestIntent;
connectionImpl = SonicSessionConnectionInterceptor.getSonicSessionConnection(session, requestIntent);
}
复制代码
public static SonicSessionConnection getSonicSessionConnection(SonicSession session, Intent intent) {
SonicSessionConnectionInterceptor interceptor = session.config.connectionInterceptor;
//是否有拦截
if (interceptor != null) {
return interceptor.getConnection(session, intent);
}
return new SonicSessionConnection.SessionConnectionDefaultImpl(session, intent);
}
复制代码
public SessionConnectionDefaultImpl(SonicSession session, Intent intent) {
super(session, intent);
//建立URLConnection
connectionImpl = createConnection();
initConnection(connectionImpl);
}
复制代码
以后回到handleFlow_Connection
,既然建立好了URLConnection
那么接下来就能够链接去请求数据了。
int responseCode = server.connect();
复制代码
protected int connect() {
long startTime = System.currentTimeMillis();
// 链接是否正常返回码
int resultCode = connectionImpl.connect();
...
if (SonicConstants.ERROR_CODE_SUCCESS != resultCode) {
return resultCode; // error case
}
startTime = System.currentTimeMillis();
//链接请求返回码
responseCode = connectionImpl.getResponseCode();
...
// When eTag is empty
if (TextUtils.isEmpty(eTag)) {
readServerResponse(null);
if (!TextUtils.isEmpty(serverRsp)) {
eTag = SonicUtils.getSHA1(serverRsp);
addResponseHeaderFields(getCustomHeadFieldEtagKey(), eTag);
addResponseHeaderFields(CUSTOM_HEAD_FILED_HTML_SHA1, eTag);
} else {
return SonicConstants.ERROR_CODE_CONNECT_IOE;
}
if (requestETag.equals(eTag)) { // 304 case
responseCode = HttpURLConnection.HTTP_NOT_MODIFIED;
return SonicConstants.ERROR_CODE_SUCCESS;
}
}
// When templateTag is empty
String templateTag = getResponseHeaderField(CUSTOM_HEAD_FILED_TEMPLATE_TAG);
if (TextUtils.isEmpty(templateTag)) {
if (TextUtils.isEmpty(serverRsp)) {
readServerResponse(null);
}
if (!TextUtils.isEmpty(serverRsp)) {
separateTemplateAndData();
templateTag = getResponseHeaderField(CUSTOM_HEAD_FILED_TEMPLATE_TAG);
} else {
return SonicConstants.ERROR_CODE_CONNECT_IOE;
}
}
//check If it changes template or update data.
String requestTemplateTag = requestIntent.getStringExtra(SonicSessionConnection.CUSTOM_HEAD_FILED_TEMPLATE_TAG);
if (requestTemplateTag.equals(templateTag)) {
addResponseHeaderFields(SonicSessionConnection.CUSTOM_HEAD_FILED_TEMPLATE_CHANGE, "false");
} else {
addResponseHeaderFields(SonicSessionConnection.CUSTOM_HEAD_FILED_TEMPLATE_CHANGE, "true");
}
return SonicConstants.ERROR_CODE_SUCCESS;
}
复制代码
主要看下readServerResponse
这个方法,它作的就是获取返回数据流并拼接成字符串。
private boolean readServerResponse(AtomicBoolean breakCondition) {
if (TextUtils.isEmpty(serverRsp)) {
BufferedInputStream bufferedInputStream = connectionImpl.getResponseStream();
if (null == bufferedInputStream) {
SonicUtils.log(TAG, Log.ERROR, "session(" + session.sId + ") readServerResponse error: bufferedInputStream is null!");
return false;
}
try {
byte[] buffer = new byte[session.config.READ_BUF_SIZE];
int n = 0;
while (((breakCondition == null) || !breakCondition.get()) && -1 != (n = bufferedInputStream.read(buffer))) {
outputStream.write(buffer, 0, n);
}
if (n == -1) {
serverRsp = outputStream.toString(session.getCharsetFromHeaders());
}
} catch (Exception e) {
SonicUtils.log(TAG, Log.ERROR, "session(" + session.sId + ") readServerResponse error:" + e.getMessage() + ".");
return false;
}
}
return true;
}
复制代码
让咱们再次回到handleFlow_Connection
方法
// When cacheHtml is empty, run First-Load flow
if (!hasCache) {
handleFlow_FirstLoad();
return;
}
复制代码
sonic处理的最后
protected void handleFlow_FirstLoad() {
pendingWebResourceStream = server.getResponseStream(wasInterceptInvoked);
if (null == pendingWebResourceStream) {
SonicUtils.log(TAG, Log.ERROR, "session(" + sId + ") handleFlow_FirstLoad error:server.getResponseStream is null!");
return;
}
String htmlString = server.getResponseData(false);
boolean hasCompletionData = !TextUtils.isEmpty(htmlString);
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleFlow_FirstLoad:hasCompletionData=" + hasCompletionData + ".");
mainHandler.removeMessages(CLIENT_CORE_MSG_PRE_LOAD);
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);
for (WeakReference<SonicSessionCallback> ref : sessionCallbackList) {
SonicSessionCallback callback = ref.get();
if (callback != null) {
callback.onSessionFirstLoad(htmlString);
}
}
String cacheOffline = server.getResponseHeaderField(SonicSessionConnection.CUSTOM_HEAD_FILED_CACHE_OFFLINE);
if (SonicUtils.needSaveData(config.SUPPORT_CACHE_CONTROL, cacheOffline, server.getResponseHeaderFields())) {
if (hasCompletionData && !wasLoadUrlInvoked.get() && !wasInterceptInvoked.get()) { // Otherwise will save cache in com.tencent.sonic.sdk.SonicSession.onServerClosed
switchState(STATE_RUNNING, STATE_READY, true);
postTaskToSaveSonicCache(htmlString);
}
} else {
SonicUtils.log(TAG, Log.INFO, "session(" + sId + ") handleFlow_FirstLoad:offline->" + cacheOffline + " , so do not need cache to file.");
}
}
复制代码
建立ResponseStream用于在webview加载资源的时候进行返回,而且移除CLIENT_CORE_MSG_PRE_LOAD消息,发送CLIENT_CORE_MSG_FIRST_LOAD消息,并进行数据的保存 这样,网页的数据就所有获取到本地了,只等待webview开始加载url时,在shouldInterceptRequest
时返回保存的pendingWebResourceStream就能够实现快速加载了。
Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if (sonicSession != null) {
//返回预载时的数据流
return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url);
}
return null;
}
复制代码