痛点是什么?

网页加载迟缓,白屏,应用卡顿。

为何有这种问题?

1.调用loadUrl()办法的时候,才会开始网页加载流程 2.js臃肿问题 3.加载图片太多 4.webview自身问题

webiew是怎么加载网页的呢?

webview初始化->DOM下载→DOM解析→CSS申请+下载→CSS解析→渲染→绘制→合成

优化方向是?

1.webview自身优化

  • 提前内核初始化 代码:
public class App extends Application {    private WebView mWebView ;    @Override    public void onCreate() {        super.onCreate();        mWebView = new WebView(new MutableContextWrapper(this));    }}

成果:见下图

  • webview复用池 代码:
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();        }    }}

带来的问题:内存透露 应用事后创立以及复用池后的成果

  • 独立过程,过程预加载 代码:
        <service            android:name=".PreWebService"            android:process=":web"/>        <activity            android:name=".WebActivity"            android:process=":web"/>

启动webview页背后,先启动PreWebService把[web]过程创立了,当启动WebActivity时,零碎发发现[web]过程曾经存在了,就不须要破费工夫Fork出新的[web]过程了。

  • 应用x5内核 间接应用腾讯的x5内核,替换原生的浏览器内核
  • 成果:

    • 首次关上

    • 二次关上

  • 其余的解决方案: 1.设置webview缓存 2.加载动画/最初让图片下载 3.渲染时关掉图片加载 4.设置超时工夫 5.开启软硬件减速

2.加载资源时的优化 这种优化多应用第三方,上面有介绍

3.网页端的优化 由网页的前端工程师优化网页,或者说是和挪动端一起,将网页实现增量更新,动静更新。app内置css,js文件并管制版本

留神:如果你寄希望于只通过webview的setting来减速网页的加载速度,那你就要悲观了。只批改设置,能做的晋升非常少。所以本文就着重剖析比拟下,当初能够应用的第三方webview框架的优缺点。


VasSonic

    //导入 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:

//创立activitypublic 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逻辑就是网页的预加载 原理:

  • Quick模式 模式分类:

    1. 无缓存模式 流程:

右边的webview流程:webview初始化后调用SonicSession的onClientReady办法,告知 webview曾经初始化结束。

client.clientReady();

左边的sonic流程:

  1. 创立SonicEngine对象
  2. 通过SonicCacheInterceptor获取本地缓存的url数据
  3. 数据为空就发送一个CLIENT\_CORE\_MSG\_PRE\_LOAD的音讯到主线程
  4. 通过SonicSessionConnection建设一个URLConnection
  5. 连贯获取服务器返回的数据,并在读取网络数据的时候一直判断webview是否发动资源拦挡申请。如果发了,就中断网络数据的读取,把曾经读取的和未读取的数据拼接成桥接流SonicSessionStream并赋值给SonicSession的pendingWebResourceStream,如果网络读取实现后webview还没有初始化实现,就会cancel掉CLIENT\_CORE\_MSG\_PRE\_LOAD音讯,同时发送CLIENT\_CORE\_MSG\_FIRST\_LOAD音讯
  6. 之后再对html内容进行模版宰割及数据保留
  7. 如果webview解决了CLIENT\_CORE\_MSG\_PRE\_LOAD这个音讯,它就会调用webview的loadUrl,之后webview会调用本身的资源拦挡办法,在这个办法中,会将之前保留的pendingWebResourceStream返回给webview让其解析渲染,
  8. 如果webview解决的是CLIENT\_CORE\_MSG\_FIRST\_LOAD音讯,webview如果没有loadUrl过就会调用loadDataWithBaseUrl办法加载之前读取的网络数据,这样webview就能够间接做解析渲染了。

2.有缓存模式 齐全缓存流程: 右边webview的流程跟无缓存统一,左边sonic的流程会通过SonicCacheInterceptor获取本地数据是否为空,不为空就会产生CLIENT\_CORE\_MSG\_PRE\_LOAD音讯,之后webview就会应用loadDataWithBaseUrl加载网页进行渲染了

  • 成果

    • 首次关上

    • 二次关上


TBS腾讯浏览服务

集成办法,请依照官网的来操作即可。这里间接放上应用后的效果图吧


百度app计划

来看下百度app对webview解决的计划

  1. 后端直出 后端直出-页面动态直出 后端服务器获取html所有首屏内容,蕴含首屏展示所需的内容和款式。这样客户端获取整个网页并加载时,内核能够间接进行渲染。 这里服务端要提供一个接口给客户端取获取网页的全部内容。而且 获取的网页中一些须要应用客户端的变量的应用宏替换,在客户端加载网页的时候替换成特定的内容,已适应不同用户的设置,例如字体大小、页面色彩等等。 然而这个计划还有些问题就是网络图片没有解决,还是要花费工夫起获取图片。

2.智能预取-提前化网络申请 提前从网络中获取局部落地页html,缓存到本地,当用户点击查看时,只须要从缓存中加载即可。

3.通用拦挡-缓存共享、申请并行 直出解决了文字展示的速度问题,然而图片加载渲染速度还不现实。 借由内核的shouldInterceptRequest回调,拦挡落地页图片申请,由客户端调用图片下载框架进行下载,并以管道形式填充到内核的WebResourceResponse中。就是说在shouldInterceptRequest拦挡所有URL,之后只针对后缀是.PNG/.JPG等图片资源,应用第三方图片下载工具相似于Fresco进行下载并返回一个InputStream。

总结:

  • 提前做:包含预创立WebView和预取数据
  • 并行做:包含图片直出&拦挡加载,框架初始化阶段开启异步线程筹备数据等
  • 轻量化:对于前端来说,要尽量减少页面大小,删减不必要的JS和CSS,不仅能够缩短网络申请工夫,还能晋升内核解析工夫
  • 简单化:对于简略的信息展现页面,对内容动态性要求不高的场景,能够思考应用直出代替hybrid,展现内容间接可渲染,无需JS异步加载
    • *

今日头条计划

那今日头条是怎么解决的呢? 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

整顿下这几个大厂的思路 目标:网页秒开 策略:

  • 针对客户端 1.预创立(application onCreate 时)webview 1.1预创立的同时加载带有css/js的html文本 2.webview复用池 3.webview setting的设置 4.预取网页并缓存,事后获取html并缓存本地,须要是从缓存中加载即可 5.资源拦挡并行加载,内核初始化和资源加载同时进行。
  • 针对服务端 1.直出网页的拼装,服务端时获取网页的全部内容,客户端获取后间接加载 2.客户端本地html资源的版本控制
  • 针对网页前端 1.删减不必要的js/css 2.配合客户端应用VasSonic,只对特定的内容进行页面更新与下载。
    • *

本人的想法:

  1. 网页秒开的这个需要,如果如果只是客户端来做,感觉只是做了一半,最好还是前后端一起致力来优化。
  2. 然而只做客户端方面的优化也是能够的,笔者理论测试了下,通过预取的形式,确实能做到秒开网页。
  3. 往年就上5G了,有可能在5G的网络下,网页加载基本就不是问题了呢。
    • *
小技巧

修复白屏景象:零碎解决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();        }    }

VasSonic 预加载局部源码剖析

前文曾经阐明了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或者StandardSonicSessionhandleFlow_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;            }

相干教程

Android根底系列教程:

Android根底课程U-小结_哔哩哔哩_bilibili

Android根底课程UI-布局_哔哩哔哩_bilibili

Android根底课程UI-控件_哔哩哔哩_bilibili

Android根底课程UI-动画_哔哩哔哩_bilibili

Android根底课程-activity的应用_哔哩哔哩_bilibili

Android根底课程-Fragment应用办法_哔哩哔哩_bilibili

Android根底课程-热修复/热更新技术原理_哔哩哔哩_bilibili

本文转自 https://juejin.cn/post/6844903887111979021,如有侵权,请分割删除。