• Android WebKit HTML主资源加载过程


    前言

    在浏览器里面输入网址,最终浏览器会调用WebView的loadUrl(),然后就开始加载整个网页。整个加载过程中,最重要的一步就是HTML主资源的加载。WebKit将网页的资源分为主资源(MainResource)和子资源(SubResource)。

    WebKit资源分类

    主资源:HTML文件。

    子资源:CSS, JS, JPG等等,除了HTML文件之外的所有资源都称之为子资源

    本章主要讲主资源的加载过程,子资源的加载过程后期会专门详细的分析和讲解。

    主资源请求

    LoadUrl

    主资源的请求是从WebView的loadUrl开始的。根据之前《Android WebKit消息处理》的讲解,WebView的操作都会有WebViewClassic进行代理。资源加载肯定是由WebCore来处理的,所以,WebVewClassic会发消息给WebViewCore,让WebViewCore最终将loadUrl传递给C++层的WebKit处理:

    [cpp] view plaincopyprint?
     
    1. /** 
    2.  * See {@link WebView#loadUrl(String, Map)} 
    3.  */  
    4. @Override  
    5. public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {  
    6.     loadUrlImpl(url, additionalHttpHeaders);  
    7. }  
    8.   
    9. private void loadUrlImpl(String url, Map<String, String> extraHeaders) {  
    10.     switchOutDrawHistory();  
    11.     WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();  
    12.     arg.mUrl = url;  
    13.     arg.mExtraHeaders = extraHeaders;  
    14.     mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);  
    15.     clearHelpers();  
    16. }  

    WebViewCore在接收到LOAD_URL之后,会通过BrowserFrame调用nativeLoadUrl,这个BrowserFrame与C++层的mainFrame对接。这里顺便提一下clearHeapers()的作用:如果当前网页有对话框dialog,有输入法之类的,clearHelpers就是用来清理这些东西的。这也是为什么加载一个新页面的时候,但当前页面的输入法以及dialog消失等等。WebViewCore收到消息之后,会直接让BrowserFrame调用JNI:  nativeLoadUrl():

    [java] view plaincopyprint?
     
    1. // BrowserFrame.java  
    2.     public void loadUrl(String url, Map<String, String> extraHeaders) {  
    3.         mLoadInitFromJava = true;  
    4.         if (URLUtil.isJavaScriptUrl(url)) {  
    5.             // strip off the scheme and evaluate the string  
    6.             stringByEvaluatingJavaScriptFromString(  
    7.                     url.substring("javascript:".length()));  
    8.         } else {  
    9.             /** M: add log */  
    10.             Xlog.d(XLOGTAG, "browser frame loadUrl: " + url);  
    11.             nativeLoadUrl(url, extraHeaders);  
    12.         }  
    13.         mLoadInitFromJava = false;  
    14.     }  

    由于LoadUrl()不仅可以Load一个url,还可以执行一段js。如果load的是一段js,js并没有被继续往下load,而是直接在这里执行掉。stringByEvaluatingJavaScriptFromString也会通过jni调用v8的接口去在mainFrame的scriptController中执行,关于js在WebKit后期会专门写一篇关于WebKit的js的文章进行专门分析。到目前为止,LoadUrl还只是简单的使用一个String传递字符串而已。

    [cpp] view plaincopyprint?
     
    1. // WebCoreFrameBridge.cpp  
    2. static void LoadUrl(JNIEnv *env, jobject obj, jstring url, jobject headers)  
    3. {  
    4.     WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj);  
    5.     ALOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!");  
    6.   
    7.     WTF::String webcoreUrl = jstringToWtfString(env, url);  
    8.     WebCore::KURL kurl(WebCore::KURL(), webcoreUrl);  
    9.     WebCore::ResourceRequest request(kurl);  
    10.     if (headers) {  
    11.         // dalvikvm will raise exception if any of these fail  
    12.         jclass mapClass = env->FindClass("java/util/Map");  
    13.         jmethodID entrySet = env->GetMethodID(mapClass, "entrySet",  
    14.                 "()Ljava/util/Set;");  
    15.         jobject set = env->CallObjectMethod(headers, entrySet);  
    16.   
    17.         jclass setClass = env->FindClass("java/util/Set");  
    18.         jmethodID iterator = env->GetMethodID(setClass, "iterator",  
    19.                 "()Ljava/util/Iterator;");  
    20.         jobject iter = env->CallObjectMethod(set, iterator);  
    21.   
    22.         jclass iteratorClass = env->FindClass("java/util/Iterator");  
    23.         jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext""()Z");  
    24.         jmethodID next = env->GetMethodID(iteratorClass, "next",  
    25.                 "()Ljava/lang/Object;");  
    26.         jclass entryClass = env->FindClass("java/util/Map$Entry");  
    27.         jmethodID getKey = env->GetMethodID(entryClass, "getKey",  
    28.                 "()Ljava/lang/Object;");  
    29.         jmethodID getValue = env->GetMethodID(entryClass, "getValue",  
    30.                 "()Ljava/lang/Object;");  
    31.   
    32.         while (env->CallBooleanMethod(iter, hasNext)) {  
    33.             jobject entry = env->CallObjectMethod(iter, next);  
    34.             jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
    35.             jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
    36.             request.setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, value));  
    37.             env->DeleteLocalRef(entry);  
    38.             env->DeleteLocalRef(key);  
    39.             env->DeleteLocalRef(value);  
    40.         }  
    41.     // ...  
    42.     pFrame->loader()->load(request, false);  
    43. }  

    接下来,在JNI的LoadUrl中就开始创建ResourceRequest,由于WebView的java层面可以对url的请求头进行设定,然后通过FrameLoader进行加载。这里的pFrame就是与Java层的BrowserFrame对应的mainFrame。HTML在WebKit的层次上看,最低层的是Frame,然后才有Document,也就意味着HTML Document也是通过Frame的FrameLoader加载的:

    [cpp] view plaincopyprint?
     
    1. pFrame->loader()->load(request, false);  

    调用栈

    最后的这句话就是让FrameLoader去加载url的request。后面的调用栈依次是:

    [cpp] view plaincopyprint?
     
    1. void FrameLoader::load(const ResourceRequest& request, bool lockHistory)  
    2. void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData, bool lockHistory)  
    3. void FrameLoader::load(DocumentLoader* newDocumentLoader)  
    4. void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState)  
    5. void FrameLoader::callContinueLoadAfterNavigationPolicy(void* argument,  
    6.     const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue)  
    7. void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState> formState, bool shouldContinue)  
    8. void FrameLoader::continueLoadAfterWillSubmitForm()  

    其中加载Document的DocumentLoader在load中创建的:

    [cpp] view plaincopyprint?
     
    1. void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData, bool lockHistory)  
    2. {  
    3.     if (m_inStopAllLoaders)  
    4.         return;  
    5.           
    6.     // FIXME: is this the right place to reset loadType? Perhaps this should be done after loading is finished or aborted.  
    7.     m_loadType = FrameLoadTypeStandard;  
    8.     RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(request, substituteData);  
    9.     if (lockHistory && m_documentLoader)  
    10.         loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory().string() : m_documentLoader->clientRedirectSourceForHistory());  
    11.     load(loader.get());  
    12. }  

    m_client->createDocumentLoader(request, substituteData);中的m_client是FrameLoaderClientAndroid。后面资源下载还有跟这个m_client打交道。在void FrameLoader::continueLoadAfterWillSubmitForm()之前,还没有真正涉及到主资源的加载,还都只是在对当前需要加载的Url进行一些列的判断,一方面是安全问题,SecurityOrigin会对Url进行安全检查,例如跨域。另一方面是Scroll,因为有时候后LoadUrl加载的Url会带有Url Fragment也就是hash。关于url的hash的内容请参考《Fragment URLS》由于URL的hash,只会滚动到页面的某一个位置,所以这种情况下也不需要真正的去请求mainResource. 如果这些检查都过了,就需要开始去加载mainResource了:

    [cpp] view plaincopyprint?
     
    1. // FrameLoader.cpp  
    2. void FrameLoader::continueLoadAfterWillSubmitForm()  
    3. {  
    4.     // ...  
    5.     m_provisionalDocumentLoader->timing()->navigationStart = currentTime();  
    6.   
    7.     // ...  
    8.     if (!m_provisionalDocumentLoader->startLoadingMainResource(identifier))  
    9.         m_provisionalDocumentLoader->updateLoading();  
    10. }  

    startLoadingMainResource这就开始load主资源也就是前面说的html文件。

    三种DocumentLoader

    这里需要对m_provisionalDocumentLoader进行讲解下:

    [cpp] view plaincopyprint?
     
    1. RefPtr<DocumentLoader> m_documentLoader;  
    2. RefPtr<DocumentLoader> m_provisionalDocumentLoader;  
    3. RefPtr<DocumentLoader> m_policyDocumentLoader;  
    4. void setDocumentLoader(DocumentLoader*);  
    5. void setPolicyDocumentLoader(DocumentLoader*);  
    6. void setProvisionalDocumentLoader(DocumentLoader*);  

    我们可以看到在FrameLoader.h中定义了三个DocumentLoader,WebKit其实是按角色划分这几个DocumentLoader的。其中:m_documentLoader是上一次已经加载过的DocumentLoader的指针,m_policyDocumentLoader就是用来做一些策略性的工作的,例如延迟加载等等。m_provisionalDocumentLoade是用来做实际的加载工作的。当一个DocumentLoader的工作完成之后,会通过setXXXXDocumentLoader来传递指针。按照URL加载的主流程:PolicyChcek------>Load MainResouce。也就是先进行策略检查,最后才开始加载主资源。那么这个三个DocumentLoader的顺序应该是先createDocumentLoader后的指针传递给m_pollicyDocumentLoader,在策略检查完之后,将指针传递给m_provisionalDocumentLoader,在Document加载完毕之后,将指针传递给m_documentLoader。

    [cpp] view plaincopyprint?
     
    1. // FrameLoader.cpp  
    2. void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr<FormState> prpFormState)  
    3. {  
    4.     // ...     
    5.     policyChecker()->stopCheck();  
    6.     // ...  
    7.     setPolicyDocumentLoader(loader);  
    8.     // ..  
    9. }  
    10.   
    11. void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState> formState, bool shouldContinue)  
    12. {  
    13.     // ...  
    14.     setProvisionalDocumentLoader(m_policyDocumentLoader.get());  
    15.     m_loadType = type;  
    16.     setState(FrameStateProvisional);  
    17.     // ...  
    18.     setPolicyDocumentLoader(0);  
    19.   
    20. }  
    21.   
    22. void FrameLoader::transitionToCommitted(PassRefPtr<CachedPage> cachedPage)  
    23. {  
    24.     // ...  
    25.     setDocumentLoader(m_provisionalDocumentLoader.get());  
    26.     setProvisionalDocumentLoader(0);  
    27.     // ...  
    28. }  
    29.   
    30. void FrameLoader::checkLoadCompleteForThisFrame()  
    31. {  
    32.     switch (m_state) {  
    33.         case FrameStateProvisional: {  
    34.                 // ...  
    35.   
    36.                 // If we're in the middle of loading multipart data, we need to restore the document loader.  
    37.                 if (isReplacing() && !m_documentLoader.get())  
    38.                     setDocumentLoader(m_provisionalDocumentLoader.get());  
    39.   
    40.                 // Finish resetting the load state, but only if another load hasn't been started by the  
    41.                 // delegate callback.  
    42.                 if (pdl == m_provisionalDocumentLoader)  
    43.                     clearProvisionalLoad();  
    44.                   
    45.     }  
    46.   
    47.     // ...  
    48. }  

    上面代码片段可以看出,这三个DocumentLoader的承接关系是一环扣一环。由于index.html加载在WebKit中分为2中方式:如果是前进后退,index.html是从CachedPage中加载的,FrameLoader::transitionToCommitted就是在从CachedPage中加载完成之后被调用的,void FrameLoader::checkLoadCompleteForThisFrame()这是在从网络加载完成之后被调用的。

    [cpp] view plaincopyprint?
     
    1. // FrameLoader.cpp  
    2. void FrameLoader::recursiveCheckLoadComplete()  
    3. {  
    4.     Vector<RefPtr<Frame>, 10> frames;  
    5.       
    6.     for (RefPtr<Frame> frame = m_frame->tree()->firstChild(); frame; frame = frame->tree()->nextSibling())  
    7.         frames.append(frame);  
    8.       
    9.     unsigned size = frames.size();  
    10.     for (unsigned i = 0; i < size; i++)  
    11.         frames[i]->loader()->recursiveCheckLoadComplete();  
    12.       
    13.     checkLoadCompleteForThisFrame();  
    14. }  
    15.   
    16. // Called every time a resource is completely loaded, or an error is received.  
    17. void FrameLoader::checkLoadComplete()  
    18. {  
    19.     ASSERT(m_client->hasWebView());  
    20.       
    21.     m_shouldCallCheckLoadComplete = false;  
    22.   
    23.     // FIXME: Always traversing the entire frame tree is a bit inefficient, but   
    24.     // is currently needed in order to null out the previous history item for all frames.  
    25.     if (Page* page = m_frame->page())  
    26.         page->mainFrame()->loader()->recursiveCheckLoadComplete();  
    27. }  

    需要强调的是,WebKit需要对Page里面的所有Frame进行确认加载完毕之后,最后将setDocumentLoader()。对于这一点我个人理解是还有优化的空间。

    startLoadingMainResource

    在m_provisionalDocumentLoader调用startLoadingMainResource之后,就开始准备发送网络请求了。调用栈如下:

    [cpp] view plaincopyprint?
     
    1. bool DocumentLoader::startLoadingMainResource(unsigned long identifier)  
    2. bool MainResourceLoader::load(const ResourceRequest& r, const SubstituteData& substituteData)  
    3. bool MainResourceLoader::loadNow(ResourceRequest& r)  
    4. PassRefPtr<ResourceHandle> ResourceHandle::create(NetworkingContext* context,   
    5.     const ResourceRequest& request,  
    6.     ResourceHandleClient* client,  
    7.     bool defersLoading,  
    8.     bool shouldContentSniff)  
    9. bool ResourceHandle::start(NetworkingContext* context)  
    10. PassRefPtr<ResourceLoaderAndroid> ResourceLoaderAndroid::start(  
    11.         ResourceHandle* handle, const ResourceRequest& request,  
    12.     FrameLoaderClient* client, bool isMainResource, bool isSync)  
    13. bool WebUrlLoaderClient::start(bool isMainResource, bool isMainFrame, bool sync, WebRequestContext* context)  

    需要指出的是,虽然LoadUrl最后是在WebCore线程中执行的,但是最后资源下载是在Chromium_net的IO线程中进行的。在资源下载完毕之后,网络数据会交给FrameLoaderClientAndroid

    网络数据

    Android WebKit数据下载在Chromium_net的IO线程中完成之后会通过WebUrlLoaderClient向WebCore提交数据。WebKt的调用栈如下:

    [cpp] view plaincopyprint?
     
    1. // Finish  
    2. void WebUrlLoaderClient::didFinishLoading()  
    3. void ResourceLoader::didFinishLoading(ResourceHandle*, double finishTime)  
    4. void MainResourceLoader::didFinishLoading(double finishTime)  
    5. void FrameLoader::finishedLoading()  
    6. void DocumentLoader::finishedLoading()  
    7. void FrameLoader::finishedLoadingDocument(DocumentLoader* loader)  
    8. void FrameLoaderClientAndroid::finishedLoading(DocumentLoader* docLoader)  
    9. void FrameLoaderClientAndroid::committedLoad(DocumentLoader* loader,   
    10.      const char* data, int length)  
    11. void DocumentLoader::commitData(const char* bytes, int length)  
    12.   
    13.   
    14.   
    15.   
    16. // Receive Data  
    17. void WebUrlLoaderClient::didReceiveData(scoped_refptr<net::IOBuffer> buf, int size)  
    18. void ResourceLoader::didReceiveData(ResourceHandle*, const char* data, int length,   
    19.      int encodedDataLength)  
    20. void ResourceLoader::didReceiveData(const char* data, int length,  
    21.      long long encodedDataLength, bool allAtOnce)  
    22. void MainResourceLoader::addData(const char* data, int length, bool allAtOnce)  
    23. void DocumentLoader::receivedData(const char* data, int length)  
    24. void DocumentLoader::commitLoad(const char* data, int length)  
    25. void FrameLoaderClientAndroid::committedLoad(DocumentLoader* loader,  
    26.      const char* data, int length)  
    27. void DocumentLoader::commitData(const char* bytes, int length)  

    这个过程其实分为两步,一步是Chromium_net收到数据,另一部是Chromium_net通知WebKit,数据已经下载完毕可以finish了。这个两个过程都会调用FrameLoaderClienetAndroid::committedLoad()。只不过参数不一样,在finish的时候,将传入的length为0,这样通知WebKit,数据已经传送完毕,记者WebKit就开始使用commitData拿到的数据进行解析,构建Dom Tree和Render Tree。关于Dom Tree Render Tree的构建过程下一节详细的讲述。

    版权申明:
    转载文章请注明原文出处,任何用于商业目的,请联系谭海燕本人:hyman_tan@126.com

  • 相关阅读:
    CountDownLatch demo演示裁判和选手赛跑
    @Async异步方法对异常的处理,从内层向外层抛出机制
    python3读csv文件,出现UnicodeDecodeError: 'utf8' codec can't decode byte 0xd0 in position 0: invalid con
    Python3 dict和str互转
    python批量读取excel csv文件插入mysql数据库
    dotnet 6 精细控制 HttpClient 网络请求超时
    读书笔记 为什么要有R5G6B5颜色格式
    dotnet 6 推荐一个可代替 .NET Remoting 的 IPC 库
    dotnet 6 通过 DOTNET_ROOT 让调起的应用的进程拿到共享的运行时文件夹
    记 Win8.1 某应用渲染抛出 OutOfMemoryException 异常及修复方法
  • 原文地址:https://www.cnblogs.com/ghostll/p/3577257.html
Copyright © 2020-2023  润新知