30分钟彻底了解Flutter整个渲染流程(超详细)

2024-06-04 3048阅读

30分钟彻底了解Flutter整个渲染流程[超详细]

  • 从运行第一行代码出发
    • WidgetsFlutterBinding初始化了一堆娃
    • 三个中流砥柱
      • SchedulerBinding
      • RendererBinding
      • WidgetsBinding
      • 申请Vsync流程
      • 下发Vsync
      • 承接Vsync

        从运行第一行代码出发

        void main() {
          runApp(const MyApp());
        }
        void runApp(Widget app) {
          WidgetsFlutterBinding.ensureInitialized()
            ..scheduleAttachRootWidget(app)
            ..scheduleWarmUpFrame();
        }
        

        WidgetsFlutterBinding.ensureInitialized作用是初始化WidgetsFlutterBinding对象。

        //...
         WidgetsFlutterBinding.ensureInitialized()
        //..
        static WidgetsBinding ensureInitialized() {
            if (WidgetsBinding.instance == null)
              WidgetsFlutterBinding();
            return WidgetsBinding.instance!;
          }
        

        WidgetsFlutterBinding初始化了一堆娃

        WidgetsFlutterBinding里面继承了BindingBase。他会初始化BindingBase的构造方法。并且

        并且with了很多类,而这些类都继承了BindingBase.也就间接对这些类进行了初始化工作

        class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
        //...
        }
        

        在BindingBase构造方法中,它调用了initInstances和initServiceExtensions。但是这里面只是做了一些debug模式一些初始化工作。由于WidgetsFlutterBinding所有with的类都是BindingBase的子类(如下图),这些子类如SchedulerBinding,RendererBinding,WidgetsBinding等他们都各自实现了initInstances, initServiceExtensions. 所以BindingBase构造函数本质是为了调用WidgetsFlutterBinding所有with的类里面的initInstances,initServiceExtensions

        30分钟彻底了解Flutter整个渲染流程(超详细) 第1张

        abstract class BindingBase {
          BindingBase() {
        	//...
            initInstances();
        	//...
            initServiceExtensions();
          }
         @protected
          @mustCallSuper
          void initInstances() {
            //...
            //做debug模式的初始化活
        	//...
          }
         @protected
          @mustCallSuper
          void initServiceExtensions() {
            //...
            //做debug模式的初始化活
        	//...
          }
        }
        

        三个中流砥柱

        在这我们重点关注SchedulerBinding, RendererBinding, WidgetsBinding

        SchedulerBinding

        SchedulerBinding在这个渲染环节中主要负责请求Vsync和接收Vsync回调的工作,并且回调会消费每一帧之前的事件任务,然后进行布局绘制。后面会介绍他是怎么被执行的。(不了解什么是Vsync看看我这篇文章2分钟带你了解什么是Vsync)

        它的initInstances里面只是做了SchedulerBinding的instance单例初始化.

        mixin SchedulerBinding on BindingBase {
         static SchedulerBinding? get instance => _instance;
         static SchedulerBinding? _instance;
         @override
          void initInstances() {
            super.initInstances();
            _instance = this;
            //..
          }
        @override
          void initServiceExtensions() {
            //...
            //做debug模式的初始化活
        	//...
          }
         
         //申请vsync
         void scheduleFrame() {
            //...
            ensureFrameCallbacksRegistered();
            window.scheduleFrame();
         }
          //下面的CALLBACK,每一帧都会在UI绘制之前执行
          void ensureFrameCallbacksRegistered() {
            //执行里面scheduleFrameCallback注册的回调,动画之类的事件
            window.onBeginFrame ??= _handleBeginFrame;
            //执行addPersistentFrameCallback和addPostFrameCallback中注册的回调
            window.onDrawFrame ??= _handleDrawFrame;
          }
        void _handleBeginFrame(Duration rawTimeStamp) {
            //...
            handleBeginFrame(rawTimeStamp);
          }
          void _handleDrawFrame() {
           //...
            handleDrawFrame();
          }
        

        RendererBinding

        RendererBinding主要是负责管理渲染的职能。

        在RendererBinding的initInstances中,他同样会初始化RendererBinding单例instance.并且初始化PipelineOwner和RenderView. 其中PipelineOwner负责管理绘制工作,RenderView是整个App的渲染树

        static RendererBinding? get instance => _instance;
        static RendererBinding? _instance;
        @override
        void initInstances() {
          super.initInstances();
          _instance = this;
          _pipelineOwner = PipelineOwner(
              onNeedVisualUpdate: ensureVisualUpdate,
              onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
              onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
            );	
          //...
          initRenderView();
          //...
          addPersistentFrameCallback(_handlePersistentFrameCallback);
        }
         void initRenderView() {
            //..
            renderView = RenderView(configuration: createViewConfiguration(), window: window);
            renderView.prepareInitialFrame();
         }
        @override
        void initServiceExtensions() {
            //...
            //做debug模式的初始化活
        	//...
        }
        

        最后,他会调用addPersistentFrameCallback绑定绘制页面的callback.

        也就是这个_handlePersistentFrameCallback被触发时候会调用drawFrame, drawFrame这个方法是对所有被标记需要刷新的页面进行布局和绘制。 这里不展开具体绘制过程。

        void _handlePersistentFrameCallback(Duration timeStamp) {
        	//
            drawFrame();
            //...
        }
         //开始绘制
         void drawFrame() {
           //进行布局
            pipelineOwner.flushLayout();
            //进行绘制
            pipelineOwner.flushPaint();
            //...
          }
        

        30分钟彻底了解Flutter整个渲染流程(超详细) 第2张

        WidgetsBinding

        WidgetsBinding主要用来挂载BuildOwner管理Element这棵树。

        他的initInstances里面会同样会初始化他的单例方法,并且会初始化携带BuildOwner。

          static WidgetsBinding? get instance => _instance;
          static WidgetsBinding? _instance;
          @override
          void initInstances() {
            super.initInstances();
            _instance = this;
         	//...
            _buildOwner = BuildOwner();
            //这个方法将会被scheduleBuildFor调用
            buildOwner!.onBuildScheduled = _handleBuildScheduled;
            //..
          }
          @override
          void initServiceExtensions() {
            //...
            //做debug模式的初始化活
        	//...
          }
        

        BuildOwner是整棵Element的树的管理类。每个Element都会有这个BuildOwner的唯一实例。BuildOwner在WidgetsBinding绑定了onBuildScheduled方法,也就是_handleBuildScheduled, 这个方法会调用ensureVisualUpdate,然后调用SchedulerBinding的 scheduleFrame方法,从而可以申请Vsync信号,从而获取下一帧的绘制。

        onBuildScheduled这个方法将被scheduleBuildFor调用。当Element执行markNeedsBuild后就会调用BuildOwner的scheduleBuildFor调用,然后调用了onBuildScheduled。

        State->setState->markNeedsBuild->scheduleBuildFor->onBuildScheduled->ensureVisualUpdate->scheduleFrame

        _handleBuildScheduled(){
        	//....
            ensureVisualUpdate();
        }
        void ensureVisualUpdate() {
            switch (schedulerPhase) {
              case SchedulerPhase.idle:
              case SchedulerPhase.postFrameCallbacks:
                scheduleFrame();
                return;
              case SchedulerPhase.transientCallbacks:
              case SchedulerPhase.midFrameMicrotasks:
              case SchedulerPhase.persistentCallbacks:
                return;
            }
          }
        

        等到下一帧到来的时候,被标记的Element就会被遍历执行渲染对象的布局和绘制。怎么被标记?看看平时的setState方法,

        State

          @protected
          void setState(VoidCallback fn) {
               _element!.markNeedsBuild();
         }
         
        

        Element

        BuildOwner? _owner;
        void markNeedsBuild() {
            //...
            _dirty = true;
           owner!.scheduleBuildFor(this);
          //..
        }
        

        BuildOwner

          void scheduleBuildFor(Element element) {
          //..
           //申请Vsync
            onBuildScheduled!();
           //..
          	_dirtyElements.add(element);
          	//
            element._inDirtyList = true;
           //..
          }
        

        首先会被标记_dirty=true代表需要被更新的对象, 然后会放到_dirtyElements里面,并且标记_inDirtyList=true已经添加到element树里面

        综上所述,WidgetsFlutterBinding.ensureInitialized()做了以下几件事情:

        1. 创建个WidgetsFlutterBinding实例
        2. 初始化, SchedulerBinding, RendererBinding, WidgetsBinding 等所有单例
        3. WidgetsBinding.instance=SchedulerBinding.instance=RendererBinding.instance=WidgetsFlutterBinding()
        4. 然后执行SchedulerBinding, RendererBinding, WidgetsBinding 所有类的initInstances,initServiceExtensions方法
        5. 完成SchedulerBinding, RendererBinding, WidgetsBinding 所有相关的callback绑定,完成渲染树,Element树的管理类的初始化

        接下来我们看看…scheduleAttachRootWidget(app),做了什么

        void runApp(Widget app) {
          WidgetsFlutterBinding.ensureInitialized()
            ..scheduleAttachRootWidget(app)
            ..scheduleWarmUpFrame();
        }
        

        scheduleAttachRootWidgets属于WidgetsBinding的方法,调用了attachRootWidget,这个方法作用是将MyApp作为Widget树的根结点绑定到_renderViewElement这个棵Element树上,并且将渲染树也绑定到上面,由于第一次执行,renderViewElement肯定是空的,所以会触发SchedulerBinding.instance!.ensureVisualUpdate(),在上面已经提过ensureVisualUpdate这个方法,这里是第一次执行,所以他会注册_handleBeginFrame,_handleDrawFrame的回调(先记住这里注册,后面会讲解怎么被系统执行的)。然后请求Vsync,将会得到下一帧的绘制回调(注意在这里,是第一帧)

        WidgetsBinding

        @protected
          void scheduleAttachRootWidget(Widget rootWidget) {
            Timer.run(() {
              attachRootWidget(rootWidget);
            });
          }
         //这个方法将Widget,Render树都绑定在Element树上
         void attachRootWidget(Widget rootWidget) {
             final bool isBootstrapFrame = renderViewElement == null;
            _readyToProduceFrames = true;
            _renderViewElement = RenderObjectToWidgetAdapter(
              container: renderView,
              debugShortDescription: '[root]',
              child: rootWidget,
            ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement?);
            //如果是第一次,这里会执行
            if (isBootstrapFrame) {
              //由于之前执行了WidgetsFlutterBinding.ensureInitialized()
              // SchedulerBinding.instance确保有实例的
              // 请求下一帧绘制,也就是第一帧绘制。
              SchedulerBinding.instance!.ensureVisualUpdate();
            }
          }
        

        接下来是…scheduleWarmUpFrame(),这个方法是属于SchedulerBinding,

        主要是执行了handleBeginFrame和handleDrawFrame。上面已经讲解这2个方法的作用,

        由于在RendererBinding中addPersistentFrameCallback,并且调用drawFrame方法,所以

        执行handleDrawFrame会执行drawFrame这个方法,从而会布局和绘制页面。

        void scheduleWarmUpFrame() {
         //...
         handleBeginFrame(null);
         //...
         handleDrawFrame();
        }
         //执行里面scheduleFrameCallback注册的回调,动画之类的事件
        void handleBeginFrame(Duration? rawTimeStamp) {
        //...
           try {
              // TRANSIENT FRAME CALLBACKS
              _frameTimelineTask?.start('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
              _schedulerPhase = SchedulerPhase.transientCallbacks;
              final Map callbacks = _transientCallbacks;
              _transientCallbacks = {};
              callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
                if (!_removedIds.contains(id))
                  _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
              });
              _removedIds.clear();
            } finally {
              _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
            }
        }
        //执行addPersistentFrameCallback和addPostFrameCallback中注册的回调
        //
         void handleDrawFrame() {
          //...
              _schedulerPhase = SchedulerPhase.persistentCallbacks;
              for (final FrameCallback callback in _persistentCallbacks)
                _invokeFrameCallback(callback, _currentFrameTimeStamp!);
              // POST-FRAME CALLBACKS
              _schedulerPhase = SchedulerPhase.postFrameCallbacks;
              final List localPostFrameCallbacks =
                  List.of(_postFrameCallbacks);
              _postFrameCallbacks.clear();
              for (final FrameCallback callback in localPostFrameCallbacks)
                _invokeFrameCallback(callback, _currentFrameTimeStamp!);
          //..
         }
        

        综上所述,scheduleAttachRootWidget和 scheduleWarmUpFrame做如下几件事情:

        1. 将所有树绑定关联起来
        2. 设置好Vsync信号下一帧回来执行的回调(后面验证 window是在哪里被执行的)

          window.onBeginFrame ??= _handleBeginFrame;

          window.onDrawFrame ??= _handleDrawFrame;

        3. 请求获取第一帧的绘制
        4. 强制执行handleBeginFrame和 handleDrawFrame

        感叹,普普通通的一行runApp,会触发这么多业务,如果不是细细品尝,很难发现这些关系。

        上面讲的都是如何申请Vsync,然后被动触发事件任务的执行,还有布局的绘制工作,那么接下来需要串通的是,如何给Vsync发出申请,然后原生App怎么下发Vsync给Flutter下发执行的

        申请Vsync流程

        我们回头看上面提到的scheduleFrame会请求Vsync这个方法,最终会执行window.scheduleFrame, scheduleFrame是在window.dart这个类里

        mixin SchedulerBinding on BindingBase{
        	//..
          void scheduleFrame() {
        	//..
            ensureFrameCallbacksRegistered();
            window.scheduleFrame();
           //...
          }
        	//..
        }
        

        window其实是FlutterWindow的引用

        class FlutterWindow extends FlutterView {
        //...
        @override
        final PlatformDispatcher platformDispatcher;
        void scheduleFrame() => platformDispatcher.scheduleFrame();
        //...
        }
        

        对于PlatformDispatcher的scheduleFrame,是调用了native方法

        class PlatformDispatcher{
          void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
        }
        

        接下来,我们来到flutter引擎源码, lib/ui/window/platform_configuration.cc.

        他执行的是PlatformConfigurationNativeApi::ScheduleFrame.他调用了

        PlatformConfigurationClient的ScheduleFrame方法

        PlatformConfigurationClient* client_;
        PlatformConfigurationClient* client() const { return client_; }
        //
        void PlatformConfigurationNativeApi::ScheduleFrame() {
          UIDartState::ThrowIfUIOperationsProhibited();
          UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
        }
        

        而PlatformConfigurationClient是由RuntimeController实现的,代码在runtime/runtime_controller.cc

        class RuntimeController : public PlatformConfigurationClient 
          RuntimeDelegate& client_;
        }
        void RuntimeController::ScheduleFrame() {
          //
          client_.ScheduleFrame();
        }
        

        接着是用RuntimeDelegate进行申请,也就是引擎类,他在shell/common/engine.h这个路径,

        他会调用animator_的RequestFrame,这个才是最终真正进行申请的类

        class Engine final : public blink::RuntimeDelegate{
        	//..
        	Animator& animator_;
        	//..
        }
        void Engine::ScheduleFrame(bool regenerate_layer_tree) {
          animator_->RequestFrame(regenerate_layer_tree);
        }
        

        代码在shell/common/animator.cc

        void Animator::RequestFrame(bool regenerate_layer_tree) {
          //......
          task_runners_.GetUITaskRunner()->PostTask(//......
               frame_request_number = frame_request_number_]() {
                //......
                //申请Vsync
                self->AwaitVSync();
              });
        }
        

        我们下面来看看Animator的源码AwaitVSync,

        class Animator final 
        {
          std::shared_ptr waiter_;
        }
        void Animator::AwaitVSync() {
          waiter_->AsyncWaitForVsync(
              [self = weak_factory_.GetWeakPtr()](
                  std::unique_ptr frame_timings_recorder) {
                //...
                self->BeginFrame(std::move(frame_timings_recorder));
        		//...
              });
        }
        

        他是委托VsyncWaiter实现,文件在shell/common/vsync_waiter.cc

        void VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {
          //......
          callback_ = std::move(callback);
          //......
          AwaitVSync();
        }
        

        然后点 AwaitVSync进去发现, 是空实现。头大了,找了很久发现是在shell/platform/android/vsync_waiter_android.cc里面实现的.也就是他对应在安卓的VsyncWaiterAndroid::AwaitVSync源码中

        void VsyncWaiterAndroid::AwaitVSync() {
          //......
          task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
            JNIEnv* env = fml::jni::AttachCurrentThread();
            env->CallStaticVoidMethod(
                g_vsync_waiter_class->obj(),  
                //调用安卓的asyncWaitForVsync
                g_async_wait_for_vsync_method_,
                java_baton
            );
          });
        }
        

        VsyncWaiterAndroid::AwaitVSync它会调用安卓的Java文件FlutterJNI.java的静态方法

        asyncWaitForVsync

        30分钟彻底了解Flutter整个渲染流程(超详细) 第3张

        asyncWaitForVsyncDelegate是个接口

         public interface AsyncWaitForVsyncDelegate {
            void asyncWaitForVsync(final long cookie);
         }
        

        让我们看看他的实现类

        // TODO(mattcarroll): add javadoc.
        public class VsyncWaiter {
        private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate =
              new FlutterJNI.AsyncWaitForVsyncDelegate() {
                @Override
                public void asyncWaitForVsync(long cookie) {
                  Choreographer.getInstance()
                      .postFrameCallback(
                          new Choreographer.FrameCallback() {
                            @Override
                            public void doFrame(long frameTimeNanos) {
                              long delay = System.nanoTime() - frameTimeNanos;
                              if (delay  
        

        ,好家伙,原来他是用Choreographer.getInstance().postFrameCallback

        这个方法会触发申请Vsync,然后收到Vsync会回调这个new Choreographer.FrameCallback.

        也就是说,等Vsync下发回来的时候执行Choreographer.FrameCallback的doFrame方法。

        不了解Android中Choreographer的朋友,可以阅读我这篇文章点击>>15分钟带你彻底了解App绘制流程-安卓篇

        找到申请Vsync后,下一步就是把Vsync发到Flutter,执行下一帧的工作

        下发Vsync

        在Choreographer.FrameCallback的doFrame执行中,会调用 flutterJNI.onVsync,在这你已经猜到了,这里开始要下发Vsync给flutter了, 于是上面的waiter_->AsyncWaitForVsync就会执行回调,也就是

        //...
        self->BeginFrame(std::move(frame_timings_recorder));
        //...
        

        让我们一路往下看

        void Animator::BeginFrame(
            std::unique_ptr frame_timings_recorder) {
          //...
          delegate_.OnAnimatorBeginFrame(frame_target_time, frame_number);
          //...
        }
        void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_target_time,
                                         uint64_t frame_number) {
          //...
          if (engine_) {
            engine_->BeginFrame(frame_target_time, frame_number);
          }
        }
        void Engine::BeginFrame(fml::TimePoint frame_time, uint64_t frame_number) {
          //..
          runtime_controller_->BeginFrame(frame_time, frame_number);
        }
        

        runtime_controller_在上面讲过,他是PlatformConfiguration的实例。

        我们现在需要去flutter看看一个文件ui.dart

        30分钟彻底了解Flutter整个渲染流程(超详细) 第4张

        里面part了hooks.dart, 而hooks里面声明了很多被c++调用的代码其中有

        @pragma('vm:entry-point')
        void _drawFrame() {
          PlatformDispatcher.instance._drawFrame();
        }
        @pragma('vm:entry-point')
        void _beginFrame(int microseconds, int frameNumber) {
          PlatformDispatcher.instance._beginFrame(microseconds);
          PlatformDispatcher.instance._updateFrameData(frameNumber);
        }
        

        PlatformConfiguration中,有个方法将hooks的方法做了关联,关联了_beginFrame,和_drawFrame,也就是PlatformConfiguration可以调用这个2个方法,如下

        void PlatformConfiguration::DidCreateIsolate() {
          Dart_Handle library = Dart_LookupLibrary(tonic::ToDart("dart:ui"));
          //...
          begin_frame_.Set(tonic::DartState::Current(),
                           Dart_GetField(library, tonic::ToDart("_beginFrame")));
          draw_frame_.Set(tonic::DartState::Current(),
                          Dart_GetField(library, tonic::ToDart("_drawFrame")));
          //...
        }
        

        承接Vsync

        接着来看看runtime_controller_的方法BeginFrame, 你会发现,这个方法其实就是调用了

        hooks.dart的_beginFrame和_drawFrame方法

        void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,
                                               uint64_t frame_number) {
          //......
          tonic::LogIfError(
              tonic::DartInvoke(begin_frame_.Get(), {
                  Dart_NewInteger(microseconds),
                  Dart_NewInteger(frame_number),
              }));
          UIDartState::Current()->FlushMicrotasksNow();
          tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get()));
        }
        

        _beginFrame和_drawFrame这两个方法是被由PlatformDispatcher持有的。

        接着回头看看上面提到的ensureFrameCallbacksRegistered这个方法,

          void ensureFrameCallbacksRegistered() {
            window.onBeginFrame ??= _handleBeginFrame;
            window.onDrawFrame ??= _handleDrawFrame;
         }
        

        看看window是怎么设置onBeginFrame和onDrawFrame的

        window.dart

          FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
          set onBeginFrame(FrameCallback? callback) {
            platformDispatcher.onBeginFrame = callback;
          }
          VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
          set onDrawFrame(VoidCallback? callback) {
            platformDispatcher.onDrawFrame = callback;
          }
        

        也就是说,Vsync下发回来其实就是触发window设置的回调onBeginFrame(SchedulerBinding的_handleBeginFrame)和onDrawFrame(SchedulerBinding的_handleDrawFrame).

        这2个方法被回调就会进行事件任务的执行,以及布局绘制的工作。

        终于,到这里所有流程都串通了.

        我们重新梳理下所有流程:

        1. 先初始化SchedulerBinding,RendererBinding,WidgetsBinding单例,确保不为空
        2. RendererBinding绑定persistentFrameCallback每次收到Vsync就进行布局和绘制工作
        3. 将SchedulerBinding的_handleBeginFrame,_handleDrawFrame通过window.onBeginFrame, onDrawFrame方法被绑定到platformDispatcher
        4. 初始化三棵树的绑定
        5. 申请第一个Vsync绘制第一帧
        6. 执行native方法从而让c++代码向安卓发出请求Vsync请求并绑定回调
        7. 安卓获取到Vsync后调用JNI传递Vsync给c++, 然后c++调用SchedulerBinding的_handleBeginFrame,_handleDrawFrame从而完成一帧的工作

        好了,所以流程已经梳理完毕,是不是很赞?如果这篇文章对你有帮助,请关注🙏,点赞👍,收藏😋三连哦


    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]