What are the pain points?
Web pages load slowly, white screen, use lag.
Why is this a problem?
1. The web page loading process starts when the loadUrl() method is called. 2. Loading too many images 4. Webview itself problem
How does Webiew load web pages?
Webview initialization ->DOM download →DOM parsing →CSS request + download →CSS parsing → Rendering → Rendering → Composition
What is the direction of optimization?
1. Webview itself optimization
- Advance kernel initialization code:
public class App extends Application {
private WebView mWebView ;
@Override
public void onCreate(a) {
super.onCreate();
mWebView = new WebView(new MutableContextWrapper(this)); }}Copy the code
Effect: See the picture below
- Webview reuse pool code:
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(a) {
mWebViews = new LinkedBlockingQueue<>();
}
public static WebPools getInstance(a) {
for(; ;) {if(mWebPools ! =null)
return mWebPools;
if (mAtomicReference.compareAndSet(null.new WebPools()))
returnmWebPools=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(newMutableContextWrapper(activity)); }}else {
MutableContextWrapper mMutableContextWrapper = (MutableContextWrapper) mWebView.getContext();
mMutableContextWrapper.setBaseContext(activity);
returnmWebView; }}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(); }}}Copy the code
Problems: Memory leaks using pre-created and reused pools
- Standalone process, process preloaded code:
<service
android:name=".PreWebService"
android:process=":web"/>
<activity
android:name=".WebActivity"
android:process=":web"/>
Copy the code
Before starting the WebView page, start the PreWebService to create the [Web] process. When starting the WebActivity, the system will find that the [Web] process already exists, so there is no need to spend time to Fork a new [Web] process.
- Use x5 kernel directly use Tencent X5 kernel, replace the native browser kernel
- Effect:
- For the first time open
- Secondary open
- For the first time open
- Other solutions: 1. Set up webView cache 2. Load animation/finally let image download 3. Turn off image loading during rendering 4. Set timeout 5. Enable software and hardware acceleration
2. Optimization when loading resources This optimization mostly uses a third party, as described below
3. The optimization of the web page is optimized by the front-end engineer of the web page, or together with the mobile end, the web page will realize incremental update, dynamic update. App built-in CSS, JS files and control version
Note: If you’re hoping to speed up your web page with webView setting alone, you’re going to be disappointed. There is very little improvement that can be made by changing the Settings. Therefore, this article focuses on the analysis and comparison of the advantages and disadvantages of the third-party WebView framework that can be used now.
Now the big factory has the following methods:
- VasSonic
- TBS Tencent Browsing service
- Baidu APP Solution
- Toutiao Program
VasSonic
Reference article: blog.csdn.net/tencent__op… Access method: STEP1:
// Import Tencent/VasSonic implementation'com. Tencent. Sonic: SDK: 3.1.0'
Copy the code
STEP2:
// Create a class that inherits SonicRuntime
// The SonicRuntime class provides the sonic runtime environment, including Context, user UA, user ID, etc. The following code shows several methods of SonicRuntime.
public class TTPRuntime extends SonicRuntime
{
/ / initialization
public TTPRuntime( Context context )
{
super(context);
}
@Override
public void log(
String tag ,
int level ,
String message )
{
/ / the log Settings
}
/ / get a cookie
@Override
public String getCookie( String url )
{
return null;
}
/ / set cookid
@Override
public boolean setCookie( String url , List
cookies )
{
return false;
}
// Obtain user UA information
@Override
public String getUserAgent(a)
{
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";
}
// Get user ID information
@Override
public String getCurrentUserAccount(a)
{
return "ttpp";
}
// Whether to use Sonic acceleration
@Override
public boolean isSonicUrl( String url )
{
return true;
}
// Create a Web resource request
@Override
public Object createWebResourceResponse( String mimeType , String encoding , InputStream data , Map
headers )
,>
{
return null;
}
// Network enabled or not
@Override
public boolean isNetworkValid(a)
{
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 )
{}// Set the Sonic cache address
@Override
public File getSonicCacheDir(a)
{
return super.getSonicCacheDir(); }}Copy the code
STEP3:
// Create a class that inherits SonicSessionClien
//SonicSessionClient is responsible for communicating with webView, such as calling webView loadUrl, loadDataWithBaseUrl and other methods.
public class WebSessionClientImpl extends SonicSessionClient
{
private WebView webView;
/ / bind the webview
public void bindWebView(WebView webView) {
this.webView = webView;
}
// Load the page
@Override
public void loadUrl(String url, Bundle extraData) {
webView.loadUrl(url);
}
// Load the page
@Override
public void loadDataWithBaseUrl(String baseUrl, String data, String mimeType, String encoding, String historyUrl) {
webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
// Load the page
@Override
public void loadDataWithBaseUrlAndHeader( String baseUrl , String data , String mimeType , String encoding , String historyUrl , HashMap
headers )
,>
{
if( headers.isEmpty() )
{
webView.loadDataWithBaseURL( baseUrl, data, mimeType, encoding, historyUrl );
}
else{ webView.loadUrl( baseUrl,headers ); }}}Copy the code
STEP4:
/ / create the 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(a)
{
getWindow().addFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
// Initializations can be placed in the Activity or Application onCreate method
if( !SonicEngine.isGetInstanceAllowed() )
{
SonicEngine.createInstance( new TTPRuntime( getApplication() ),new SonicConfig.Builder().build() );
}
// Set preloading
SonicSessionConfig config = new SonicSessionConfig.Builder().build();
SonicEngine.getInstance().preCreateSession( url,config );
WebSessionClientImpl client = null;
//SonicSessionConfig Sets timeout duration, cache size and other parameters.
// Create a SonicSession object and bind clients to the session. After session creation sonic loads data asynchronously
sonicSession = SonicEngine.getInstance().createSession( url,config );
if( null! = sonicSession ) { sonicSession.bindClient( client =new WebSessionClientImpl() );
}
/ / get the 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() );
}
SonicSession's onClientReady method tells SonicSession that the WebView is ready to start loadUrl when the WebView is ready to initiate loadUrl. In this case, sonic will execute webView logic (loadUrl, loadData, etc.) based on local data.
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest( WebView view , String url )
{
if( sonicSession ! =null )
{
return (WebResourceResponse)sonicSession.getSessionClient().requestResource( url );
}
return null; }});/ / the webview Settings
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);
SonicSession's onClientReady method tells SonicSession that the WebView is ready to start loadUrl when the WebView is ready to initiate loadUrl. In this case, sonic will execute webView logic (loadUrl, loadData, etc.) based on the local data.
if( client ! =null )
{
client.bindWebView( webView );
client.clientReady();
}
else{ webView.loadUrl( url ); }}@Override
public void onBackPressed(a)
{
super.onBackPressed();
}
@Override
protected void onDestroy(a)
{
if( null! = sonicSession ) { sonicSession.destroy(); sonicSession =null;
}
super.onDestroy(); }}Copy the code
Simple analysis of its core idea: parallel, make full use of the webView initialization time for some data processing. An activity that contains a WebView starts with the webView initialization logic and executes sonic logic in parallel. This sonic logic is how web pages are preloaded:
- Quick mode
- No cache mode flow:
- No cache mode flow:
The webView flow on the left: After the webView is initialized, SonicSession’s onClientReady method is called to inform the WebView that it has been initialized.
client.clientReady();
Copy the code
Sonic flow on the right:
- Create SonicEngine objects
- Get locally cached URL data via SonicCacheInterceptor
- If the data is empty, send a CLIENT_CORE_MSG_PRE_LOAD message to the main thread
- Establish a URLConnection with SonicSessionConnection
- The connection obtains the data returned by the server, and continuously determines whether the WebView initiates the resource interception request when reading the network data. If yes, it interrupts the reading of network data, concatenates the read and unread data into a bridge SonicSessionStream and assigns the value to SonicSession’s pendingWebResourceStream. If the webView is not initialized after the network reading is completed, The CLIENT_CORE_MSG_PRE_LOAD message is cancelled and the CLIENT_CORE_MSG_FIRST_LOAD message is sent
- Then the HTML content template segmentation and data storage
- If the webView handles the CLIENT_CORE_MSG_PRE_LOAD message, it calls the loadUrl of the WebView, after which the WebView calls its own resource interceptor method, in which, PendingWebResourceStream is returned to the WebView to parse the render,
- If the webView handles the CLIENT_CORE_MSG_FIRST_LOAD message, the webView will call loadDataWithBaseUrl if the loadUrl has not passed, so that the webView can do the parsing rendering directly.
2. Complete cache process with cache mode: The flow of the webView on the left is the same as that of no cache. The flow of the sonic on the right uses the SonicCacheInterceptor to check whether the local data is empty or not. If not, a CLIENT_CORE_MSG_PRE_LOAD message is generated. Then the webview will load the page with loadDataWithBaseUrl and render it
- The effect
- For the first time open
- Secondary open
- For the first time open
TBS Tencent Browsing service
website
Integration method, please follow the official website to operate. Here directly put the effect picture after use
Baidu APP Solution
Let’s take a look at baidu APP’s webview processing scheme
- Back end Straight Out Back end Straight out – Page static straight out The back end server obtains all the content of the FIRST HTML screen, including the content and style required for the first display. This allows the kernel to render directly when the client retrieves the entire web page and loads it. Here the server should provide an interface to the client to get the full content of the web page. And some of the web pages need to use the client variable use macro replacement, when the client loaded the web page to replace the specific content, has been adapted to different user Settings, such as font size, page color and so on. However, the problem with this scheme is that the network image is not processed, or it takes time to get the image.
2. Intelligent prefetch – Advance Network Request Part of the landing page HTML is obtained from the network in advance and cached locally. When users click to view the HTML, they only need to load it from the cache.
3. General interception-cache sharing and parallel direct request solve the problem of the speed of text presentation, but the speed of image loading and rendering is not ideal. The kernel’s shouldInterceptRequest callback intercepts the image request for the landing page. The client calls the image download framework to download the image and pipe it into the kernel’s WebResourceResponse. That is, shouldInterceptRequest intercepts all urls, and then only the suffix is. PNG/.jpg, using a third-party image download tool like Fresco and returning an InputStream.
Conclusion:
- Do it ahead of time: This includes pre-creating webViews and pre-fetching data
- Parallel work: including picture straight out & intercept loading, frame initialization stage open asynchronous thread to prepare data, etc
- Lightweight: For the front end, minimizing page size and eliminating unnecessary JS and CSS can not only reduce network request times, but also improve kernel parsing times
- Simplicity: For simple information display pages and scenes with low requirements for content dynamics, hybrid can be replaced by direct output. The display contents can be directly rendered without JS asynchronous loading
Toutiao Program
So what about today’s headlines? 1. CSS/JS and other files of article details page are preset in assets folder, and version control can be carried out. 2. 3. The article details page uses a pre-created WebView, which has been pre-loaded with HTML, and then calls JS to set the page content 3. For image resources, use ContentProvider to get them, and images are downloaded using Fresco
content://com.xposed.toutiao.provider.ImageProvider/getimage/origin/eJy1ku0KwiAUhm8l_F3qvuduJSJ0mRO2JtupiNi9Z4MoWiOa65ci nMeX57xXVDda6QPKFld0bLQ9UckbJYlR-UpX3N5Smfi5x3JJ934YxWlKWZhEgbeLhBB-QNFyYUfL1s6uUQFgMkKMtwLA4gJSVwrndUWmUP8CC5xhm87izlKY 7VDeTgLXZUtOlJzjkP6AxXfiR5eMYdMCB9PHneGHBzh-VzEje7AzV3ZvHYpjJV599w-uZWXvWadQR_vlAhtY_Bn2LKuzu_GGOscc1MfZ4veyTyNuuu4G1giVqQ==/6694469396007485965 / 3Copy the code
Tidy up the idea of these several large factory purpose: web page second open strategy:
- For the client 1. Pre-create (application onCreate) WebView 1.1 Pre-create while loading HTML text with CSS/JS 2. Webview reuse pool 3. Prefetch web page and cache, prefetch HTML and cache local, need to load from the cache can 5. Resource interception is loaded in parallel, and kernel initialization and resource loading occur simultaneously.
- For the server 1. Directly out of the web page assembly, the server to get all the content of the web page, the client to get directly loaded 2. Version control of the client’s local HTML resources
- 1. Delete unnecessary JS/CSS; 2. Cooperate with the client to use VasSonic to update and download only specific content.
My own thoughts:
- Page seconds open this demand, if only the client to do, the feeling is only half done, it is best to work together before and after the end to optimize.
- But only do the client side of the optimization is also possible, the author of the actual test, through the way of prefetching, can indeed do second open web pages.
- With 5G coming this year, it’s possible that web loading won’t be a problem at all.
tip
Fix the blank screen: System with a view to draw, with an attribute setDrawDuringWindowsAnimating, this attribute is used to control the window can do animation in the process of normal mapping, and between the Android to Android 4.2 N, For the flow of component switching, this field is false. We can manually modify this property by reflection
/** * Render the page properly */ private voidsetDrawDuringWindowsAnimating(View view) {
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.M || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { // 1 This problem does not exist for Android n or Android 4.1 or lower and does not need to be solvedreturn; } // 4.2 does not existsetDrawDuringWindowsAnimating, need special treatmentif (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
handleDispatchDoneAnimating(view);
return; } try {// 4.3 and above, reflectionsetDrawDuringWindowsAnimating implemented in the process of animation rendering ViewParent rootParent = the getRootView (). The getParent (); Method method = rootParent.getClass() .getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
method.setAccessible(true);
method.invoke(rootParent, true); } catch (Exception e) { e.printStackTrace(); }} / android4.2 can reflect handleDispatchDoneAnimating to solve * * * * / private void handleDispatchDoneAnimating (View paramView) {a try { ViewParentlocalViewParent = paramView.getRootView().getParent();
Class localClass = localViewParent.getClass();
Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
localMethod.setAccessible(true);
localMethod.invoke(localViewParent);
} catch (Exception localException) {
localException.printStackTrace(); }}Copy the code
VasSonic preloads part of the source code analysis
Having explained the main idea of Sonic and the main cache logic, let’s take a look at how it works with the source code.
SonicSessionConfig.Builder sessionConfigBuilder = new SonicSessionConfig.Builder();
sessionConfigBuilder.setSupportLocalServer(true);
// Preload
boolean preloadSuccess = SonicEngine.getInstance().preCreateSession(DEMO_URL, sessionConfigBuilder.build());
Copy the code
Enter the preCreateSession method and take a look
public synchronized boolean preCreateSession(@NonNull String url, @NonNull SonicSessionConfig sessionConfig) {
// Whether the database is ready
if (isSonicAvailable()) {
// Generate a unique sessionId based on the URL and the account set in RunTime
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;
}
// Check whether the preload pool is full
if (preloadSessionPool.size() < config.MAX_PRELOAD_SESSION_COUNT) {
// Network judgment
if (isSessionAvailable(sessionId) && runtime.isNetworkValid()) {
// Create sonicSession to preload
sonicSession = internalCreateSession(sessionId, url, sessionConfig);
if (null! = sonicSession) {// Put it in the pool
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;
}
Copy the code
Analysis: This method only does the sonic session creation work. However, it is created only when the size of the preloadSessionPool is smaller than MAX_PRELOAD_SESSION_COUNT. Let’s move on to the next method internalCreateSession and see how it’s created, okay
private SonicSession internalCreateSession(String sessionId, String url, SonicSessionConfig sessionConfig) {
// The preloaded Session ID is not in the map of the running Session
if(! runningSessionHashMap.containsKey(sessionId)) { SonicSession sonicSession;// Set the cache type
if (sessionConfig.sessionMode == SonicConstants.SESSION_MODE_QUICK) {
// Fast type
sonicSession = new QuickSonicSession(sessionId, url, sessionConfig);
} else {
// Standard type
sonicSession = new StandardSonicSession(sessionId, url, sessionConfig);
}
// Session state change monitor
sonicSession.addSessionStateChangedCallback(sessionCallback);
// Starts the session with true by default
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;
}
Copy the code
This method creates a different cache session type based on the sessionMode type in sessionConfig. QuickSonicSession and StandardSonicSession types. Finally, start the session for preloading. Let’s continue with sonicsession.start ().
public void start(a) {...for (WeakReference<SonicSessionCallback> ref : sessionCallbackList) {
SonicSessionCallback callback = ref.get();
if(callback ! =null) {
// Callback the startup statecallback.onSonicSessionStart(); }}...// Run the preload page method in the session thread
SonicEngine.getInstance().getRuntime().postTaskToSessionThread(new Runnable() {
@Override
public void run(a) {
runSonicFlow(true); }}); . }Copy the code
The main one is runSonicFlow(True), which performs network request operations in sonic’s dedicated thread pool.
private void runSonicFlow(boolean firstRequest) { ... // First requestif(firstRequest) {/ / get the HTML cache is empty cacheHtml = SonicCacheInterceptor. For the first time getSonicCacheData (this); statistics.cacheVerifyTime = System.currentTimeMillis(); SonicUtils.log(TAG, Log.INFO,"session(" + sId + ") runSonicFlow verify cache cost " + (statistics.cacheVerifyTime - statistics.sonicFlowStartTime) + " ms"); // Send messages 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 ()) {// The network does not existif(hasHtmlCache && ! TextUtils.isEmpty(config.USE_SONIC_CACHE_IN_BAD_NETWORK_TOAST)) { runtime.postTaskToMainThread(newRunnable() {
@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{// Start request handleFlow_Connection(hasHtmlCache, sessionData); statistics.connectionFlowFinishTime = System.currentTimeMillis(); }... }Copy the code
Analysis: At the time of the first request, Calling handleFlow_LoadLocalCache actually calls the QuickSonicSession or StandardSonicSession handleFlow_LoadLocalCache that was created earlier to send a message CLIEN T_CORE_MSG_PRE_LOAD and whether it contains cache. Then the handleFlow_Connection(hasHtmlCache, sessionData) method is called for the network presence. Next, go to the handleFlow_Connection method to see how the connection is established.
protected void handleFlow_Connection(boolean hasCache, SonicDataHelper.SessionData sessionData) {...// Create a network request
server = new SonicServer(this, createConnectionIntent(sessionData)); . }Copy the code
It’s a long method so let’s look at it in sections, first of all this creates a SonicServer object, which creates a URLConnection through SonicSessionConnection
public SonicServer(SonicSession session, Intent requestIntent) {
this.session = session;
this.requestIntent = requestIntent;
connectionImpl = SonicSessionConnectionInterceptor.getSonicSessionConnection(session, requestIntent);
}
Copy the code
public static SonicSessionConnection getSonicSessionConnection(SonicSession session, Intent intent) {
SonicSessionConnectionInterceptor interceptor = session.config.connectionInterceptor;
// Whether there is an intercept
if(interceptor ! =null) {
return interceptor.getConnection(session, intent);
}
return new SonicSessionConnection.SessionConnectionDefaultImpl(session, intent);
}
Copy the code
public SessionConnectionDefaultImpl(SonicSession session, Intent intent) {
super(session, intent);
/ / create a URLConnection
connectionImpl = createConnection();
initConnection(connectionImpl);
}
Copy the code
Back to handleFlow_Connection, now that you have created the URLConnection, you can now connect to request data.
int responseCode = server.connect();
Copy the code
protected int connect(a) {
long startTime = System.currentTimeMillis();
// Whether the connection is normal return code
intresultCode = connectionImpl.connect(); .if(SonicConstants.ERROR_CODE_SUCCESS ! = resultCode) {return resultCode; // error case
}
startTime = System.currentTimeMillis();
// Connection request return coderesponseCode = 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;
returnSonicConstants.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 {
returnSonicConstants.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;
}
Copy the code
Take a look at the readServerResponse method, which takes the returned data stream and concatenates it into a string.
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;
}
Copy the code
Let’s go back to the handleFlow_Connection method
// When cacheHtml is empty, run First-Load flow
if(! hasCache) { handleFlow_FirstLoad();return;
}
Copy the code
The end of sonic processing
protected void handleFlow_FirstLoad(a) {
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);
booleanhasCompletionData = ! 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."); }}Copy the code
Create ResponseStream to return when the webView loads the resource, remove the CLIENT_CORE_MSG_PRE_LOAD message, send the CLIENT_CORE_MSG_FIRST_LOAD message, and save the data so that When the webView starts loading the URL, shouldInterceptRequest returns pendingWebResourceStream to enable fast loading.
Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if(sonicSession ! =null) {
// Return the preload data stream
return (WebResourceResponse) sonicSession.getSessionClient().requestResource(url);
}
return null;
}
Copy the code