Android源码分析 - View的触摸事件分发

news2024/10/6 10:36:28

0. 相关分享

Android源码分析 - InputManagerService与触摸事件

1. 接收Input系统发送来的事件

时序图源:稀土掘金

APP端事件处理流程

在注册Window的时候,来到ViewRootImpl,其中不仅发起窗口注册,还开启了输入事件的监听:

//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {
	res = mWindowSession.addToDisplayAsUser();//注册window
    
    //注册输入事件监听器
    mInputEventReceiver= new WindowInputEventReceiver(inputChannel, Looper.myLooper());
}

在ViewRootImpl的内部类WindowInputEventReceiver中重写了InputEventReceiver的onInputEvent()方法,处理事件分发:

//ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
    @Override
    public void onInputEvent(InputEvent event) {
        List<InputEvent> processedEvents;
        try {
            processedEvents =
                mInputCompatProcessor.processInputEventForCompatibility(event);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        if (processedEvents != null) {
            if (processedEvents.isEmpty()) {
                finishInputEvent(event, true);
            } else {
                //如果有输入事件,分发
                for (int i = 0; i < processedEvents.size(); i++) {
                    //入队
                    enqueueInputEvent(
                        processedEvents.get(i), this,
                        QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                }
            }
        } else {
            enqueueInputEvent(event, this, 0, true);
        }
    }
    //...
}

接收到输入事件之后,来到ViewRootImpl内部类QueuedInputEvent,一个QueuedInputEvent存着一个InputEvent和对应的InputEventReceiver,ViewRootImpl.enqueueInputEvent()将消息存入:

//ViewRootImpl
private static final class QueuedInputEvent {
    public QueuedInputEvent mNext;//复用池
    public InputEvent mEvent;
    public InputEventReceiver mReceiver;
    public int mFlags;
}

void enqueueInputEvent(InputEvent event,
                       InputEventReceiver receiver, int flags, boolean processImmediately) {
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);//获取一个可用的QueuedInputEvent对象
    QueuedInputEvent last = mPendingInputEventTail;//本身QueuedInputEvent也是个队列,队尾为mPendingInputEventTail
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;//添加在队尾
        mPendingInputEventTail = q;
    }
    //事件+1
    mPendingInputEventCount += 1;
    //立即分发来到doProcessInputEvents();
    if (processImmediately) {
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}

由于传入时,processImmediately=true,所以来到doProcessInputEvents立即分发事件:

//ViewRootImpl.java
void doProcessInputEvents() {
    //一个个出队,直到所有事件都处理完
    while (mPendingInputEventHead != null) {
        //取出队头,(队列先进先出,队头先出)
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        if (mPendingInputEventHead == null) {
            mPendingInputEventTail = null;
        }
        q.mNext = null;
        mPendingInputEventCount -= 1;
        mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
		//分发事件
        deliverInputEvent(q);
    }

    if (mProcessInputEventsScheduled) {
        mProcessInputEventsScheduled = false;
        mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
    }
}

顾名思义 deliverInputEvent将会将事件分发到相关的处理者身上,不过这里并不是直接传给View,而是先交给InputState:

//ViewRootImpl.java
private void deliverInputEvent(QueuedInputEvent q) {
    try {
        //这个mFirstPostImeInputState和mFirstInputState在setView()的时候进行了初始化,对应的SynctheticInputState是ViewRootImpl的内部类,被逐层包装在InputStage中(单向链表,deliver()方法责任链模式分发QueuedInputEvent给下一个InputState),最终会分发到SynctheticInputState
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (q.mEvent instanceof KeyEvent) {//如果是按键
            try {
                mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }

        if (stage != null) {
            //责任链模式地分发QueuedInputEvent事件
            handleWindowFocusChanged();
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

这里的InputState通过责任链的方式,将QueuedInputEvent传递给InputState链表,逐个处理,首先来看到在ViewRootImpl的setView()中有哪些InputState:

//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {
    // Set up the input pipeline.
    CharSequence counterSuffix = attrs.getTitle();
    //最底层
    mSyntheticInputStage = new SyntheticInputStage();
    //ViewPostImeState接在链头
    InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
    //NativePostImeState接在链头
    InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                                                                "aq:native-post-ime:" + counterSuffix);
    InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
    InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                                            "aq:ime:" + counterSuffix);
    InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
    InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                                                              "aq:native-pre-ime:" + counterSuffix);
	//最后赋给全局
    mFirstInputStage = nativePreImeStage;
    mFirstPostImeInputStage = earlyPostImeStage;
}

事件分发的时候,会通过deliver()方法分发事件,并以责任链模式传递给下一个InputState处理。先来看到InputState的deliver()方法,然后我们再直接看到ViewPostImeState处理输入事件。责任链主要做了几件事:

  1. 通过 deliver(QueuedInputEvent q) , 进入事件在链上传递
  2. onProcess(q) , 处理事件
  3. apply(q,result), 事件传递给下一个处理者 mNext.deliver(q),或者直接终止事件传递 finishInputEvent(q)
//ViewRootImpl.InputStage.java

//1. 从inputState.deliver()为入口开始处理事件并传递责任链
public final void deliver(QueuedInputEvent q) {
    if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
        forward(q);
    } else if (shouldDropInputEvent(q)) {
        finish(q, false);
    } else {
        traceEvent(q, Trace.TRACE_TAG_VIEW);
        final int result;
        try {
            //处理事件
            result = onProcess(q);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        //继续分发事件
        apply(q, result);
    }
}

//2. onProcess()处理完事件后,apply()交给下一个处理者
protected void apply(QueuedInputEvent q, int result) {
    //如果继续分发
    if (result == FORWARD) {
        //发给下一个处理者
        forward(q);
    } else if (result == FINISH_HANDLED) {
        finish(q, true);
    } else if (result == FINISH_NOT_HANDLED) {
        finish(q, false);
    } else {
        throw new IllegalArgumentException("Invalid result: " + result);
    }
}

//交给下一个处理者
protected void forward(QueuedInputEvent q) {
    onDeliverToNext(q);
}
//3. 最后通过mNext.deliveer()交给责任链下一个InputState处理事件
protected void onDeliverToNext(QueuedInputEvent q) {
    if (mNext != null) {
        //调用下一个InputState的deliver()方法
        mNext.deliver(q);
    } else {
        finishInputEvent(q);
    }
}

看完责任链,我们直接看到ViewPostImeInputState,它的onProcess()真正将事件传递下来:

//ViewRootImpl.ViewPostImeInputState.java
final class ViewPostImeInputStage extends InputStage {
    public ViewPostImeInputStage(InputStage next) {
        super(next);
    }
    @Override
    protected int onProcess(QueuedInputEvent q) {
        //如果是按键事件
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                //如果是触摸事件
                return processPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }
}

我们来看看分发PointerEvent也就是触摸事件,**在这里,我们看到了熟悉的mView.dispatchPointerEvent()**事件分发:

//ViewRootImpl.ViewPostImeInputStage.java
private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;
    mHandwritingInitiator.onTouchEvent(event);

    mAttachInfo.mUnbufferedDispatchRequested = false;
    mAttachInfo.mHandlingPointerEvent = true;
    //这个mView就是DecorView,在setView的时候传入的。
    boolean handled = mView.dispatchPointerEvent(event);
    maybeUpdatePointerIcon(event);
    maybeUpdateTooltip(event);
    mAttachInfo.mHandlingPointerEvent = false;
    if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
        mUnbufferedInputDispatch = true;
        if (mConsumeBatchedInputScheduled) {
            scheduleConsumeBatchedInputImmediately();
        }
    }
    return handled ? FINISH_HANDLED : FORWARD;
}

DecorView中途继承自View,看到 dispatchPointerEvent() 方法:

//View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        //如果是触摸事件,分发触摸事件
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

再往后,就进入了我们熟知的View树的触摸事件传递了。

2. View树的触摸事件传递

2.1 DecorView

如果PhoneWindow设置了Callback的话,优先通过Callback进行dispatchTouchEvent()否则使用默认的dispatchTouchEvent()

//DecorView.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
        ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

如果是Activity,在attach()方法中,注册了Callback:

//Activity.java
final void attach(){
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setCallback(this);//Activity实现了Window.Callback接口
}

所以 DecorView.dispatchTouchEvent() 应当来到Activity的dispatchTouchEvent():

//Activity.java
//实现了Window.Callback接口
@Override
public boolean dispatchTouchEvent(MotionEvent ev){
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();//空实现,可重写
    }
    //getWindow()来到PhoneWindow
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //如果孩子没有消费这个事件,就会回到Activity的onTouchEvent中
    return onTouchEvent(ev);
}

Activity实现的Window.Callback接口中,先有一个钩子函数捕获到down事件,接下来来到PhoneWindow.superDispatchTouchEvent()这个函数直接调用了 DecorView.superDispatchTouchEvent() 也就是以ViewGroup的身份,向下传递事件:

//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

接下来,就来到了ViewGroup的dispatchTouchEvent()

2.2 ViewGroup的触摸事件传递

在ViewGroup的dispatchTouchEvent()中,主要做了几件事:

  1. 检查事件的安全性
  2. onInterceptTouchEvent()拦截事件
  3. 如果触摸事件没有取消,且 2 没被拦截,将会遍历所有子View进行分发
  4. down事件只分发给触摸点在子View范围之内的子View,并记录是哪个View消费了down事件
  5. 后续通过TouchTarget来分发move、up等事件
//ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    //------onInterceptTouchEvent()拦截事件---------
    final boolean intercepted = false;
    if (actionMasked == MotionEvent.ACTION_DOWN
        //如果不是Down,但是mFirstTouchTarget不为空,说明有targetView消费了down,仍然需要尝试拦截
        || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        //如果孩子请求了 disallowParentIntercept,这里可能会进不去
        if (!disallowIntercept) {
            //先过一层onInterceptTouchEvent
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
    } else {
        intercepted = true;
    }
    //------分发Down事件,并记录是哪个子View消费的---------
    //如果onInterceptTouchEvent()不拦截,而且event没有cancel取消
    if(!canceled && ! intercepted){
        if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            //如果是down事件
            //遍历所有子View
            final View[] children = mChildren;
            //统计所有触摸到的view
            for (int i = childrenCount - 1; i >= 0; i--) {
                //遍历所有子View
                
                //判断这个触摸点是否在Child中,且这个Child可以接受触摸事件
                if (!child.canReceivePointerEvents()
                    || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }
				//拿到这个子View的TouchTarget(同时也拿到了这个孩子的孩子的TouchTarget,这是一棵树的某条路径)
                newTouchTarget = getTouchTarget(child);
                if (newTouchTarget != null) {
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                    break;
                }
				
                //触摸事件(down事件)分发给这个孩子的dispatchTouchEvent()
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    //如果这个down事件被这个子View消费了,进来到这里(此时它的TouchTarget也被递归更新了)
                    //由于这个孩子的mTouchTarget在孩子自己的分发过程中递归更新了
                    //孩子的mTouchTarget.next = 父亲的mFirstTouchTarget
                    //父亲的mFirstTouchTarget = 孩子的mTouchTarget
                    //最后这个newTouchTarget的形式就是,头为最后消费者,后缀为消费者View的父亲View
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                ev.setTargetAccessibilityFocus(false);
            }
            //如果没有孩子处理事件
            if (newTouchTarget == null && mFirstTouchTarget != null) {
                //遍历找到之前的根View的TouchTarget作为newTouchTarget
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
    }
    
    //除了down的其他事件,后续都通过TouchTarget来分发:
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                                TouchTarget.ALL_POINTER_IDS);
    }else{
        //如果之前有TouchTarget记录,则往其上分发事件
        TouchTarget predesessor = null;//前一个TouchTarget
        TouchTarget target = mFirstTouchTarget;//这个mFirstTouchTarget是最终处理了Down事件的View,它的next是其父亲View的TouchTarget
        while(target != null){
            //子View的TouchTarget的next是其父亲的TouchTarget
            final TouchTarget next = target.next;
            //给孩子分发触摸事件
            //后续的例如move事件都在这里分发下去,分发给target
            if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
                handled = true;
            }
            //同时也把这个事件分发给其父亲的TouchTarget,进入dispatchTouchEvent()
            predecessor = target;
            target = next;
        }
    }
    //返回是否被消费
    return handled;
}

这里出现了TouchTarget,这是ViewGroup的内部类,这是一个链表数据结构,用于记录在View树上,触摸事件传递的路径(传递经过了哪些View):

//ViewGroup.java
private static final class TouchTarget {
    private static TouchTarget sRecycleBin;//复用池
    public View child;//聚合到的View
    public TouchTarget next;//链表
}

此外,分发事件通过的是 dispatchTransformedTouchEvent() 方法:

//ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    //如果是cancel事件
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            //如果没有View进行分发,就使用默认
            handled = super.dispatchTouchEvent(event);
        } else {
            //分发到子View
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    
    //...
    
    // 分发事件,如move、up等
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
		//发给子View
        handled = child.dispatchTouchEvent(transformedEvent);
    }
}

回到ViewGroup的dispatchTouchEvent的逻辑,再次梳理一下:

  1. 检查事件的安全性
  2. onInterceptTouchEvent()拦截事件
  3. 如果触摸事件没有取消,且 2 没被拦截,将会遍历所有子View进行分发
  4. down事件只分发给触摸点在子View范围之内的子View,并记录是哪个View消费了down事件
  5. 后续通过TouchTarget来分发move、up等事件
  6. 如果2拦截了,那么 intercepted 变量为 true,就不会将 down 事件分发给子View,那么 TouchTarget 这样一个树上传递路径的记录就不会记录到子View,后续非 down 事件的分发就不会传递给子View,实现了拦截
  7. 如果没有子View进行分发,最后会进入 super.dispatchTouchEvent() 也就来到了父类 View .java的dispatchTouchEvent() 默认实现

2.3 View的触摸事件传递

如果某个ViewGroup拦截了触摸事件,会来到父类View.java实现的dispatchTouchEvent()中。也有可能事件传递给了子View,本就是View类型,直接来到dispatchTouchEvent()中。

View.java的dispatchTouchEvent()主要做了几件事:

  1. 触摸事件的安全检查
  2. mOnTouchListener.onTouch(),如果有监听器,优先回调监听器的onTouch()方法
  3. 如果 2 消费了事件,就不会再进入 onTouchEvent()
//View.java
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        //先调用TouchListener的onTouch
        //如果消费了,return true,那么 result= true
        && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    //如果OnTouchListener.onTouch消费了事件,result=true就不会进入到onTouchEvent()中
    //onTouchEvent()是默认实现,用户可以重写onTouchEvent,或者添加OnTouchListener来处理自己的逻辑。OnTouchLIstener优先级更高
    if (!result && onTouchEvent(event)) {
        result = true;
    }
    //...
    return result;
}

其中,onTouchEvent()的默认实现中,有click事件与长按事件:

//View.java
public boolean onTouchEvent(MotionEvent event) {
        //默认处理逻辑
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        //当前控件是否可点击
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            //如果当前来到的手势是 ACTION_UP
            case MotionEvent.ACTION_UP:
                //一系列判断
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }
                //点击事件,使用post,而不是直接回调
                if (!post(mPerformClick)) {
                    performClickInternal();
                }
		//...
        return true;
    }
    return false;
}   

也就是说,如果某个ViewGroup拦截了事件,或者它的onTouchEvent消费了事件,后续都将通过其super.dispatchTouchEvent()也就是View.java的dispatchTouchEvent()来到listener.onTouch()或者onTouchEvent()处理事件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/403964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nuxt3使用总结

目录 背景 安装 项目配置 路由 Tailwindcss引入 全局样式配置 css预处理器 安装 Tailwindcss 项目的配置 部署上线 seo优化 背景 新入职了一家公司&#xff0c;刚进入公司第一个需求就是先做一个公司的官网&#xff0c;需要使用vue写&#xff0c;作为祖师爷的粉丝…

Java 电话号码的组合

电话号码的字母组合中等给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。示例 1&#xff1a;输入&#xff1a;digits "23…

案例学习--016 消息队列作用和意义

简介MQ全称为Message Queue, 是一种分布式应用程序的的通信方法&#xff0c;它是消费-生产者模型的一个典型的代表&#xff0c;producer往消息队列中不断写入消息&#xff0c;而另一端consumer则可以读取或者订阅队列中的消息。主要产品有&#xff1a;ActiveMQ、RocketMQ、Rabb…

【RV1126】RKMedia模块简介

文章目录简介源码与编译rkmedia log等级配置目录参考文档&#xff1a;【Rockchip RKMedia Development Guide】rkmedia的手册在sdk目录下/docs/RV1126_RV1109/Multimedia rkmedia的代码在sdk目录下/external/rkmedia rkmedia的demo在sdk目录下/external/rkmedia/examples&…

antlr4-maven-plugin简单学习

1. 序言 antlr4-maven-plugin的官方介绍为&#xff1a; The ANTLR 4 plugin for Maven can generate parsers for any number of grammars in your project.博客《 mac上的Antlr4环境搭建》&#xff0c;有介绍如何通过antlr4-maven-plugin实现.g4文件的编译 这里将介绍antlr4-…

弹性存储-对象存储OSS部分

对象存储介绍 对象存储&#xff08;object storage service&#xff0c;简称oss&#xff09;&#xff0c;具备与平台无关的rest api接口&#xff0c;可提供99.9999999999%&#xff08;12个9&#xff09;的数据持久性和99.995%的数据可用性。 OSS优势 功能介绍 存储空间bucke…

秒杀高并发解决方案

秒杀高并发解决方案 1.秒杀/高并发方案-介绍 秒杀/高并发 其实主要解决两个问题&#xff0c;一个是并发读&#xff0c;一个是并发写并发读的核心优化理念是尽量减少用户到 DB 来"读"数据&#xff0c;或者让他们读更少的数据, 并 发写的处理原则也一样针对秒杀系统需…

麒麟服务器V10 版本 安装 Anaconda教程,也就是安装Python环境的教程(亲测有效)

目录1 Anaconda 是什么2 安装1 Anaconda 是什么 你可以理解为一个软件&#xff0c;和QQ一样的软件&#xff0c;你安装之后&#xff0c;里面就有naconda包括Conda、Python以及一大堆安装好的工具包&#xff0c;比如&#xff1a;numpy、pandas等 1&#xff09;包含conda&#x…

【C++学习】类和对象(上)

前言&#xff1a; 由于之前电脑“嗝屁”了&#xff0c;导致这之前一直没有更新博客&#xff0c;今天才拿到电脑&#xff0c;在这里说声抱歉。接下来就进入今天的学习&#xff0c;在之前我们已经对【C】进行了初步的认识&#xff0c;有了之前的知识铺垫&#xff0c;今天我们将来…

初识BFC

初识BFC 先说如何开启BFC&#xff1a; 1.设置display属性&#xff1a;inline-block&#xff0c;flex&#xff0c;grid 2.设置定位属性&#xff1a;absolute&#xff0c;fixed 3.设置overflow属性&#xff1a;hidden&#xff0c;auto&#xff0c;scroll 4.设置浮动&#xf…

英雄算法学习路线

文章目录零、自我介绍一、关于拜师二、关于编程语言三、算法学习路线1、算法集训1&#xff09;九日集训2&#xff09;每月算法集训2、算法专栏3、算法总包四、英雄算法联盟1、英雄算法联盟是什么&#xff1f;2、如何加入英雄算法联盟&#xff1f;3、为何会有英雄算法联盟&#…

Linux系统安装mysql(rpm版)

目录 Linux系统安装mysql&#xff08;rpm版&#xff09; 1、检测当前系统中是否安装MySQL数据库 2、将mysql安装包上传到Linux并解压 3、按照顺序安装rpm软件包 4、启动mysql 5、设置开机自启 6、查看已启动的服务 7、查看临时密码 8、登录mysql&#xff0c;输入临时密…

C++ STL学习之【vector的使用】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 The power of imagination makes us infinite. 想象力的力量使我们无限。 文章目录&#x1f4d8;前言&#x1f4d8;正文1、默认成员函数1.1、默认构造…

STM32之SPI

SPISPI介绍SPI是串行外设接口(Serial Peripherallnterface)的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚&#xff0c;同时为PCB的布局上节省空间&#xff0c;提供方便…

蓝桥杯嵌入式(G4系列):定时器捕获

前言&#xff1a; 定时器的三大功能还剩下最后一个捕获&#xff0c;而这在蓝桥杯嵌入式开发板上也有555定时器可以作为信号发生器供定时器来测量。 原理图部分&#xff1a; 开发板上集成了两个555定时器&#xff0c;一个通过跳线帽跟PA15相连&#xff0c;最终接到了旋钮R40上&…

STM32F103CubeMX定时器

前言定时器作为最重要的内容之一&#xff0c;是每一位嵌入式软件工程师必备的能力。STM32F103的定时器是非常强大的。1&#xff0c;他可以用于精准定时&#xff0c;当成延时函数来使用。不过个人不建议这么使用&#xff0c;因为定时器很强大&#xff0c;这么搞太浪费了。如果想…

Zookeeper的Java API操作

Zookeeper的Java API操作一、先启动Zookeeper集群二、IDEA 环境搭建三、创建子节点四、获取子节点并监听节点变化五、判断 Znode 是否存在六、Watcher工作流程一、先启动Zookeeper集群 二、IDEA 环境搭建 1.创建一个Maven工程&#xff1a;ZookeeperProject 2.在pom.xml文件添…

ARM uboot 的移植4 -从 uboot 官方标准uboot开始移植

一、添加DDR初始化1 1、分析下一步的移植路线 (1) cpu_init_crit 函数成功初始化串口、时钟后&#xff0c;转入 _main 函数&#xff0c;函数在 arch/arm/lib/crt0.S 文件中。 (2) 在 crt0.S 中首先设置栈&#xff0c;将 sp 指向 DDR 中的栈地址&#xff1b; #if defined(CONF…

CNCF x Alibaba云原生技术公开课 【重要】第九章 应用存储和持久化数据卷:核心知识

1、Pod Volumes 场景 同一个pod中的某个容器异常退出&#xff0c;kubelet重新拉起来&#xff0c;保证容器之前产生数据没丢同一个pod的多个容器共享数据 常见类型 本地存储&#xff0c;常用的有 emptydir/hostpath&#xff1b;网络存储&#xff1a;网络存储当前的实现方式有两…

2021年我国半导体分立器件市场规模已达3037亿元,国内功率半导体需求持续快速增长

半导体分立器件是由单个半导体晶体管构成的具有独立、完整功能的器件。例如&#xff1a;二极管、三极管、双极型功率晶体管(GTR)、晶闸管(可控硅)、场效应晶体管(结型场效应晶体管、MOSFET)、IGBT、IGCT、发光二极管、敏感器件等。半导体分立器件制造&#xff0c;指单个的半导体…