首页 > 其他分享 >Choreographer原理

Choreographer原理

时间:2024-03-03 13:55:59浏览次数:33  
标签:Choreographer frameTimeNanos frame long CALLBACK VSYNC 原理

Android 系统在 VSYNC 信号的指引下,有条不紊地进行者每一帧的渲染、合成操作,使我们可以享受稳定帧率的画面。引入 VSYNC 之前的 Android 版本,渲染一帧相关的 Message ,中间是没有间隔的,上一帧绘制完,下一帧的 Message 紧接着就开始被处理。这样的问题就是,帧率不稳定,可能高也可能低,不稳定。

Choreographer 是 Android 4.1 新增的机制,主要是配合 VSYNC ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 VSYNC 到来的时候 ,系统通过对 VSYNC 信号周期的调整,来控制每一帧绘制操作的时机。目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 VSYNC 的周期也设置为 16.6 ms,每个 16.6 ms,VSYNC 信号唤醒 Choreographer 来做 App 的绘制操作 ,这就是引入 Choreographer 的主要作用。

一、Choreographer 简介

Choreographer 通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 可以以一个稳定的帧率运行(目前大部分是 60fps),减少帧率波动带来的不适感。

1、负责接收和处理 App 的各种更新消息和回调,等到 VSYNC 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等。

2、负责请求和接收 VSYNC 信号。接收 VSYNC 事件回调(通过 FrameDisplayEventReceiver.onVsync());请求 VSYNC(FrameDisplayEventReceiver.scheduleVsync())。

二、原理解析

1、初始化

Choreographer构造方法
private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        //初始化FrameHandler,与looper绑定
        mHandler = new FrameHandler(looper);
        // 初始化 DisplayEventReceiver(开启VSYNC后将通过FrameDisplayEventReceiver接收VSYNC脉冲信号)
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;
        // 计算一帧的时间,Android手机屏幕采用60Hz的刷新频率(这里是纳秒 ≈16000000ns 还是16ms)
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        // 初始化CallbackQueue(CallbackQueue中存放要执行的输入、动画、遍历绘制等任务)
        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
        // b/68769804: For low FPS experiments.
        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
    }
获取Choreographer实例
    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };


可以看出构造的实例是与线程绑定的。

2、FrameHandler

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // 如果启用VSYNC机制,当VSYNC信号到来时触发
                // 执行doFrame(),开始渲染下一帧的操作
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
                    break;
				// 请求VSYNC信号,例如当前需要绘制任务时
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
				// 处理 Callback(需要延迟的任务,最终还是执行上述两个事件)
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

Choreographer 的所有任务最终都会发送到该 Looper 所在的线程。

3、Choreographer 初始化链

在 Activity 启动过程,执行完 onResume() 后,会调用 Activity.makeVisible(),然后再调用到 addView(), 层层调用会进入如下方法:

点击查看代码
ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app) 
-->WindowManagerImpl.addView(View, LayoutParams) (android.view) 
   -->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) 
      -->ViewRootImpl.ViewRootImpl(Context, Display) (android.view) 
          public ViewRootImpl(Context context, Display display) { 
              ...... 
              mChoreographer = Choreographer.getInstance(); 
              ...... 
             }

4、FrameDisplayEventReceiver

VSYNC 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类,FrameDisplayEventReceiver 是 DisplayEventReceiver 的子类, 有三个比较重要的方法:

  • onVsync():Vsync 信号回调,系统native方法会调用。
  • scheduleVsync():请求 Vsync 信号,当应用需要绘制时,通过 scheduledVsync() 方法申请 VSYNC 中断,来自 EventThread 的 VSYNC 信号就可以传递到 Choreographer。
  • run():执行 doFrame。

DisplayEventReceiver 是一个 abstract class,在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
        private VsyncEventData mLastVsyncEventData = new VsyncEventData();

        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
            super(looper, vsyncSource, 0);
        }

        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
        // for the internal display implicitly.
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                VsyncEventData vsyncEventData) {
            try {
                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                    Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                            "Choreographer#onVsync " + vsyncEventData.id);
                }
                // Post the vsync event to the Handler.
                // The idea is to prevent incoming vsync events from completely starving
                // the message queue.  If there are no messages in the queue with timestamps
                // earlier than the frame time, then the vsync event will be processed immediately.
                // Otherwise, messages that predate the vsync event will be handled first.
                long now = System.nanoTime();
                if (timestampNanos > now) {
                    Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                            + " ms in the future!  Check that graphics HAL is generating vsync "
                            + "timestamps using the correct timebase.");
                    timestampNanos = now;
                }

                if (mHavePendingVsync) {
                    Log.w(TAG, "Already have a pending vsync event.  There should only be "
                            + "one at a time.");
                } 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);
            }
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
        }
    }

可以看出,其最终是执行了run,然后调用doFrame绘制一帧。

5、Choreographer 处理一帧的逻辑

    void doFrame(long frameTimeNanos, int frame,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
        final long startNanos;
        final long frameIntervalNanos = vsyncEventData.frameInterval;
        try {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                        "Choreographer#doFrame " + vsyncEventData.id);
            }
            synchronized (mLock) {
                if (!mFrameScheduled) {
                    traceMessage
                            ("Frame not scheduled");
                    return; // no work to do
                }

                if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                    mDebugPrintNextFrameTimeDelta = false;
                    Log.d(TAG, "Frame time delta: "
                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
                }
                //预期执行时间
                long intendedFrameTimeNanos = frameTimeNanos;
                //当前时间
                startNanos = System.nanoTime();
                final long jitterNanos = startNanos - frameTimeNanos;
                //超过的时间是否大于一帧的时间
                if (jitterNanos >= frameIntervalNanos) {
                    final long skippedFrames = jitterNanos / frameIntervalNanos;
                    //掉帧超过30帧就打印log
                    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                        Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                                + "The application may be doing too much work on its main thread.");
                    }
                    final long lastFrameOffset = jitterNanos % frameIntervalNanos;
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                                + "which is more than the frame interval of "
                                + (frameIntervalNanos * 0.000001f) + " ms!  "
                                + "Skipping " + skippedFrames + " frames and setting frame "
                                + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                    }
                    frameTimeNanos = startNanos - lastFrameOffset;
                }
                // 未知原因,居然小于最后一帧的时间,重新申请VSYNC信号
                if (frameTimeNanos < mLastFrameTimeNanos) {
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                                + "previously skipped frame.  Waiting for next vsync.");
                    }
                    traceMessage("Frame time goes backward");
                    scheduleVsyncLocked();
                    return;
                }

                if (mFPSDivisor > 1) {
                    long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                    if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                        traceMessage("Frame skipped due to FPSDivisor");
                        scheduleVsyncLocked();
                        return;
                    }
                }

                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                        vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
                mFrameScheduled = false;
                mLastFrameTimeNanos = frameTimeNanos;
                
                mLastFrameIntervalNanos = frameIntervalNanos;
                mLastVsyncEventData = vsyncEventData;
            }

            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            //执行input任务
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

            mFrameInfo.markAnimationsStart();
            //执行animation任务
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
            //执行Insert animation任务
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                    frameIntervalNanos);

            mFrameInfo.markPerformTraversalsStart();
            //执行traversal任务,即测量绘制任务
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
            //执行commit任务
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
        }
    }

可以看到执行任务的顺序为:
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);

我们看一下doCalbacks方法:

    void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            // We use "now" to determine when callbacks become due because it's possible
            // for earlier processing phases in a frame to post callbacks that should run
            // in a following phase, such as an input event that causes an animation to start.
            final long now = System.nanoTime();
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;

            // Update the frame time if necessary when committing the frame.
            // We only update the frame time if we are more than 2 frames late reaching
            // the commit phase.  This ensures that the frame time which is observed by the
            // callbacks will always increase from one frame to the next and never repeat.
            // We never want the next frame's starting frame time to end up being less than
            // or equal to the previous frame's commit frame time.  Keep in mind that the
            // next frame has most likely already been scheduled by now so we play it
            // safe by ensuring the commit time is always at least one frame behind.
            if (callbackType == Choreographer.CALLBACK_COMMIT) {
                final long jitterNanos = now - frameTimeNanos;
                Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
                if (jitterNanos >= 2 * frameIntervalNanos) {
                    final long lastFrameOffset = jitterNanos % frameIntervalNanos
                            + frameIntervalNanos;
                    if (DEBUG_JANK) {
                        Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
                                + " ms which is more than twice the frame interval of "
                                + (frameIntervalNanos * 0.000001f) + " ms!  "
                                + "Setting frame time to " + (lastFrameOffset * 0.000001f)
                                + " ms in the past.");
                        mDebugPrintNextFrameTimeDelta = true;
                    }
                    frameTimeNanos = now - lastFrameOffset;
                    mLastFrameTimeNanos = frameTimeNanos;
                }
            }
        }
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

CallbackRecord是一个链表,从上面可以看出其遍历了链表,并且执行了CallbackRecord里面的run方法。

6、CallbackQueue与CallbackRecord

private final CallbackQueue[] mCallbackQueues;

    private final class CallbackQueue {
        private CallbackRecord mHead;
		.
		.
		.
   }

mCallbackQueues是一个CallbackQueue的数组,里面保存着CallbackQueue对象,而CallbackQueue里面持有CallbackRecord对象。
CallbackQueue保存了通过postCallback()添加的任务,目前定义的任务类型有:

  • CALLBACK_INPUT:优先级最高,和输入事件处理有关。
  • CALLBACK_ANIMATION:优先级其次,和 Animation 的处理有关
  • CALLBACK_INSETS_ANIMATION:优先级其次,和 Insets Animation 的处理有关
  • CALLBACK_TRAVERSAL:优先级最低,和 UI 绘制任务有关
  • CALLBACK_COMMIT:最后执行,和提交任务有关(在 API Level 23 添加)
    并且这些任务都是按顺序添加进去的,当收到VSYNC信号时候,会按照顺序执行这些任务。

CallbackRecord的数据结构如下

    private static final class
    CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;

        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                // 通过postFrameCallback()或postFrameCallbackDelayed()添加的任务会执行这里
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
    }

可以看出CallbackRecord其实是一个链表结构,它的run方法会调用传进来的Runnable的run方法或者FrameCallback的doFrame方法。

7、添加任务

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

里面会调用mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        @UnsupportedAppUsage
        public void addCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
            CallbackRecord entry = mHead;
            if (entry == null) {
                mHead = callback;
                return;
            }
            if (dueTime < entry.dueTime) {
                callback.next = entry;
                mHead = callback;
                return;
            }
            while (entry.next != null) {
                if (dueTime < entry.next.dueTime) {
                    callback.next = entry.next;
                    break;
                }
                entry = entry.next;
            }
            entry.next = callback;
        }

其实就是取出数组里对应任务链表,按照时间添加到链表中的对应位置。

三、调用栈

1、Animation 回调调用栈

一般接触的多的是调用 View.postOnAnimation() 的时候,会使用到 CALLBACK_ANIMATION,View 的 postOnAnimation() 方法源码如下:

public void postOnAnimation(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    // 判断AttachInfo是否为空
    if (attachInfo != null) {
        // 如果不为null,直接调用Choreographer.postCallback()方法
        attachInfo.mViewRootImpl.mChoreographer.postCallback(
                Choreographer.CALLBACK_ANIMATION, action, null);
    } else {
        // 否则加入当前View的等待队列
        getRunQueue().post(action);
    }
}

另外 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION,Choreographer 的 postFrameCallbackDelayed() 方法源码如下:

public void postFrameCallbackDelayed(Choreographer.FrameCallback callback, long delayMillis) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis);

2、ViewRootImpl 调用栈

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

四、总结

  1. Choreographer 是线程单例的,而且必须要和一个Looper绑定,因为其内部有一个Handler需要和Looper绑定,一般是 App 主线程的Looper` 绑定。
  2. DisplayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IDisplayEventConnection 的 VSYNC 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 VSYNC 信号到来时,DisplayEventReceiver 的 onVsync() 方法将被调用。
  3. DisplayEventReceiver 还有一个 scheduleVsync` 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。
  4. Choreographer 定义了一个 FrameCallback 接口,每当 VSYNC 到来时,其 doFrame() 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。
  5. Choreographer 的主要功能是,当收到 VSYNC 信号时,去调用使用者通过 postCallback() 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:
  6. CALLBACK_INPUT:处理输入事件处理有关
  7. CALLBACK_ANIMATION:处理 Animation 的处理有关
  8. CALLBACK_INSETS_ANIMATION:处理 Insets Animation 的相关回调
  9. CALLBACK_TRAVERSAL:处理和 UI 等控件绘制有关
  10. CALLBACK_COMMIT:处理 Commit 相关回调
  11. ListView 的 Item 初始化(obtain\setup) 会在 Input 里面也会在 Animation 里面,这取决于 CALLBACK_INPUT、CALLBACK_ANIMATION 会修改 View 的属性,所以要先与 CALLBACK_TRAVERSAL 执行。
  12. 每次执行完callback后会自动移除。

参考:https://henleylee.github.io/posts/2020/f831dca5.html,https://androidperformance.com/2019/10/22/Android-Choreographer/#/Choreographer-自身的掉帧计算逻辑

标签:Choreographer,frameTimeNanos,frame,long,CALLBACK,VSYNC,原理
From: https://www.cnblogs.com/tangZH/p/17127519.html

相关文章

  • 《大型网站技术架构:核心原理与案例分析》读后感
    《大型网站技术架构:核心原理与案例分析》这本书,对我而言,不仅仅是一本介绍技术架构的专著,它更是一次深入探索互联网技术奥秘的精神之旅。作者李智慧以其丰富的行业经验和深厚的技术底蕴,为我们揭开了大型网站背后复杂架构的神秘面纱。在阅读第一章后,我被作者系统化、层次分明的叙述......
  • ChatGPT的工作原理
    ChatGPT是美国OpenAI研发的聊天机器人程序,2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话。原理ChatGPT是一种基于人工智能技术的自然语言生成模型,它能够从大量的数据和历史对话中学习,并生成与人类语言相似的输出......
  • C++类开发的第六篇(虚拟继承实现原理和cl命令的使用的bug修复)
    Class_memory接上一篇末尾虚拟继承的简单介绍之后,这篇来详细讲一下这个内存大小是怎么分配的。使用clcl是MicrosoftVisualStudio中的C/C++编译器命令。通过在命令行中键入cl命令,可以调用VisualStudio的编译器进行编译操作。cl命令提供了各种选项和参数,用于指定源......
  • 【Spring AOP】SpringAOP配置过程——基于XML&&基于注解 && Spring AOP实现原理
    概念SpringAOP-AspectOrientedProgramming面向切面编程AOP的做法是将通用、与业务无关的功能抽象封装为切面类切面可配置在目标方法的执行前、后运行,真正做到即插即用可以在不修改源码的情况下对程序进行扩展AOP配置过程——基于XML配置0.添加依赖创建配置文件......
  • Kafka 集群工作原理
    Broker集群工作原理broker启动后,会向zookeeper注册,并记录在Kafka配置节点下的/brokers/ids节点下,之后抢占/controller节点,率先注册的节点的Controller就会负责Leader的选举选举节点会监听/brokers/ids节点的变化,之后根据选举规则选举出Leader,并将Leader信......
  • Vue源码解读:响应式原理
    Vue一大特点就是数据响应式,数据的变化会作用于视图而不用进行DOM操作。原理上来讲,是利用了Object.defifineProperty(),通过定义对象属性setter方法拦截对象属性的变更,从而将属性值的变化转换为视图的变化。在Vue初始化时,会调用initState,它会初始化props,methods,data,......
  • kafka节点故障恢复原理
    Kafka的LEO和HWLEOLEO是Topic每一个副本的最后的偏移量offset+1HW(高水位线)HighWaterMark是所有副本中,最小的LEOFollower副本所在节点宕机由于数据同步的时候数据是先写入Leader,然后Follower副本向Leader同步只要Leader和其他的Follower副本继续往前存储数据,挂掉的节点在......
  • 置换群 / Polya 原理 / Burnside 引理 学习笔记
    置换群/Polya原理/Burnside引理学习笔记在GJOI上做手链强化,经过长达三小时的OEIS和手推无果后开摆,喜提rnk12,故开始学习置换群相关内容。笔记主要以Polya原理和Burnside引理的应用为主,所以会非常简单,很大一部分的群论概念和证明不会写,因为我不会。基础群论定......
  • JAVA基础:数组在计算机中的执行原理 多个变量指向一个数组
    程序都是在计算机中的内存中执行的,java程序编译之后会产生一个class文件,这个class文件是提取到内存中的JVM虚拟机中执行的。java为了便于虚拟机这个java程序,也即这个class文件。将虚拟机这块内存区域进行了划分:方法区,栈,堆,  本地方法栈,程序计数器方法区:放编译后的class文件的......
  • Vue学习笔记19--key的原理
    react、vue中key的作用(key的原理?):虚拟DOM中key的作用:key是虚拟DOM对象的标识,当张贴中的数据发生变化时,vue会根据--新数据,生成新的虚拟DOM,随后vue进行新虚拟DOM与旧虚拟DOM的差异比较。比较规则如下:对比规则旧虚拟DOM中找到了与新虚拟DOM相同的key若虚拟DOM中内容没......