webkit之http请求

原帖地址 :http://blog.csdn.net/hxwwf/article/details/7290909    原作者还有很多webkit的文章,可以看看


执行加载一个页面时,BrowserFrame的loadUrl会通过jni调用到WebCoreFrameBridge.cpp的LoadUrl中。Java层的BrowserFrame对应的是c层的WebCore::Frame

WebCoreFrameBridge::LoadUrl

这个函数会通过参数获取到url并用url创建一个WebCore::KURL。这个类其实就是对url的一个封装,可以通过该类对url的一些信息做分离。

之后创建了关键的类对象WebCore::ResourceRequest request(kurl);

ResourceRequest

这个类就是对Http请求的一个抽象的封装。这个类集成自ResourceRequestBase。在android上ResourceRequest本身没有实现什么新的函数,其主要工作都在ResourceRequestBase上。ResourceRequest主要用于针对特殊的平台添加某些特殊的数据信息和接口。可以看到webkit中有多个ResourceRequest.h,都是针对各个相应平台的,但是ResourceRequestBase是唯一的,提供了公共的数据结构和接口。

ResourceRequestBase

该了包含了一个http请求所需要的各种数据信息。一些头的key-value信息存在HTTPHeaderMap m_httpHeaderFields;成员变量中。

另外这里定义了几个枚举值

enum ResourceRequestCachePolicy {

       UseProtocolCachePolicy, // normal load

       ReloadIgnoringCacheData, // reload

       ReturnCacheDataElseLoad, // back/forward or encoding change - allowstale data

       ReturnCacheDataDontLoad  //results of a post - allow stale data and only use cache

};

这些枚举值用于定义该请求的一个Policy,或者可以说是一个类型,标识该请求是出于什么原因发出的。

在创建出ResourceRequest后,会判断下调用LoadUrl时是否设置了一些扩展的Header信息,如果有则把这些信息设置进ResourceRequest中。这些扩展信息就是一些key-value的对,存他们的位置就是上述的HTTPHeaderMap m_httpHeaderFields中。这样一个Http请求(ResourceRequest)就准备好了。

此时我们有了一个Frame还有一个ResourceRequest。

现在需要看下Frame里面如何利用这个ResourceRequest。Frame有个成员mutable FrameLoader m_loader;

FrameLoader

看名字就知道是为Frame执行Load用的。FrameLoader提供了很多跟加载相关的接口,另外有一个FrameLoadType枚举类型用于标识FrameLoader的类型。在Frame的构造时会同时创建它的FrameLoader成员,在Frame::init时也会执行FrameLoader::init。可见FrameLoader是Frame与生俱来的重要成员。但是注意FrameLoader没有ResourceRequest类型的成员,即它本身也不维护ResourceRequest。

在刚才的WebCoreFrameBridge::LoadUrl最后通过Frame找到它的FrameLoader,然后执行FrameLoader::load,并把ResourceRequest传给它。

FrameLoader::load

刚开始先对这个FrameLoader设置下类型,把类型设置成标准的。

之后会创建一个DocumentLoader,创建时会把ResourceRequest传入。

DocumentLoader

DocumentLoader会执行网络请求,下载资源的操作。在DocumentLoader中终于看到了ResourceRequest的成员,并且还不止一个。

// A reference to actual request used to create the data source.

       // This should only be used by the resourceLoadDelegate's

       // identifierForInitialRequest:fromDatasource: method. It is

       // not guaranteed to remain unchanged, as requests are mutable.

       ResourceRequest m_originalRequest;  

// A copy of the original request used to create the data source.

       // We have to copy the request because requests are mutable.

       ResourceRequest m_originalRequestCopy;

       

       // The 'working' request. It may be mutated

       // several times from the original request to include additional

       // headers, cookie information, canonicalization and redirects.

       ResourceRequest m_request;

// The last request that we checked click policy for - kept around

       // so we can avoid asking again needlessly.

       ResourceRequest m_lastCheckedRequest;

这里额外先看下ResourceResponse在DocumentLoader中的情况

ResourceResponse m_response;

// We retain all the received responses so we can play back the

       // WebResourceLoadDelegate messages if the item is loaded from the

       // page cache.

       ResponseVector m_responses;

一个html帧对应于一个DocumentLoader,而一个html帧中,会有一个html资源,多个派生资源(image,css,object等),所以每个DocumentLoader维护一个m_response,对应于html资源,维护一个ResourceResponse数组(ResponseVector m_responses),对应于派生资源(SubresourceLoader)。类DocumentLoader中既包含ResourceRequest对象,也包含ResourceResponse对象。DocumentLoader中封装了部分ResourceRequest和ResourceResponse的接口。

在构造DocumentLoader是会把参数传入的ResourceRequest设置给m_originalRequest,m_originalRequestCopy,m_request。

终于找到ResourceRequest的家了。

回到FrameLoader::load处,之后又会通过FrameLoader::addExtraFieldsToRequest对ResourceRequest设置一些数据信息,包括UA的设置,CachePolicy的设置等等。

之后在FrameLoader::checkNavigationPolicy中通过FrameLoader::SetPolicyDocumentLoader将FrameLoader与新创建的DocumentLoader第一次关联上。这里设置的是m_policyDocumentLoader

加载Frames

FrameLoader类负责将documents加载到Frames。当你点击一个链接的时候,FrameLoader创建一个新的处于”policy”状态的DocumentLoader对象,等待WebKit客户端决定是否处理这个加载。通常WebKit客户端会指示FrameLoader将这个加载视为一个导航(navigation),而不是阻止加载等。

一旦客户端指示FrameLoader将本次加载视为一个导航,FrameLoader就推动DocumentLoader进入”provisional”状态,在该状态,DocumentLoader会发起一个网络请求,并等待以确定网络请求将发起一个下载还是一个新的document。

接下去,DocumentLoader会创建一个MainResourceLoader对象,这个对象主要用来通过ResourceHandle接口同平台网络库进行交互。将MainResourceLoader和DocumentLoader分开来主要有两个目的:

(1)MainResourceLoader让DocumentLoader从处理ResourceHandle回调的细节中抽身出来

(2)降低MainResourceLoader的生命周期和DocumentLoader的生命周期(同Document绑定)的耦合度。

一旦加载系统接收到足够的信息可以确定资源确实代表了document,FrameLoader就将DocumentLoader推向”committed”状态,在该状态中,frame将显示document。

此时看下执行的栈情况:

#0 WebCore::DocumentLoader::startLoadingMainResource

#1 WebCore::FrameLoader::continueLoadAfterWillSubmitForm

#2 WebCore::FrameLoader::continueLoadAfterNavigationPolicy

#3 WebCore::FrameLoader::callContinueLoadAfterNavigationPolicy

#4 WebCore::PolicyCallback::call

#5 WebCore::PolicyChecker::continueAfterNavigationPolicy

#6 android::FrameLoaderClientAndroid::dispatchDecidePolicyForNavigationAction

#7 WebCore::PolicyChecker::checkNavigationPolicy

#8 WebCore::FrameLoader::loadWithDocumentLoader

#9 WebCore::FrameLoader::load

#10 WebCore::FrameLoader::load

#11WebCore::FrameLoader::load

#12 in LoadUrl

上面的执行栈中

Frame构造时就有FrameLoader成员。

在FrameLoader::LoadUrl处创建了ResourceRequest

在FrameLoader::load处创建了DocumentLoader,并把ResourceRequest设置给了DocumentLoader

在FrameLoader::checkNavigationPolicy处把创建的DocumentLoader设置给FrameLoader::m_policyDocumentLoader

在FrameLoader::continueLoadAfterNavigationPolicy处把FrameLoader::m_policyDocumentLoader设置给了FrameLoader::m_provisionalDocumentLoader,之后清掉FrameLoader::m_policyDocumentLoader

在FrameLoader::continueLoadAfterWillSubmitForm处此时会判断下当前activeDocumentLoader是否正在加载,如果正在加载则直接返回。

当前activeDocumentLoader正是FrameLoader::m_provisionalDocumentLoader

在DocumentLoader::startLoadingMainResource处这个DocumentLoader也是FrameLoader::m_provisionalDocumentLoader。

继续看DocumentLoader::startLoadingMainResource,这又是个比较重要的函数。

DocumentLoader::startLoadingMainResource

这里首先就创建了一个重要的类对象MainResourceLoader,并记录在DocumentLoader中RefPtr<MainResourceLoader> m_mainResourceLoader;

MainResourceLoader

MainResourceLoader继承自ResourceLoader,另外还有一个类SubresourceLoader也是继承自ResourceLoader。

ResourceLoader

封装了跟网络请求相关的接口。包括资源的请求以及响应的回调接口。

包含ResourceRequest m_request;和ResourceResponsem_response;成员,用于请求和响应信息的封装。包含Frame的引用,主要用在一些回调事件中,向Frame发送一些通知信息。比如在接收到数据时,会告诉Frame,可以让Frame做一些状态的更新处理。最重要的一个成员就是RefPtr<ResourceHandle> m_handle;它作为ResourceLoader与平台相关的网络接口的一个中介。

ResourceHandle

提供了平台无关的网络相关的接口定义,即作为平台相关的网络资源请求控制的一个代理中间层。ResourceLoader会利用他与平台相关网络做交互。暂时先不细看它,一会会用到。

回到DocumentLoader::startLoadingMainResource中,在创建了MainResourceLoader后会设置MainResourceLoader的Identifier,然后又设置了遍ResourceRequest的ExtraFields。回顾下设置ExtraFields的函数是FrameLoader提供的,当前函数在DocumentLoader中。

DocumentLoader是由FrameLoader创建的,但是DocumentLoader中也记录了的Frame的指针,通过Frame可以找到FrameLoader。而ResourceRequest和RequestResponse是在DocumentLoader中维护的。

接下来就调用了MainResourceLoader::load函数,并把之前创建的RequestRequest传入。

MainResourceLoader::load

这里会记录下当前的时间给DocumentLoader的DocumentLoadTiming。

然后会判断下是否是defer,如果是defer是要求延迟加载的。这里不是defer则立刻开始加载了,执行MainResourceLoader::loadnow。

MainResourceLoader::loadnow

先执行MainResourdeLoader::willSendRequest,这里会配置下ResourceRequest,比如设置cookies之类,然后调用基类ResourceLoader::willSendRequest。ResourceLoader::willSendRequest会通过ResourceLoader的成员FrameLoader的引用来执行下面的语句frameLoader()->notifier()->willSendRequest(…)

这里就要看下notifier()这个东西,首先再回顾下,ResourceLoader中有Frame的引用,frameLoader()就是通过Frame找到Frame的FrameLoader。

FrameLoader中有成员ResourceLoadNotifier。

ResourceLoadNotifier

这个类看名字,资源加载通知,可见这个是个通知,并且是资源加载的通知,那么通知应该主要就是一些回调的信息,果然该函数有很多didXXX用于某些接收数据阶段的回调通知,刚才执行的willSendRequest,是在资源正式加载前的通知。也就是这个通知是包含在某些发送操作准备阶段的回调,和某些接收操作接收到数据后的回调。这些接口大多都包含ResourceLoader指针作为参数。因为功能的主体还是ResourceLoader,而ResourceLoadNotifier

只是辅助主体的通知类。

这里先不具体看ResourceLoadNotifier::willSendRequest,回到MainResourceLoader::loadnow中,接着判断下url是否为空,如果为空会做其他的处理。

接下来调用ResourceLoaderScheduler::addMainResoureLoad,把当前MainResourceLoader加入到ResourceLoaderScheduler中。

ResourceLoaderScheduler

这个ResourceLoaderScheduler有个静态的实例,是个单例的。通过typedefHashSet<RefPtr<ResourceLoader> > RequestMap;

RequestMap m_requestsLoading;

记录了加入的ResourceLoader。这个负责对ResourceLoader的调度,具体的调度策略暂时不看,只需要知道有地方负责调度就好了。

回到MainResourceLoader::loadnow,最后重要的一步调用,执行ResourceHandle::create创建了ResourceHandle,并赋值给MainResourceLoader::m_handle,即之前提到的。

看下栈情况:

#0 WebCore::ResourceHandle::create

#1WebCore::MainResourceLoader::loadNow

#2WebCore::MainResourceLoader::load

#3WebCore::DocumentLoader::startLoadingMainResource

到现在为止还没有开始真正的Request发送呢。

ResourceHandle::creat

这里先看下它的参数它首先需要传入一个NetworkingContext的指针,在MainResourceLoader中是通过FrameLoader中存的NetworkingContext传入的,FrameLoader中有成员NetworkingContext m_networkingContext; 他是在FrameLoader::init中通过FrameLoaderClient*m_client;成员的FrameLoaderClient::createNetworkingContext创建的。FrameLoaderClient是个提供平台无关接口,平台相关的继承类会实现它。

NetworkingContext

这个提供了一个平台无关的网络环境,但是看提供的接口很少,貌似作用也不大,android上创建的是它的继承类FrameNetworkingContextAndroid。

ResourceHandle

前面提到过ResourceHandle提供了平台无关的网络相关的接口定义,即作为平台相关的网络资源请求控制的一个代理中间层。ResourceLoader会利用他与平台相关网络做交互。

ResourceHandle中有个成员OwnPtr<ResourceHandleInternal>d;

ResourceHandleInternal

这个类其实就是个数据成员的封装,它根据不同平台定义了一堆不同类型的成员变量,是个成员变量的集合,它定义了一些平台无关的变量的名字,但是变量的类型跟平台相关。而ResourceHandle会通过它来执行一些功能的实现。这么说吧,ResourceHandle定义了平台无关的公共的接口以及一些公共的实现。但由于平台相关的网络会需要不同类型的成员变量,所以把成员变量的区别在ResourceHandleInternal中来体现,这样ResourceHandle中只直接操作ResourceHandleInternal中平台无关的成员变量部分以及平台相关的但是变量名字一样的成员。

回到ResourceHandle::create中,这个函数首先会创建一个ResourceHandle对象,会把ResourceRequest作为参数传入,告诉它要执行什么请求。会把MainResourceLoader作为ResourceHandleClient传给它,便于它回调它的方法。

ResourceHandleClient

该类是一个接口类,定义了一堆didXXX之类的回调接口,并且都是以空实现。ResourceLoader类就是继承自这个接口类。故ResourceLoader会实现很多的didXXX的回调接口。之前提到过ResourceLoadNotifier也提供了一堆didXXX形式的回调接口。实际上ResourceLoader的一些didXXX的实现中就是前面提到的ResourceLoader找到Frame的引用,进一步找到FrameLoader的引用,再进一步找到ResourceLoadNotifier的引用,(额外说一句,这里说的引用不是c++语法中的引用,实现是指针或者智能指针,我说的引用是语义含义的引用)。然后调用ResourceLoadNotifier的didXXX。这里就关联上了。

ResourceHandle中存有ResourceHandleClient的引用,这里ResourceHandleClient实际上就是ResourceLoader。ResourceHandle进行回调时,会调用ResourceHandleClient的回调接口,实际上就是调用ResourceLoader的回调接口。ResourceLoader的回调接口在处理一些事情后可以通过Frame->FrameLoader->ResourceLoadNotifier调用到ResourceLoadNotifier的回调接口。这个ResourceLoadNotifier的回调接口会处理一些通知的事件。这些回调接口的具体实现先不看。但这里要清楚回调的流程。

 

回到ResourceHandle::create中,在成功创建了ResourceHandle的对象后,会调用ResourceHandle::start(NetworkingContext* context)。

ResourceHandle::start(NetworkingContext*context)

这里会调用ResourceLoaderAndroid的静态方法start,并从ResourceHandle中获取一些数据信息作为参数传入,其中就包含ResourceHandle,ResourceRequest,FrameLoaderClient。ResourceHandle用于回调,ResourceRequest是请求发送的数据,FrameLoaderClient可以获取平台相关的一些信息。

ResourceLoaderAndroid

这个是平台相关的一个资源加载的虚基类,定义了一些虚接口,但定义的接口很少。他实现了一个静态方法ResourceLoaderAndroid::start,这个方法内实现具体的网络操作。

ResourceLoaderAndroid::start

该函数在android2.X的版本中是通过java层的apache-http库来执行http通讯的,会调用WebFrame::startLoadingResource,该函数内会通过jni调用到java层BrowserFrame::startLoadingResource进行http的发送,还会设置一个java层的LoadListener类作为回调的侦听接口。

但是在android 4.0中ResourceLoaderAndroid::start中定义了两套不同的使用网络的流程,通过CHROME_NETWORK_STACK宏来区分。如果定义了该宏则使用新的/external/chromium/下的网络的流程,如果没有定义该宏则还是使用2.X版本中的流程。

这里关注4.0的流程,先通过参数传入的FrameLoaderClientAndroid(在android平台中,FrameLoaderClientAndroid是FrameLoaderClient的子类),获取到WebViewCore。注意这个获取过程是平台相关的,FrameLoaderClientAndroid中存有Frame的成员,Frame中有FrameView成员,之后通过静态函数WebViewCore::getWebViewCore(constWebCore::FrameView* view);找到WebViewCore。找到WebViewCore的目的是为了通过它找到WebRequestContext这个类对象。

WebRequestContext

看名字是跟http Request的环境相关的类,其实里面就是一些状态参数,包括UserAgent,CacheMode,AcceptLanguage的信息。

回到ResourceLoaderAndroid::start中,接下来才是关键,调用了WebUrlLoader::start,并把FrameLoaderClient,WebCore::ResourceHandle,WebCore::ResourceRequest,WebRequestContext等信息作为参数传入。

WebUrlLoader::start

再回顾下,FrameLoaderClient提供平台相关的信息,ResourceHandle提供回调接口,ResourceRequest提供请求数据的信息,WebRequestContext提供请求的一些参数。

在这个函数里创建了一个WebUrlLoader的对象,android 2.X版本中没有这个类的,看下这个源文件的路径,/external/webkit/Source/WebKit/android/WebCoreSupport/WebUrlLoader.cpp可见这个类还是平台相关的。这个WebUrlLoader::start也是个静态方法,用于创建WebUrlLoader的对象并通过该对象执行开始加载的函数。即ResourceLoaderAndroid::start中调用了WebUrlLoader::start,而WebUrlLoader::start会创建WebUrlLoader的类并发起网络请求。

WebUrlLoader

它继承自ResourceLoaderAndroid,实现了ResourceLoaderAndroid定义的纯虚函数,它也没有多接口做什么扩展,但是它有一个成员WebUrlLoaderClient,会在WebUrlLoader构造时被创建。这个类才是网络请求的真正关键,前面几个都是作为一些中介的类而存在,即定义了一些主要接口,但数据信息的存储和方法的实现还是扔给了WebUrlLoaderClient。

WebUrlLoaderClient

看名字又是个作为平台无关跟平台相关的中介的类,但是这里已经处于android平台相关的地方了,WebUrlLoaderClient本身已经是android平台专门实现的类了,那么他还跟谁去再做平台相关呢?

WebUrlLoaderClient是继承自net::URLRequest::Delegate,这个net::URLRequest::Delegate是定义在/external/chromium/net/url_request/url_request.h中的。这就知道了,WebUrlLoaderClient是webkit和chromium的net相互关联的中介。即webkit通过WebUrlLoaderClient与chromium的net交互。

首先看下它的主要成员,WebFrame,WebCore::ResourceHandle,WebRequest,WebResponse。其中WebRequest是在构造函数中创建的,WebResponse是在http接收到response相应后在WebUrlLoaderClient::didReceiveResponse中被赋值的,它的来源是该回调函数的参数。

 //Queue of callbacks to be executed by the main thread. Must only be accessedinside mutex.

std::deque<Task*> m_queue;

回到WebUrlLoader::start中,在创建完WebUrlLoader后,会调用WebUrlLoader的WebUrlLoaderClient的WebUrlLoaderClient::start。

WebUrlLoaderClient::start

Request的发送有两种方式,一种是同步的一种是异步的,这里只看异步的方式。

会先对成员WebRequest设置WebRequestContext。然后执行了下面这句关键的语句。

thread->message_loop()->PostTask(FROM_HERE,NewRunnableMethod(m_request.get(), &WebRequest::start));

看来就是向某个线程的消息循环中,发送一个任务。任务的含义其实就是执行某个对象的某个函数,所以需要两个参数,1对象,2方法,即这里的m_request.get()和&WebRequest::start。

消息循环是message_loop,消息循环当处理到该消息时,就会执行这个任务。

这里要注意了,消息循环MessageLoop是/external/chromium/base/message_loop.h中定义的。

这里还需要确定一个问题,thread到底是哪个线程。WebUrlLoaderClient::start的开头有个函数WebUrlLoaderClient::ioThread,thread是这个函数给赋值的。

WebUrlLoaderClient::ioThread

这个函数里首先有个具备的静态指针变量static base::Thread* networkThread。在第一次进入该函数时,创建一个名为”network”的线程,并赋值给这个静态变量,以后再调用该函数则直接返回这个networkThread。

由此可见chromium中net部分只有这一个线程负责网络数据的连接(这里可能是我理解错了,可能这个ioThread只是负责接收并处理http 请求任务的,但是真正执行http连接的未必是这个线程,个人认为应该不是这个线程,应该会有其他的线程做真正的http连接,这个ioThread只是个汇聚的口)。另外,这里有个锁用于防止多线程对ioThread的并发请求。

但是在android上这个线程是java层开的还是c层直接开的呢?看了下/external/chromium/base/threading/下对平台相关的thread的实现,没有android相关的,android上使用的是platform_thread_posix.cc中定义的平台相关的线程创建方法。

那么可以理一下:

4.0中,会有一个ioThread的线程,专门用来做网络的任务的处理,到底有几个线程做http的连接我也还不清楚。

在代码/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp中

WebUrlLoaderClient::ioThread处

这里有个局部静态的static base::Thread* networkThread。

base::Thread是chromium中定义的,这个是c层开的线程,调用的是platform_thread_posic.cc中线程创建的接口。

在http请求发送时,会把http的具体net发送部分,作为一个任务,放在networkThread的MessageLoop中运行。

代码/external/webkit/Source/WebKit/android/WebCoreSupport/WebUrlLoaderClient.cpp的thread->message_loop()->PostTask(FROM_HERE,NewRunnableMethod(m_request.get(), &WebRequest::start));句

回到WebUrlLoaderClient::start中,在执行了PostTask后,该函数结束,可知一个叫network的线程(ioThread)在过后会执行WebRequest::start函数。

看下当前完整栈情况:

#0 MessageLoop::PostTask

#1android::WebUrlLoaderClient::start

#2 android::WebUrlLoader::start

#3 WebCore::ResourceLoaderAndroid::start

#4 WebCore::ResourceHandle::start

#5 WebCore::ResourceHandle::create

#6WebCore::MainResourceLoader::loadNow

#7WebCore::MainResourceLoader::load

#8 WebCore::DocumentLoader::startLoadingMainResource

#9 WebCore::FrameLoader::continueLoadAfterWillSubmitForm

#10 WebCore::FrameLoader::continueLoadAfterNavigationPolicy

#11 WebCore::FrameLoader::callContinueLoadAfterNavigationPolicy

#12 WebCore::PolicyCallback::call

#13 WebCore::PolicyChecker::continueAfterNavigationPolicy

#14 android::FrameLoaderClientAndroid::dispatchDecidePolicyForNavigationAction

#15 WebCore::PolicyChecker::checkNavigationPolicy

#16 WebCore::FrameLoader::loadWithDocumentLoader

#17 WebCore::FrameLoader::load

#18 WebCore::FrameLoader::load

#19WebCore::FrameLoader::load

#20 in LoadUrl

http请求在IOThread中的处理

经过以上的内容,已经把Request的请求发给ioThread的消息循环中了,该线程可以继续运行其他的内容了。接下来看ioThread的任务处理。之前设置的任务为WebRequest::start。那么该函数会被调用。调用它的对象就是WebUrlLoaderClient中的m_request。

WebRequest

这个类定义在/external/wekit/Source/Webkit/android/WebCoreSupport/WebRequest.cpp这个是个android平台相关的类,它存储了httprequest相关的数据信息以及操作的接口,但是真正实现操作处理的是它的成员OwnPtr<net::URLRequest> m_request;

net::URLRequest

这个类定义在/external/chromium/net/url_request/url_request.cc,可见它是chromium的net中定义的。

WebRequest::start会调用net::URLRequest::start

Chromium中的实现就不去看了。但是可以知道,到此,http请求是发出去了,接下来就等待各种回调函数了。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值