导航

Android WebKit HTML主资源加载过程

Posted on 2014-03-02 20:47  ghostll  阅读(528)  评论(0编辑  收藏  举报

前言

在浏览器里面输入网址,最终浏览器会调用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

我的专栏