編輯:關於Android編程
在浏覽器裡面輸入網址,最終浏覽器會調用WebView的loadUrl(),然後就開始加載整個網頁。整個加載過程中,最重要的一步就是HTML主資源的加載。WebKit將網頁的資源分為主資源(MainResource)和子資源(SubResource)。
主資源:HTML文件。
子資源:CSS, JS, JPG等等,除了HTML文件之外的所有資源都稱之為子資源
本章主要講主資源的加載過程,子資源的加載過程後期會專門詳細的分析和講解。
主資源的請求是從WebView的loadUrl開始的。根據之前《Android WebKit消息處理》的講解,WebView的操作都會有WebViewClassic進行代理。資源加載肯定是由WebCore來處理的,所以,WebVewClassic會發消息給WebViewCore,讓WebViewCore最終將loadUrl傳遞給C++層的WebKit處理:
/**
* See {@link WebView#loadUrl(String, Map)}
*/
@Override
public void loadUrl(String url, Map additionalHttpHeaders) {
loadUrlImpl(url, additionalHttpHeaders);
}
private void loadUrlImpl(String url, Map extraHeaders) {
switchOutDrawHistory();
WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
arg.mUrl = url;
arg.mExtraHeaders = extraHeaders;
mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
clearHelpers();
}
WebViewCore在接收到LOAD_URL之後,會通過BrowserFrame調用nativeLoadUrl,這個BrowserFrame與C++層的mainFrame對接。這裡順便提一下clearHeapers()的作用:如果當前網頁有對話框dialog,有輸入法之類的,clearHelpers就是用來清理這些東西的。這也是為什麼加載一個新頁面的時候,但當前頁面的輸入法以及dialog消失等等。WebViewCore收到消息之後,會直接讓BrowserFrame調用JNI: nativeLoadUrl():
// BrowserFrame.java
public void loadUrl(String url, Map extraHeaders) {
mLoadInitFromJava = true;
if (URLUtil.isJavaScriptUrl(url)) {
// strip off the scheme and evaluate the string
stringByEvaluatingJavaScriptFromString(
url.substring("javascript:".length()));
} else {
/** M: add log */
Xlog.d(XLOGTAG, "browser frame loadUrl: " + url);
nativeLoadUrl(url, extraHeaders);
}
mLoadInitFromJava = false;
} 由於LoadUrl()不僅可以Load一個url,還可以執行一段js。如果load的是一段js,js並沒有被繼續往下load,而是直接在這裡執行掉。stringByEvaluatingJavaScriptFromString也會通過jni調用v8的接口去在mainFrame的scriptController中執行,關於js在WebKit後期會專門寫一篇關於WebKit的js的文章進行專門分析。到目前為止,LoadUrl還只是簡單的使用一個String傳遞字符串而已。// WebCoreFrameBridge.cpp
static void LoadUrl(JNIEnv *env, jobject obj, jstring url, jobject headers)
{
WebCore::Frame* pFrame = GET_NATIVE_FRAME(env, obj);
ALOG_ASSERT(pFrame, "nativeLoadUrl must take a valid frame pointer!");
WTF::String webcoreUrl = jstringToWtfString(env, url);
WebCore::KURL kurl(WebCore::KURL(), webcoreUrl);
WebCore::ResourceRequest request(kurl);
if (headers) {
// dalvikvm will raise exception if any of these fail
jclass mapClass = env->FindClass("java/util/Map");
jmethodID entrySet = env->GetMethodID(mapClass, "entrySet",
"()Ljava/util/Set;");
jobject set = env->CallObjectMethod(headers, entrySet);
jclass setClass = env->FindClass("java/util/Set");
jmethodID iterator = env->GetMethodID(setClass, "iterator",
"()Ljava/util/Iterator;");
jobject iter = env->CallObjectMethod(set, iterator);
jclass iteratorClass = env->FindClass("java/util/Iterator");
jmethodID hasNext = env->GetMethodID(iteratorClass, "hasNext", "()Z");
jmethodID next = env->GetMethodID(iteratorClass, "next",
"()Ljava/lang/Object;");
jclass entryClass = env->FindClass("java/util/Map$Entry");
jmethodID getKey = env->GetMethodID(entryClass, "getKey",
"()Ljava/lang/Object;");
jmethodID getValue = env->GetMethodID(entryClass, "getValue",
"()Ljava/lang/Object;");
while (env->CallBooleanMethod(iter, hasNext)) {
jobject entry = env->CallObjectMethod(iter, next);
jstring key = (jstring) env->CallObjectMethod(entry, getKey);
jstring value = (jstring) env->CallObjectMethod(entry, getValue);
request.setHTTPHeaderField(jstringToWtfString(env, key), jstringToWtfString(env, value));
env->DeleteLocalRef(entry);
env->DeleteLocalRef(key);
env->DeleteLocalRef(value);
}
// ...
pFrame->loader()->load(request, false);
}接下來,在JNI的LoadUrl中就開始創建ResourceRequest,由於WebView的java層面可以對url的請求頭進行設定,然後通過FrameLoader進行加載。這裡的pFrame就是與Java層的BrowserFrame對應的mainFrame。HTML在WebKit的層次上看,最低層的是Frame,然後才有Document,也就意味著HTML Document也是通過Frame的FrameLoader加載的:
pFrame->loader()->load(request, false);
void FrameLoader::load(const ResourceRequest& request, bool lockHistory) void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData, bool lockHistory) void FrameLoader::load(DocumentLoader* newDocumentLoader) void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr其中加載Document的DocumentLoader在load中創建的:prpFormState) void FrameLoader::callContinueLoadAfterNavigationPolicy(void* argument, const ResourceRequest& request, PassRefPtr formState, bool shouldContinue) void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr formState, bool shouldContinue) void FrameLoader::continueLoadAfterWillSubmitForm()
void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData, bool lockHistory)
{
if (m_inStopAllLoaders)
return;
// FIXME: is this the right place to reset loadType? Perhaps this should be done after loading is finished or aborted.
m_loadType = FrameLoadTypeStandard;
RefPtr loader = m_client->createDocumentLoader(request, substituteData);
if (lockHistory && m_documentLoader)
loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory().string() : m_documentLoader->clientRedirectSourceForHistory());
load(loader.get());
} 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了:// FrameLoader.cpp
void FrameLoader::continueLoadAfterWillSubmitForm()
{
// ...
m_provisionalDocumentLoader->timing()->navigationStart = currentTime();
// ...
if (!m_provisionalDocumentLoader->startLoadingMainResource(identifier))
m_provisionalDocumentLoader->updateLoading();
}startLoadingMainResource這就開始load主資源也就是前面說的html文件。這裡需要對m_provisionalDocumentLoader進行講解下:
RefPtr我們可以看到在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。m_documentLoader; RefPtr m_provisionalDocumentLoader; RefPtr m_policyDocumentLoader; void setDocumentLoader(DocumentLoader*); void setPolicyDocumentLoader(DocumentLoader*); void setProvisionalDocumentLoader(DocumentLoader*);
// FrameLoader.cpp void FrameLoader::loadWithDocumentLoader(DocumentLoader* loader, FrameLoadType type, PassRefPtr上面代碼片段可以看出,這三個DocumentLoader的承接關系是一環扣一環。由於index.html加載在WebKit中分為2中方式:如果是前進後退,index.html是從CachedPage中加載的,FrameLoader::transitionToCommitted就是在從CachedPage中加載完成之後被調用的,void FrameLoader::checkLoadCompleteForThisFrame()這是在從網絡加載完成之後被調用的。prpFormState) { // ... policyChecker()->stopCheck(); // ... setPolicyDocumentLoader(loader); // .. } void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr formState, bool shouldContinue) { // ... setProvisionalDocumentLoader(m_policyDocumentLoader.get()); m_loadType = type; setState(FrameStateProvisional); // ... setPolicyDocumentLoader(0); } void FrameLoader::transitionToCommitted(PassRefPtr cachedPage) { // ... setDocumentLoader(m_provisionalDocumentLoader.get()); setProvisionalDocumentLoader(0); // ... } void FrameLoader::checkLoadCompleteForThisFrame() { switch (m_state) { case FrameStateProvisional: { // ... // If we're in the middle of loading multipart data, we need to restore the document loader. if (isReplacing() && !m_documentLoader.get()) setDocumentLoader(m_provisionalDocumentLoader.get()); // Finish resetting the load state, but only if another load hasn't been started by the // delegate callback. if (pdl == m_provisionalDocumentLoader) clearProvisionalLoad(); } // ... }
// FrameLoader.cpp
void FrameLoader::recursiveCheckLoadComplete()
{
Vector frames;
for (RefPtr<frame> frame = m_frame->tree()->firstChild(); frame; frame = frame->tree()->nextSibling())
frames.append(frame);
unsigned size = frames.size();
for (unsigned i = 0; i < size; i++)
frames[i]->loader()->recursiveCheckLoadComplete();
checkLoadCompleteForThisFrame();
}
// Called every time a resource is completely loaded, or an error is received.
void FrameLoader::checkLoadComplete()
{
ASSERT(m_client->hasWebView());
m_shouldCallCheckLoadComplete = false;
// FIXME: Always traversing the entire frame tree is a bit inefficient, but
// is currently needed in order to null out the previous history item for all frames.
if (Page* page = m_frame->page())
page->mainFrame()->loader()->recursiveCheckLoadComplete();
} 需要強調的是,WebKit需要對Page裡面的所有Frame進行確認加載完畢之後,最後將setDocumentLoader()。對於這一點我個人理解是還有優化的空間。bool DocumentLoader::startLoadingMainResource(unsigned long identifier) bool MainResourceLoader::load(const ResourceRequest& r, const SubstituteData& substituteData) bool MainResourceLoader::loadNow(ResourceRequest& r) PassRefPtr需要指出的是,雖然LoadUrl最後是在WebCore線程中執行的,但是最後資源下載是在Chromium_net的IO線程中進行的。在資源下載完畢之後,網絡數據會交給FrameLoaderClientAndroidResourceHandle::create(NetworkingContext* context, const ResourceRequest& request, ResourceHandleClient* client, bool defersLoading, bool shouldContentSniff) bool ResourceHandle::start(NetworkingContext* context) PassRefPtr ResourceLoaderAndroid::start( ResourceHandle* handle, const ResourceRequest& request, FrameLoaderClient* client, bool isMainResource, bool isSync) bool WebUrlLoaderClient::start(bool isMainResource, bool isMainFrame, bool sync, WebRequestContext* context)
Android WebKit數據下載在Chromium_net的IO線程中完成之後會通過WebUrlLoaderClient向WebCore提交數據。WebKt的調用棧如下:
// Finish
void WebUrlLoaderClient::didFinishLoading()
void ResourceLoader::didFinishLoading(ResourceHandle*, double finishTime)
void MainResourceLoader::didFinishLoading(double finishTime)
void FrameLoader::finishedLoading()
void DocumentLoader::finishedLoading()
void FrameLoader::finishedLoadingDocument(DocumentLoader* loader)
void FrameLoaderClientAndroid::finishedLoading(DocumentLoader* docLoader)
void FrameLoaderClientAndroid::committedLoad(DocumentLoader* loader,
const char* data, int length)
void DocumentLoader::commitData(const char* bytes, int length)
// Receive Data
void WebUrlLoaderClient::didReceiveData(scoped_refptr buf, int size)
void ResourceLoader::didReceiveData(ResourceHandle*, const char* data, int length,
int encodedDataLength)
void ResourceLoader::didReceiveData(const char* data, int length,
long long encodedDataLength, bool allAtOnce)
void MainResourceLoader::addData(const char* data, int length, bool allAtOnce)
void DocumentLoader::receivedData(const char* data, int length)
void DocumentLoader::commitLoad(const char* data, int length)
void FrameLoaderClientAndroid::committedLoad(DocumentLoader* loader,
const char* data, int length)
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
Android Studio添加Parcelable序列化小工具(快速提高開發效率)
Android Studio添加Parcelable序列化小工具(快速提高開發效率)Android Studio是google專門為開發Android提供的開發工具,在它
Android常用對話框使用大全
日常生活中我們隨處可見對話框,上面有很多提示信息,更加方便提示用戶進行不同的操作。一、對話框的兩個特點和一些常見的對話框1.當彈出對話框是會結束UI線程(即主線程);2.
15、Android數據存儲——SharedPreferences及SDCard
一、數據存儲選項:Data Storage ——Storage Options【重點】 1、Shared Preferences Stor
圖文詳解Android屬性動畫
Android中的動畫分為視圖動畫(View Animation)、屬性動畫(Property Animation)以及Drawable動畫。從Android