#############################################

本文为极度寒冰原创,转载请注明出处 #############################################


前面分析了下载的主要流程,当下载资源开始的时候,会有一个类DocumentWriter在backtrace里面总是出现,一直不知道它具体的意思是什么,应该怎么操作。

在android 4.1的平台上,这个类所在的文件路径为: external/webkit/Source/WebCore/loader/DocumentWriter.cpp


可以先看一个堆栈来了解这个类出现的时机:
#0  WebCore::DocumentWriter::end (this=0x2a2aa93c) at external/webkit/Source/WebCore/loader/DocumentWriter.cpp:211#1  0x48d4943a in WebCore::FrameLoader::finishedLoading (this=0x2a146f50) at external/webkit/Source/WebCore/loader/FrameLoader.cpp:2256#2  0x48f38d32 in WebCore::MainResourceLoader::didFinishLoading (this=0x2a37b600, finishTime=0) at external/webkit/Source/WebCore/loader/MainResourceLoader.cpp:475#3  0x48d5105a in WebCore::ResourceLoader::didFinishLoading (this=<value optimized out>, finishTime=<value optimized out>) at external/webkit/Source/WebCore/loader/ResourceLoader.cpp:445#4  0x48e42d52 in android::WebUrlLoaderClient::didFinishLoading (this=0x2a286cb0) at external/webkit/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp:454

在这个堆栈里面,我们可以看到DocumentWriter这个类主要是由DocumentLoader来负责调用的。
看一下code:
DocumentWriter主要有下列的几个方法:
void DocumentWriter::replaceDocument(const String& source)void DocumentWriter::clear()void DocumentWriter::begin()PassRefPtr<Document> DocumentWriter::createDocument(const KURL& url)void DocumentWriter::begin(const KURL& url, bool dispatch, SecurityOrigin* origin)TextResourceDecoder* DocumentWriter::createDecoderIfNeeded()void DocumentWriter::reportDataReceived()void DocumentWriter::addData(const char* str, int len, bool flush)void DocumentWriter::end()void DocumentWriter::endIfNotLoadingMainResource()String DocumentWriter::encoding() constvoid DocumentWriter::setEncoding(const String& name, bool userChosen)void DocumentWriter::setDecoder(TextResourceDecoder* decoder)String DocumentWriter::deprecatedFrameEncoding() constvoid DocumentWriter::setDocumentWasLoadedAsPartOfNavigation()

其中 DocumentWriter::begin(const KURL& url, bool dispatch, SecurityOrigin* origin)是由void DocumentWriter::begin()直接调用。

在接收完数据的时候, WebUrlLoaderClient这个与网络有关的回调类首先接收到回调的信息。 android::WebUrlLoaderClient::didFinishLoading接收到了回调的信息。
然后一路回调MainResourceLoader和FrameLoader去处理这个接收完的信息。
在FrameLoader中,DocumentLoader类的finishedLoading方法被调用,而在DocumentLoading里面,WebCore::DocumentWriter::end方法被使用。

这样的话,在DocumentLoader中维护了DocumentWriter成员,那么在DocumentLoader中就会使用DocumentWriter来处理接收到的数据。
为什么说DocumentWrite来处理接收到的数据呢?
下面这个堆栈说明了一定的问题:
#0  WebCore::DocumentLoader::commitData (this=0x2a3f7fb8,     bytes=0x2a85f880 "<!DOCTYPE html>\r\n<html><!--STATUS OK--><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,"..., length=11653) at external/webkit/Source/WebCore/loader/DocumentLoader.cpp:311#1  0x48f8be5c in android::FrameLoaderClientAndroid::committedLoad (this=0x2a11ef20, loader=0x2a3f7fb8,     data=0x2a85f880 "<!DOCTYPE html>\r\n<html><!--STATUS OK--><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,"..., length=11653) at external/webkit/Source/WebKit/android/WebCoreSupport/FrameLoaderClientAndroid.cpp:732#2  0x48f36a6c in WebCore::DocumentLoader::commitLoad (this=0x2a3f7fb8,     data=0x2a85f880 "<!DOCTYPE html>\r\n<html><!--STATUS OK--><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,"..., length=11653) at external/webkit/Source/WebCore/loader/DocumentLoader.cpp:307#3  0x48d51310 in WebCore::ResourceLoader::didReceiveData (this=0x2a25e708,     data=0x2a85f880 "<!DOCTYPE html>\r\n<html><!--STATUS OK--><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,"..., length=11653, encodedDataLength=11653, allAtOnce=false) at external/webkit/Source/WebCore/loader/ResourceLoader.cpp:288#4  0x48f38cca in WebCore::MainResourceLoader::didReceiveData (this=<value optimized out>,     data=0x2a85f880 "<!DOCTYPE html>\r\n<html><!--STATUS OK--><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,"..., length=11653, encodedDataLength=11653, allAtOnce=false) at external/webkit/Source/WebCore/loader/MainResourceLoader.cpp:454#5  0x48d5104e in WebCore::ResourceLoader::didReceiveData (this=<value optimized out>, data=<value optimized out>, length=<value optimized out>, encodedDataLength=11653)    at external/webkit/Source/WebCore/loader/ResourceLoader.cpp:439#6  0x48e4229a in android::WebUrlLoaderClient::didReceiveData (this=0x2a259458, buf=<value optimized out>, size=11653) at external/webkit/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp:398#7  0x48e441b2 in DispatchToMethod<android::WebUrlLoaderClient, void (android::WebUrlLoaderClient::*)(scoped_refptr<net::IOBuffer>, int), scoped_refptr<net::IOBuffer>, int> (this=0x2a25d7b0)    at external/chromium/base/tuple.h:558#8  RunnableMethod<android::WebUrlLoaderClient, void (android::WebUrlLoaderClient::*)(scoped_refptr<net::IOBuffer>, int), Tuple2<scoped_refptr<net::IOBuffer>, int> >::Run (this=0x2a25d7b0)    at external/chromium/base/task.h:332#9  0x48e41f1a in RunTask (v=<value optimized out>) at external/webkit/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp:343

WebUrlLoaderClient这个网络相关的回调类接收到了回调的信息,这个时候的数据正在被下载,RunTask方法被执行。
android::WebUrlLoaderClient::didReceiveData已经接收到了数据,然后会去回调WebCore::ResourceLoader::didReceiveData的方法。

如果只看堆栈不清楚的话,则函数的具体的过程为:
WebCore::ResourceLoader::didReceiveData -> WebCore::MainResourceLoader::didReceiveData -> WebCore::ResourceLoader::didReceiveData -> addData(data, length, allAtOnce) -> MainResourceLoader::addData(const char* data, int length, bool allAtOnce) -> documentLoader()->receivedData(data, length) -> DocumentLoader::receivedData(const char* data, int length) -> DocumentLoader::commitLoad(const char* data, int length) -> frameLoader->client()->committedLoad(this, data, length) -> FrameLoaderClientAndroid::committedLoad(DocumentLoader* loader, const char* data, int length) -> DocumentLoader::commitData

在接收到数据,并且到commit的过程中,会根据不同的平台去选择不同的commit load的方式。
在Android平台上,最终会去调用FrameLoaderClientAndroid::committedLoad,然后最终会使用到DocumentLoader::commitData

在DocumentLoader::commitData这个函数中,我们终于又看到了对于DocumentWriter的操作。
1. m_writer.setEncoding(encoding, userChosen);
2. m_writer.addData(bytes, length);
这两个函数分别有什么作用呢? 从字面意思就可以看出来,这两个一个是对DocumentWriter设置编码,一个是把接收到的数据给传送到了DocumentWriter中。


setEncoding(encoding, userChosen)这个函数的原型是这样的:
void DocumentWriter::setEncoding(const String& name, bool userChosen){    m_frame->loader()->willSetEncoding();    m_encoding = name;    m_encodingWasChosenByUser = userChosen;}


举个例子,我们如果查看name与m_encoding的时候,可以看到
(gdb) p m_encoding$11 = {m_impl = {m_ptr = 0x2a0a9e20}}(gdb) p name$12 = (const WTF::String &) @0x4a8c6b3c: {m_impl = {m_ptr = 0x2a0a9e20}}
所以这个编码我认为只是保留了真正需要的部分,将多余的地址,和类型名称给去掉,便于以后使用。
还有一个作用,我们可以看到,在setEncoding的过程中,按照下面的流程是会调用到DocumentWriter的begin这个函数。这样就形成了一个完整的begin -> addData -> end的调用流程。

DocumentWriter::setEncoding(const String& name, bool userChosen) -> m_frame->loader()->willSetEncoding() -> FrameLoader::willSetEncoding() -> FrameLoader::receivedFirstData() -> activeDocumentLoader()->writer()->begin(m_workingURL, false)

在m_writer.addData这个操作中,我们可以看到,在addData中,设置了一个DocumentParser对象,然后使用appendBytes方法去进行了parser的操作。
addData函数中的: parser->appendBytes(this, str, len, flush)
我们可以认为是向DocumentParser去写数据。
这里面需要注意的是什么呢?
1. this这个参数里面,我们刚才设置的m_encoding参数已经传递给了DocumentParser,具体这个参数在以后有什么作用呢? 这个以后再探讨。
2. 这个str里面传输的内容就是我们从ResourceLoader一路回调过来的数据流,就是网页的具体信息。
3. len是这个数据流的长度
4. flush这个值还未赋值。具体什么作用呢? 目前还不是非常清楚。。

刚才分析到了FrameLoader,DocumentLoader这两个类中都有了DocumentWriter的对象。
具体的关联是什么呢? 我们可以看到FrameLoader的流程中,经过receivedFirstData才会去进行begin的操作。
顾名思义,这是在第一次接收到数据的时候就去创建一个DocumentWriter的对象。
begin函数到底做了些什么工作呢?
首先它会去createDocument(url),这样的话就new了一个Document的对象,Document这个类又是去干什么的呢?
首先class Document : public TreeScope, public ScriptExecutionContext {
去看一下TreeScope这个类,TreeScope : public ContainerNode 并且 friend class Document;
ContainerNode呢? class ContainerNode : public Node
这样看得话, Document其实就是一个Node。
既然是Node的话,这样就很容易理解了。
就是说这个就是Dom Tree的根,可以通过这个节点去遍历,寻找这个Dom tree下面的所有的节点。
在具体创建的时候,DOMImplementation::createDocument(m_mimeType, m_frame, url, m_frame->inViewSourceMode())是最终完成创建的一个function。
DOMImplementation所在的位置为external/webkit/Source/WebCore/dom/, 由此可以看出刚才的推测是对的。
创建Document的操作,其实已经进入到了对Dom的操作中。


回到begin函数中,在Document对象已经创建成功以后,会把这个Document的对象设置给DocumentWriter的成员Frame中。
m_frame->setDocument(document)去具体完成了这样的一个操作。函数的原型为: Frame::setDocument(PassRefPtr<Document> newDoc)


begin这个函数在打开一个网页的时候,只有一次被调用。所以Document的对象也只会被创建一次。
通过setDocument这个函数,我们把当前创建好的Document的对象传递给了Frame中,这样的话,Frame中就有了一个Document的成员,而Document由于是由Frame去创建的,所以它里面也有一个Frame的成员。
这两个是一一对应的关系。


Frame相当于一个页面总的数据结构,它包含了跟一个页面相关的很多信息。而Document只是一个Node,是一个根Node。
在setDocument中,我们可以看到 m_doc = newDoc; 即为把当前创建的Document给赋值到了frame的m_doc中。
然后m_doc这个获取到值以后,就去调用了attach()函数。函数原型为:void Document::attach()
attach函数是一个非常重要的函数:
在Document里面的实现为:
void Document::attach(){    ASSERT(!attached());    ASSERT(!m_inPageCache);#if !PLATFORM(ANDROID)    ASSERT(!m_axObjectCache);#endif    if (!m_renderArena)        m_renderArena = new RenderArena();    // Create the rendering tree    setRenderer(new (m_renderArena.get()) RenderView(this, view()));#if USE(ACCELERATED_COMPOSITING)    renderView()->didMoveOnscreen();#endif    recalcStyle(Force);    RenderObject* render = renderer();    setRenderer(0);    TreeScope::attach();    setRenderer(render);}

刚才也提到了Document有个父类是:ContainerNode
在这个里面也有对attach的实现:
void ContainerNode::attach(){    for (Node* child = m_firstChild; child; child = child->nextSibling())        child->attach();    Node::attach();}

我们可以在这个里面看到,containerNode里面对每一个字节点都进行了遍历,然后调用每一各子节点的attach的函数,在全部调用完成之后,调用了其基类的attach函数。

Document::attach的处理呢? 因为Document的本质也是一个Node,所以它首先也会有自己队形的RenderObject
但是因为它是整个Dom树的根,所以我们可以看到它对应的是setRenderer(new (m_renderArena.get()) RenderView(this, view()));
很显然,从这里也创建了一颗RenderTree。 而且它对应的就是Render树的根节点了。。

所以Document::attach里面,就是进行的创建RenderView并且把它赋值给Document的操作。(创建并且设置RenderView)


这样的话,我们完成了Document和RenderView的创建,并且两者进行了关联。
Document赋值给了Frame,以后就可以通过Frame来查找到Document。

继续研究begin函数:m_frame->loader()->didBeginDocument(dispatch);
Frame通过FrameLoader来找到didBeginDocument。而didBeginDocument这个函数是怎么实现的呢?
首先是对FrameLoader里面的参数进行赋值,然后m_frame->document()->setReadyState(Document::Loading)
这里就可以理解为Frame已经可以查找到刚才设置的Document,然后对

这样的话,我们完成了Document和RenderView的创建,并且两者进行了关联。
Document赋值给了Frame,以后就可以通过Frame来查找到Document,然后对Document进行了setReadyState的操作。
这里的设置状态主要有以下三个: Loading Interactive Complete 在对事件的处理其实都是得到当前的时间,方便以后的判断。
重要的是这边会将得到的状态dispatch出去, 具体为dispatchEvent(Event::create(eventNames().readystatechangeEvent, false, false))
在Document里面我们没有找到这个方法,一路追上去的话发现这个方法是在父类的Node里面去实现的。

仍然回到begin的函数中,我们在begin中,发现之后进行的就是document->implicitOpen();

还是先复习一下这个begin函数的作用,它是在setEncoding里面被调用到的,后面还是接着addData,所以在这个里面应该配置好跟解析相关的类,等数据一传到的时候就进行解析。
而数据的传输流程是: ResourceLoader -> MainResourceLoader -> DocumentWriter -> Document
所以到DocumentWriter之前,都是在loader模块,到了Document才是真正到了dom模块中,进行dom的构建。

刚才我们已经知道创建RenderView了,可是parser还没有创建。
这个创建是不是在implicatiOpen里面去创建的呢?
看一下code:
void Document::implicitOpen(){    cancelParsing();    removeChildren();    setCompatibilityMode(NoQuirksMode);    m_parser = createParser();    setParsing(true);    setReadyState(Loading);    // If we reload, the animation controller sticks around and has    // a stale animation time. We need to update it here.    if (m_frame && m_frame->animation())        m_frame->animation()->beginAnimationUpdate();}

在这个函数里面,我们首先取消了之前的Parsing,然后把子节点给移除了。为什么这样做呢? 这样做的原因是将目前的节点清除,只留下一个根节点。
相当于对当前网页的解析,都是从这个干净的根节点开始进行操作。
然后就开始了createParse的操作,稍后将parsing的状态设置为true,将当前的状态设置为Loading。
由此可见,这个函数的主要作用就是创建了一个DocumentParser.

DocumentParser的作用和实现我们在接下来去研究。

到这里为止,begin函数应该是研究完了。
再回到一开始的commitData函数,我们在这个函数调用完成之后,会去调用addData。

回到DocumentLoader.cpp文件里面的void DocumentLoader::commitData(const char* bytes, int length)函数。
在这个里面,我们看到
m_writer.setEncoding(encoding, userChosen);
ASSERT(m_frame->document()->parsing());
m_writer.addData(bytes, length);
当setEnconding完成之后,DocumentParser,Document,renderView都完成了创建。
所以在之后,去assert检查是否parsing为NULL,当然不会是null了。。
所以就开始使用DocumentWriter进行addData的操作。

addData的操作非常简单:
void DocumentWriter::addData(const char* str, int len, bool flush){    if (len == -1)        len = strlen(str);    DocumentParser* parser = m_frame->document()->parser();    if (parser)        parser->appendBytes(this, str, len, flush);}

判断接收到的数据,获得已经创建的parser
如果parser存在的时候,将数据传递给parser.

当然,当数据已经解析完成以后,就会调用DocumentWriter::end。
end函数的实现如下:
void DocumentWriter::end(){    m_frame->loader()->didEndDocument();    endIfNotLoadingMainResource();}

这里面我们可以看到,因为FrameLoader里面进行didEndDocument的操作
在完成解析的时候,我们调用DocumentWriter的end函数来进行,这里就看出了FrameLoader这个类的部分作用。
因为在一开是的时候也是从FrameLoader里面进行的开始的操作:didBeginDocument
这里主要是对Frame Load过程的控制。
FrameLoader里面的实现为:
void FrameLoader::didEndDocument(){    m_isLoadingMainResource = false;}

所以至此,我们知道了DocumentWriter这个类的作用。
总结如下:


当我们第一次接收到数据的时候,我们通过setEncoding来调用DocumentWriter的begin来进行Dom tree和Render Tree的根节点的创建。

同时,我们创建了DocumentParser的对象,来准备进行数据的解析。

在从load模块接收数据后,通过DocumentWriter传递数据给DocumentParser
在这个过程中,DocumentWriter起到的是两个模块之间的一个桥梁的作用。

在结束的时候,DocumentWriter从Frameloader知道了当前的Resource Loading过程已经结束。
变改变信号,告诉Parser模块当前已经没有数据需要解析。
完成整个工作流程的使命。。。。

更多相关文章

  1. Android游戏引擎《Rokon》:常见问题汇总(2010.11.15更新)
  2. Android布局文件的加载过程分析:Activity.setContentView()源码
  3. android SQLite 优化(三)使用事务 优化 insert
  4. 使用SQLiteOpenHelper 和使用ContentProvider。
  5. Android(安卓)访问WebService
  6. Android(安卓)UI- 对话框 (AlertDialog & ProgressDialog)
  7. 使用android画布的save()和restore()方法
  8. android studio中的so库调用
  9. Android磁盘管理-之vold源码分析(4)

随机推荐

  1. Android 入门 和 环境搭建
  2. Cordova 低版本安卓白屏
  3. Android 开发环境安装 新版本Android Stu
  4. 《第一行代码——Android》
  5. Android获取其他包的Context实例
  6. android 的权限
  7. 【MonkeyRunner】eclipse中编写monkeyrun
  8. Android中的线程
  9. 【Android】Conversion to Dalvik format
  10. 匿名类中this的特殊用法(class.this)