vsync信号其实主要有两种,APP和SF两种。
两种类似的信号分别具有以下的作用:
1.APP类型,我们常说的Vsync信号其实指的就是这种类型。
2.SF类型,我感觉有可能是SurfaceFlinger的缩写,这个同步信号主要是通知进行数据buffer的合成。
本文主要探讨的主APP类型的Vsync信号,SF类型的会在另外一篇文章,View绘制流程中讲解。
我把app-VSync的整个流程分为四大块:
第一块,APP侧发出请求信号,通知到SurfaceFlinger;
第二块,SurfaceFlinger收到通知后,作为消费者侧去缓存池中查询是否存在VSYNC,如果有,则通知APP侧。
第三块,SurfaceFlinger中的生产者逻辑,生产下一次的Vsync信号。
第四块,APP侧收到Vsync信号后进行处理,最终完成绘制流程。
整体的流程图如下图所示,后续文章也会按照这四大块流程去细讲。

我们把代码的开始点设置为ViewRootImpl中的scheduleTraversals方法,如果想了解这个方法之前的流程,可以参看我的另外一篇文章:
View绘制流程2-安卓是如何执行measure/layout/draw三个绘制流程_失落夏天的博客-CSDN博客
scheduleTraversals是负责渲染流程的,界面上任何布局上的改动,最终都会执行这个方法进行刷新操作。scheduleTraversals会通过Choreographer的postCallback方法去请求Vsync信号并且设置回调方法。
- mChoreographer.postCallback(
- Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
Choreographer中的最终实现是postCallbackDelayedInternal方法:
- private void postCallbackDelayedInternal(int callbackType,
- Object action, Object token, long delayMillis) {
- ...
- synchronized (mLock) {
- final long now = SystemClock.uptimeMillis();
- final long dueTime = now + delayMillis;
- mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
-
- if (dueTime <= now) {
- scheduleFrameLocked(now);
- }
- ...
- }
- }
这个方法会把回调加入到mCallbackQueues中,然后通过scheduleFrameLocked方法开始请求Vsync信号。
scheduleFrameLocked中又会进行层层传递,最终调用到native方法。传递关系如下:
- scheduleFrameLocked(Choreographer.java)->
- scheduleVsyncLocked(Choreographer.java)->
- scheduleVsync(DisplayEventReceiver.java)->
- nativeScheduleVsync(DisplayEventReceiver.java)
nativeScheduleVsync在native层的注册是DisplayEventReceiver.cpp中的nativeScheduleVsync方法,方法中通过NativeDisplayEventReceiver的scheduleVsync来完成请求:
- static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
- sp<NativeDisplayEventReceiver> receiver =
- reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
- status_t status = receiver->scheduleVsync();
- ...
- }
NativeDisplayEventReceiver是DisplayEventDispatcher的子类,scheduleVsync方法会执行到DisplayEventDispatcher中的scheduleVsync方法,调用DisplayEventReceiver的requestNextVsync继续请求流程。
- status_t DisplayEventDispatcher::scheduleVsync() {
- if (!mWaitingForVsync) {
- ...
- status_t status = mReceiver.requestNextVsync();
- ...
- mWaitingForVsync = true;
- ...
- }
- return OK;
- }
这里有一个本地变量mWaitingForVsync,如果请求了一次sync,就会改为true,接下来的Vsync请求,都不会传输到SurfaceFlinger一层了,避免重复无意义请求。只有等到收到Vsync信号的时候,才会改为false。
DisplayEventReceiver中,会交给mEventConnection处理:
- status_t DisplayEventReceiver::requestNextVsync() {
- if (mEventConnection != nullptr) {
- mEventConnection->requestNextVsync();
- return NO_ERROR;
- }
- return NO_INIT;
- }
mEventConnection其实是一个binder客户端,是在创建DisplayEventReceiver的时候通过binder方法创建的,其binder服务端实现在SurfaceFlinger进程侧的EventThread.cpp。
- DisplayEventReceiver::DisplayEventReceiver(
- ISurfaceComposer::VsyncSource vsyncSource,
- ISurfaceComposer::EventRegistrationFlags eventRegistration) {
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
- if (sf != nullptr) {
- mEventConnection = sf->createDisplayEventConnection(vsyncSource, eventRegistration);
- ...
- }
- }
首先,SurfaceFlinger的总体结构图如下,SurfaceFlinger中存在一个Scheduler,Scheduler中存在多个VsyncDispatch。其中VSYNC-app负责所有APP的VSYNC信号的处理,本文主要讲的也是这个流程中的。

其次,下图是主流程图中的一部分,也是本章要讲的内容。

是接收方法如下,转发到mEventThread的requestNextVsync方法中。
- binder::Status EventThreadConnection::requestNextVsync() {
- ATRACE_CALL();
- mEventThread->requestNextVsync(this);
- return binder::Status::ok();
- }
我们接着看一下EventThread中的requestNextVsync方法:
- void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
- ...
-
- if (connection->vsyncRequest == VSyncRequest::None) {
- connection->vsyncRequest = VSyncRequest::Single;
- mCondition.notify_all();
- } else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
- connection->vsyncRequest = VSyncRequest::Single;
- }
- }
这里的逻辑很简单,如果当前的VSYNC状态是None的话,释放锁mCondition。
这里既然notify_all,那么一定有地方wait等待锁,而等待的地方就是threadMain方法。
这个方法算是整个Vsync流程的核心,它是一个无限循环的模式,其作用是不断的从mPendingEvents中获取Vsync信号,然后转交给APP端。并且在一次Vsync流程结束后,又通过VsyncSource请求下一次的Vsync信号。
threadMain方法是EventThread类初始化的时候创建的线程去执行的方法:
- mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {
- std::unique_lock<std::mutex> lock(mMutex);
- threadMain(lock);
- });
我们看一下精简后的threadMain方法:
- void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
- DisplayEventConsumers consumers;
-
- //只要没有退出,就一直循环
- while (mState != State::Quit) {
- 执行以下1-7的操作
- }
- }
总体来说,分为以下四大步骤:
1.作为消费者尝试从mPendingEvents中获取Vsync信号,如果获取成功,则赋值给event。
- std::optional<DisplayEventReceiver::Event> event;
-
- //查看mPendingEvents中是否存在Vsync信号
- if (!mPendingEvents.empty()) {
- event = mPendingEvents.front();
- mPendingEvents.pop_front();
- ...
- }
2.计算vsyncRequested的状态,只要客户端消费者的Connection保持连接,则vsyncRequested=true,并且上面步骤一获取到event的话,则把消费者的connection加入到consumers集合中。
- bool vsyncRequested = false;
-
- //获取当前的状态,并且判断是否有客户端的消费者在请求,如果有则加入到consumers集合中
- auto it = mDisplayEventConnections.begin();
- while (it != mDisplayEventConnections.end()) {
- if (const auto connection = it->promote()) {
- //客户端还是处于请求的状态
- vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;
-
- if (event && shouldConsumeEvent(*event, connection)) {
- consumers.push_back(connection);
- }
-
- ++it;
- } else {
- it = mDisplayEventConnections.erase(it);
- }
- }
3.如果consumers集合不为空,则进行消费。把Vsync信号分发给消费者。(具体步骤我们下一小节中讲)
- //如果消费者不为空,则通过dispatchEvent方法最终通知到APP一侧
- if (!consumers.empty()) {
- dispatchEvent(*event, consumers);
- consumers.clear();
- }
4.获取下一个状态
- //mVSyncState不会为空,则主要是根据vsyncRequested来判断的。vsyncRequested上面计算的
- State nextState;
- if (mVSyncState && vsyncRequested) {
- nextState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync;
- } else {
- ALOGW_IF(!mVSyncState, "Ignoring VSYNC request while display is disconnected");
- nextState = State::Idle;
- }
5.进行判断,确定是否需要请求VSYNC信号。这一块挺有意思的,简单理解如下:
如果当前是VSYNC状态,下一个状态也是VSYNC状态,那么说明信号还没来,所以没必要重复发送。
如果当前是Idle状态,下一个状态是VSYNC状态,那么则要进行VSYNC信号请求。
如果当前是VSYNC状态,下一个状态也是Idle状态,那么说明信号已经来了,下一次的客户端请求还没来,所以不要进行VSYNC信号请求,则会进行取消操作。
- if (mState != nextState) {
- if (mState == State::VSync) {
- mVSyncSource->setVSyncEnabled(false);
- } else if (nextState == State::VSync) {
- //如果下一个状态还是VSync,则继续去请求VSYNC信号
- mVSyncSource->setVSyncEnabled(true);
- }
-
- mState = nextState;
- }
如何开始和结束去进行VSYNC信号获取的获取操作,我们第四章中讲,这个主要就是消费者逻辑了。
6.如果event为空,说明mPendingEvents中已经取光了,则进入休眠操作。
反之event不为空,说明mPendingEvents中也许还存在未消费的VSYNC信号,则contine继续消费。
- //如果处理了event,那么说明此次已经拿到了Vsync信号,说明后面有可能还有,则继续拿
- if (event) {
- continue;
- }
7.进入休眠或者超时之后主动模拟信号加入到mPendingEvents中。
- //说明Vsync信号已经消费完了,则进入休眠模式,等到APP侧的下一次通知进行唤醒
- // Wait for event or client registration/request.
- if (mState == State::Idle) {
- mCondition.wait(lock);
- } else {
- // Generate a fake VSYNC after a long timeout in case the driver stalls. When the
- // display is off, keep feeding clients at 60 Hz.
- const std::chrono::nanoseconds timeout =
- mState == State::SyntheticVSync ? 16ms : 1000ms;
- if (mCondition.wait_for(lock, timeout) == std::cv_status::timeout) {
- if (mState == State::VSync) {
- ALOGW("Faking VSYNC due to driver stall for thread %s", mThreadName);
- std::string debugInfo = "VsyncSource debug info:\n";
- mVSyncSource->dump(debugInfo);
- // Log the debug info line-by-line to avoid logcat overflow
- auto pos = debugInfo.find('\n');
- while (pos != std::string::npos) {
- ALOGW("%s", debugInfo.substr(0, pos).c_str());
- debugInfo = debugInfo.substr(pos + 1);
- pos = debugInfo.find('\n');
- }
- }
-
- LOG_FATAL_IF(!mVSyncState);
- const auto now = systemTime(SYSTEM_TIME_MONOTONIC);
- const auto deadlineTimestamp = now + timeout.count();
- const auto expectedVSyncTime = deadlineTimestamp + timeout.count();
- mPendingEvents.push_back(makeVSync(mVSyncState->displayId, now,
- ++mVSyncState->count, expectedVSyncTime,
- deadlineTimestamp));
- }
- }
首先遍历消费者,调用postEvent进行通知
- void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event,
- const DisplayEventConsumers& consumers) {
- for (const auto& consumer : consumers) {
- ...
- switch (consumer->postEvent(copy)) {
- }
- }
-
- }
然后postEvent方法中,调用sendEvent进行信号的发送
- status_t EventThreadConnection::postEvent(const DisplayEventReceiver::Event& event) {
- ...
-
- auto size = DisplayEventReceiver::sendEvents(&mChannel, mPendingEvents.data(),
- mPendingEvents.size());
- ...
- }
最终通过Socket的方法进行信号的发送,接受者就是APP侧了。
- ssize_t DisplayEventReceiver::sendEvents(gui::BitTube* dataChannel,
- Event const* events, size_t count)
- {
- return gui::BitTube::sendObjects(dataChannel, events, count);
- }
上面讲到通过setVSyncEnabled方法去开始或者结束获取Vsync信号的操作。
setVSyncEnabled方法如下:
- void DispSyncSource::setVSyncEnabled(bool enable) {
- std::lock_guard lock(mVsyncMutex);
- if (enable) {
- mCallbackRepeater->start(mWorkDuration, mReadyDuration);
- } else {
- mCallbackRepeater->stop();
- }
- mEnabled = enable;
- }
对应的其实就是mCallbackRepeater的start和stop方法,其实现类是DispSyncSource.cpp中的CallbackRepeater。
我们这里看到一个成员变量mWorkDuration,这个值其实就是控制Vscyn触发时间的。这个我们后续小节再讲,这里只是知道有这个值就好了。
start方法中,记录一下传入的workDuration时间,然后传递给mRegistration处理。
- void start(std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration) {
- std::lock_guard lock(mMutex);
- mStarted = true;
- mWorkDuration = workDuration;
- mReadyDuration = readyDuration;
-
- auto const scheduleResult =
- mRegistration.schedule({.workDuration = mWorkDuration.count(),
- .readyDuration = mReadyDuration.count(),
- .earliestVsync = mLastCallTime.count()});
-
- }
mRegistration的实现类是VSyncCallbackRegistration,其中schedule方法也是交给VSyncDispatchTimerQueue来处理:
- ScheduleResult VSyncCallbackRegistration::schedule(VSyncDispatch::ScheduleTiming scheduleTiming) {
- if (!mValidToken) {
- return std::nullopt;
- }
- return mDispatch.get().schedule(mToken, scheduleTiming);
- }
schedule方法中,进行一系列的合法判断,最终会交给 rearmTimerSkippingUpdateFor方法处理。
然后我们就可以看到rearmTimerSkippingUpdateFor中去调用setTimer方法去设置定时触发。
rearmTimerSkippingUpdateFor方法略,
setTimer方法如下:
- void VSyncDispatchTimerQueue::setTimer(nsecs_t targetTime, nsecs_t /*now*/) {
- mIntendedWakeupTime = targetTime;
- mTimeKeeper->alarmAt(std::bind(&VSyncDispatchTimerQueue::timerCallback, this),
- mIntendedWakeupTime);
- mLastTimerSchedule = mTimeKeeper->now();
- }
则到了targetTime之后,就会执行timerCallBack方法。
timerCallback方法如下:
- void VSyncDispatchTimerQueue::timerCallback() {
- struct Invocation {
- std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
- nsecs_t vsyncTimestamp;
- nsecs_t wakeupTimestamp;
- nsecs_t deadlineTimestamp;
- };
- std::vector<Invocation> invocations;
- {
- std::lock_guard lock(mMutex);
- auto const now = mTimeKeeper->now();
- mLastTimerCallback = now;
- for (auto it = mCallbacks.begin(); it != mCallbacks.end(); it++) {
- auto& callback = it->second;
- auto const wakeupTime = callback->wakeupTime();
- if (!wakeupTime) {
- continue;
- }
-
- auto const readyTime = callback->readyTime();
-
- auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast<nsecs_t>(0));
- if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) {
- callback->executing();
- invocations.emplace_back(Invocation{callback, *callback->lastExecutedVsyncTarget(),
- *wakeupTime, *readyTime});
- }
- }
-
- mIntendedWakeupTime = kInvalidTime;
- rearmTimer(mTimeKeeper->now());
- }
-
- for (auto const& invocation : invocations) {
- invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,
- invocation.deadlineTimestamp);
- }
这里的核心逻辑其实就是遍历mCallbacks,然后分别回调。
那么mCallbacks是怎么添加的呢? CallbackRepeater创建的时候,回去注册 mRegistration,同时会传入CallbackRepeater::callback方法作为回调,所以mCallbacks其实就是CallbackRepeater::callback。
- void callback(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
- ...
- mCallback(vsyncTime, wakeupTime, readyTime);
-
- ...
- }
很明显直接交给mCallback处理,所以我们又得看一下这个mCallback从何而来。
这个mCallback是CallbackRepeater创建时传入的DispSyncSource::onVsyncCallback方法:
- mCallbackRepeater =
- std::make_unique<CallbackRepeater>(vSyncDispatch,
- std::bind(&DispSyncSource::onVsyncCallback, this,
- std::placeholders::_1,
- std::placeholders::_2,
- std::placeholders::_3),
- name, workDuration, readyDuration,
- std::chrono::steady_clock::now().time_since_epoch());
所以,最终会调用到DispSyncSource::onVsyncCallback方法:
- void DispSyncSource::onVsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime,
- nsecs_t readyTime) {
- VSyncSource::Callback* callback;
- {
- std::lock_guard lock(mCallbackMutex);
- callback = mCallback;
- }
-
- ...
- if (callback != nullptr) {
- callback->onVSyncEvent(targetWakeupTime, {vsyncTime, readyTime});
- }
- }
又是回调,这里很绕。这里的callback其实就是EventThread,仍然是在创建EventThread的时候设置的:
- EventThread::EventThread(std::unique_ptr<VSyncSource> vsyncSource,
- android::frametimeline::TokenManager* tokenManager,
- InterceptVSyncsCallback interceptVSyncsCallback,
- ThrottleVsyncCallback throttleVsyncCallback,
- GetVsyncPeriodFunction getVsyncPeriodFunction)
- : mVSyncSource(std::move(vsyncSource)),
- mTokenManager(tokenManager),
- mInterceptVSyncsCallback(std::move(interceptVSyncsCallback)),
- mThrottleVsyncCallback(std::move(throttleVsyncCallback)),
- mGetVsyncPeriodFunction(std::move(getVsyncPeriodFunction)),
- mThreadName(mVSyncSource->getName()) {
-
- LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr,
- "getVsyncPeriodFunction must not be null");
-
- mVSyncSource->setCallback(this);
- ...
- }
所以,终于可以看到尽头了。最终其实就是调用到EventThread的onVSyncEvent方法:
- void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {
- std::lock_guard<std::mutex> lock(mMutex);
-
- LOG_FATAL_IF(!mVSyncState);
- mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,
- vsyncData.expectedPresentationTime,
- vsyncData.deadlineTimestamp));
- mCondition.notify_all();
- }
这里我们看到,会生成一个VSync信号,加入到mPendingEvents集合中,并且发出通知,让threadMain去获取,从而完成了VSync信号的生产者流程。
画了如下的surfaceFlinger结构图,方便理解(非完整版):

本章讲的主要流程如下图红圈所示:

3.3小节中讲到,SurfaceFlinger会通过BitTube的方式传递给APP侧Vsync信号。发送vscyn信号的方法在DisplayEventReceiver.cpp中,而接收方法也在这个类当中。而具体调用方则是DisplayEventDispatcher.cpp中的dispatchVsync方法。流程如下图所示:

上面在handleEvent中,processPendingEvents获取到了Vsync信号VsyncEventData后,交给dispatchVsync方法负责处理。
dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount, vsyncEventData);
dispatchVsync方法的实现者是android_view_DisplayEventReceiver.cpp,如下:
- void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
- uint32_t count, VsyncEventData vsyncEventData) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- ...
- jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData);
- env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
- timestamp, displayId.value, count, javaVsyncEventData);
- ...
- }
我们可以看到,先是把VsyncEventData转换为java可以接受的jobject对象,然后通过CallVoidMethod方法通知到java层中DisplayEventReceiver.java中的dispatchVsync方法。
首先DisplayEventReceiver中dispatchVsync方法被调用:
- // Called from native code.
- @SuppressWarnings("unused")
- private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
- VsyncEventData vsyncEventData) {
- onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);
- }
该方法中直接调用onVsync方法,调用到Choreographer.java中FrameDisplayEventReceiver下的onVsync方法:
- @Override
- public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
- VsyncEventData vsyncEventData) {
- try {
- ...
- if (timestampNanos > now) {
- timestampNanos = now;
- }
-
- if (mHavePendingVsync) {
-
- } else {
- mHavePendingVsync = true;
- }
-
- mTimestampNanos = timestampNanos;
- mFrame = frame;
- mLastVsyncEventData = vsyncEventData;
- Message msg = Message.obtain(mHandler, this);
- msg.setAsynchronous(true);
- mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- }
onVsync方法中,主要判断时间。如果timestampNanos>now,则是用当前时间。所以还是以方法的调用时间为准。然后通过handle转发到主线程中执行。
Message.obj=this,本身FrameDisplayEventReceiver又实现了Runnable接口,所以自然会执行FrameDisplayEventReceiver下的run方法:
- @Override
- public void run() {
- mHavePendingVsync = false;
- doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
- }
这时候我们就看到,执行到了doFrame方法,而这个方法也是就是渲染流程的执行者。
讲到这里,你或许有个疑问,上面流程中,如何执行到5.1中的handleEvent方法的呢?
主要是下图所示的流程:

如果你断点调试的时候,会发现下面所示的这个方法,竟然是主线程调用的:
- DisplayEventReceiver.java
-
-
- // Called from native code.
- @SuppressWarnings("unused")
- private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
- long frameTimelineVsyncId, long frameDeadline, long frameInterval) {
- onVsync(timestampNanos, physicalDisplayId, frame,
- new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval));
- }
为什么用竟然呢?因为一般来看,应该是子线程等待接受SurfaceFlinger的信号,收到了信号后交给主线程处理,如果是主线程去等待,岂不是主线程阻塞了?
这里使用looper.addFd()方法,在该方法中,用到了一个epoll_ctl的机制,即对FD文件进行监听,当FD改变时触发主线程的回调。如果处理完回调任务,则会进入epoll_wait的阻塞,继续监听。
问:高频次请求vsync信号,会突破60FPS的限制吗?
答:不会。
首先ViewRootImpl中做了一层处理,哪怕16ms改变了很多View的布局,最终执行到了scheduleTraversals方法时,因为有如下的判断,所以都只会执行一次vsync信号的请求和注册一次回调,直至收到VSYNC信号。
- void scheduleTraversals() {
- if (!mTraversalScheduled) {
- mTraversalScheduled = true;
- mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
- mChoreographer.postCallback(
- Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
- }
- }
取消限制:
- void doTraversal() {
- if (mTraversalScheduled) {
- mTraversalScheduled = false;
- }
其次,2.2小节中讲到,native请求VSYNC信号时也有一次限制,等到VSYNC信号时是不会再次发送请求的。
- status_t DisplayEventDispatcher::scheduleVsync() {
- if (!mWaitingForVsync) {
- ...
- status_t status = mReceiver.requestNextVsync();
- ...
- mWaitingForVsync = true;
- ...
- }
- return OK;
- }
https://www.jianshu.com/p/6083c590521b
https://www.jianshu.com/p/386bbb5fa29a //努比亚技术团队文章
1.本文是原创,根据网上资料和阅读ASOP的源码之后得出的结论,如有问题,欢迎指出。
2.图中涉及到的流程图如果想要高清大图或者pos格式的原图,可以私信我。