Android input 事件分发 -- inputReader

news2024/11/28 8:47:53

inputReader

  • inputReader

inputReader

	这个章节主要是围绕inputReader 、inputReaderThread进行的,老规矩先上时序图

在这里插入图片描述
通过前面我们已经知道了InputReader和InputReaderThread都是在InputManager的构造函数里面new出来的,然后InputReaderThread的启动是在SystemServer里面InputManagerService 调用start间接调到了InputManager的start,然后调到InputReaderThread.run()

frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    // 这里new 出来 InputReader
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

InputManager::InputManager(
        const sp<InputReaderInterface>& reader,
        const sp<InputDispatcherInterface>& dispatcher) :
        mReader(reader),
        mDispatcher(dispatcher) {
    initialize();
}

InputManager::~InputManager() {
    stop();
}

// 这里new出来InputReaderThread,并且持有ReaderThread对象
void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

// 这里的start会在inputManagerServer里面间接调用
status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }

	// 启动ReaderThread线程
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        ALOGE("Could not start InputReader thread due to error %d.", result);

        mDispatcherThread->requestExit();
        return result;
    }

    return OK;
}

后面跟着时序图走,前面已经启动了inputReaderThread后面线程启动就会走到InputReaderThread::threadLoop

bool InputReaderThread::threadLoop() {
	// 调到了InputReader里面的loopOnce方法
    mReader->loopOnce();
    return true;
}

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        AutoMutex _l(mLock);

        oldGeneration = mGeneration;
        timeoutMillis = -1;

        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock

    // 获得事件, 没有事件就block在EventHub中的epoll处
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();

        if (count) {
           // 上面从EventHub得到EventBuffer后就会想着把事件处理后给到InputDispatcher,继续看这个方法
            processEventsLocked(mEventBuffer, count);
        }

        if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
                ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
#endif
                mNextTimeout = LLONG_MAX;
                timeoutExpiredLocked(now);
            }
        }

        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }

    // Flush queued events out to the listener.
    // This must happen outside of the lock because the listener could potentially call
    // back into the InputReader's methods, such as getScanCodeState, or become blocked
    // on another thread similarly waiting to acquire the InputReader lock thereby
    // resulting in a deadlock.  This situation is actually quite plausible because the
    // listener is actually the input dispatcher, which calls into the window manager,
    // which occasionally calls into the input reader.
    // 这里会接收到不同的事件类型,然后调不同的notify(keyEvent、MotionEvent)去处理,在后面讲解
    mQueuedListener->flush();
}

mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE) 看看这个方法


size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);
 
    AutoMutex _l(mLock);
    // 创建读取buffer
    struct input_event readBuffer[bufferSize];
    RawEvent* event = buffer;
    size_t capacity = bufferSize;
    // 在这个for循环里面读取设备节点里面的内容读到上面申请的buffer里面
    for (;;) {
	
    // 处理代码
             if (eventItem.data.u32 == EPOLL_ID_WAKE) {
                if (eventItem.events & EPOLLIN) {
                    ALOGV("awoken after wake()");
                    awoken = true;
                    char buffer[16];
                    ssize_t nRead;
                    do {
                    // 读取管道中的数据
                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
                            eventItem.events);
                }
                continue;
            }
    }
 
    // All done, return the number of events we read.
    // 返回已经被读取的buffer
    return event - buffer;
}

EventHub算是一个比较大的内容了,大家可以参考一下这个文章,不过我觉得你看完整个文章后再去细追这个。

processEventsLocked(mEventBuffer, count);


void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
#if DEBUG_RAW_EVENTS
            ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
#endif
           // 这里会根据不同的设备,发送不同的input事件
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}


void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    if (deviceIndex < 0) {
        ALOGW("Discarding event for unknown deviceId %d.", deviceId);
        return;
    }

    // KeyedVector<int32_t, InputDevice*> mDevices 这mDevices 里面add了不同的设备节点
    InputDevice* device = mDevices.valueAt(deviceIndex);
    if (device->isIgnored()) {
        //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return;
    }

    device->process(rawEvents, count);
}

// 这里是往mDevices里面add的内容
InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);

    // External devices.
    if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
        device->setExternal(true);
    }

    // Devices with mics.
    // 麦克风设备
    if (classes & INPUT_DEVICE_CLASS_MIC) {
        device->setMic(true);
    }

    // Switch-like devices.
    // switch 类似的设备
    if (classes & INPUT_DEVICE_CLASS_SWITCH) {
        device->addMapper(new SwitchInputMapper(device));
    }

    // Scroll wheel-like devices.
    // 鼠标设备
    if (classes & INPUT_DEVICE_CLASS_ROTARY_ENCODER) {
        device->addMapper(new RotaryEncoderInputMapper(device));
    }

    // Vibrator-like devices.
    // 振动器设备
    if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
        device->addMapper(new VibratorInputMapper(device));
    }

    // Keyboard-like devices.
    // 键盘
    uint32_t keyboardSource = 0;
    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
    }
    if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
    }
    if (classes & INPUT_DEVICE_CLASS_DPAD) {
        keyboardSource |= AINPUT_SOURCE_DPAD;
    }
    if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
        keyboardSource |= AINPUT_SOURCE_GAMEPAD;
    }

    if (keyboardSource != 0) {
        device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
    }

    // Cursor-like devices.
    if (classes & INPUT_DEVICE_CLASS_CURSOR) {
        device->addMapper(new CursorInputMapper(device));
    }

    // Touchscreens and touchpad devices.
    // 屏幕touch
    if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
        device->addMapper(new MultiTouchInputMapper(device));
    } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
        device->addMapper(new SingleTouchInputMapper(device));
    }

    // Joystick-like devices.
    if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
        device->addMapper(new JoystickInputMapper(device));
    }

    // External stylus-like devices.
    if (classes & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS) {
        device->addMapper(new ExternalStylusInputMapper(device));
    }

    return device;
}

接着上面device->process(rawEvents, count);我们会看到在InputReader.cpp里面有多个process方法

void TouchInputMapper::process(const RawEvent* rawEvent) {
    mCursorButtonAccumulator.process(rawEvent);
    mCursorScrollAccumulator.process(rawEvent);
    mTouchButtonAccumulator.process(rawEvent);

    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        sync(rawEvent->when);
    }
}

void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
    if (rawEvent->type == EV_REL) {
        switch (rawEvent->code) {
        case REL_WHEEL:
            mRelWheel = rawEvent->value;
            break;
        case REL_HWHEEL:
            mRelHWheel = rawEvent->value;
            break;
        }
    }
}

void SwitchInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_SW:
        processSwitch(rawEvent->code, rawEvent->value);
        break;

    case EV_SYN:
        if (rawEvent->code == SYN_REPORT) {
            sync(rawEvent->when);
        }
    }
}
.................
这就是我们前面吧这些InputMapper的子类都set到了mDevices里面了,我们这里假设调用的是
TouchInputMapper::process

frameworks/native/services/inputflinger/InputReader.cpp

void TouchInputMapper::process(const RawEvent* rawEvent) {
	// 这几个都属于touch事件里面的
    mCursorButtonAccumulator.process(rawEvent);
    mCursorScrollAccumulator.process(rawEvent);
    mTouchButtonAccumulator.process(rawEvent);

    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        // 继续跟这里
        sync(rawEvent->when);
    }
}

void TouchInputMapper::sync(nsecs_t when) {
	..........
    processRawTouches(false /*timeout*/);
}

void TouchInputMapper::processRawTouches(bool timeout) {
    const size_t N = mRawStatesPending.size();
    size_t count;
    for(count = 0; count < N; count++) {
        const RawState& next = mRawStatesPending[count];

        // A failure to assign the stylus id means that we're waiting on stylus data
        // and so should defer the rest of the pipeline.
        if (assignExternalStylusId(next, timeout)) {
            break;
        }

        // All ready to go.
        clearStylusDataPendingFlags();
        mCurrentRawState.copyFrom(next);
        if (mCurrentRawState.when < mLastRawState.when) {
            mCurrentRawState.when = mLastRawState.when;
        }
        // 这里
        cookAndDispatch(mCurrentRawState.when);
    }

}

void TouchInputMapper::cookAndDispatch(nsecs_t when) {

        if (!mCurrentMotionAborted) {
            dispatchButtonRelease(when, policyFlags);
            dispatchHoverExit(when, policyFlags);
            // 这里
            dispatchTouches(when, policyFlags);
            dispatchHoverEnterAndMove(when, policyFlags);
            dispatchButtonPress(when, policyFlags);
        }
}

void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
    BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
    BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
    int32_t metaState = getContext()->getGlobalMetaState();
    int32_t buttonState = mCurrentCookedState.buttonState;

    if (currentIdBits == lastIdBits) {
        if (!currentIdBits.isEmpty()) {
            // No pointer id changes so this is a move event.
            // The listener takes care of batching moves so we don't have to deal with that here.
            dispatchMotion(when, policyFlags, mSource,
                    AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState,
                    AMOTION_EVENT_EDGE_FLAG_NONE,
                    mCurrentCookedState.deviceTimestamp,
                    mCurrentCookedState.cookedPointerData.pointerProperties,
                    mCurrentCookedState.cookedPointerData.pointerCoords,
                    mCurrentCookedState.cookedPointerData.idToIndex,
                    currentIdBits, -1,
                    mOrientedXPrecision, mOrientedYPrecision, mDownTime);
        }
    } else {

        // Dispatch pointer up events.
        while (!upIdBits.isEmpty()) {
            uint32_t upId = upIdBits.clearFirstMarkedBit();

            dispatchMotion(when, policyFlags, mSource,
                    AMOTION_EVENT_ACTION_POINTER_UP, 0, 0, metaState, buttonState, 0,
                    mCurrentCookedState.deviceTimestamp,
                    mLastCookedState.cookedPointerData.pointerProperties,
                    mLastCookedState.cookedPointerData.pointerCoords,
                    mLastCookedState.cookedPointerData.idToIndex,
                    dispatchedIdBits, upId, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
            dispatchedIdBits.clearBit(upId);
        }

        // Dispatch move events if any of the remaining pointers moved from their old locations.
        // Although applications receive new locations as part of individual pointer up
        // events, they do not generally handle them except when presented in a move event.
        if (moveNeeded && !moveIdBits.isEmpty()) {
            ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
            // 这里
            dispatchMotion(when, policyFlags, mSource,
                    AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState, 0,
                    mCurrentCookedState.deviceTimestamp,
                    mCurrentCookedState.cookedPointerData.pointerProperties,
                    mCurrentCookedState.cookedPointerData.pointerCoords,
                    mCurrentCookedState.cookedPointerData.idToIndex,
                    dispatchedIdBits, -1, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
        }

        // Dispatch pointer down events using the new pointer locations.
        while (!downIdBits.isEmpty()) {
            uint32_t downId = downIdBits.clearFirstMarkedBit();
            dispatchedIdBits.markBit(downId);

            if (dispatchedIdBits.count() == 1) {
                // First pointer is going down.  Set down time.
                mDownTime = when;
            }
			
            dispatchMotion(when, policyFlags, mSource,
                    AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState, 0,
                    mCurrentCookedState.deviceTimestamp,
                    mCurrentCookedState.cookedPointerData.pointerProperties,
                    mCurrentCookedState.cookedPointerData.pointerCoords,
                    mCurrentCookedState.cookedPointerData.idToIndex,
                    dispatchedIdBits, downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
        }
    }
}

void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
        int32_t action, int32_t actionButton, int32_t flags,
        int32_t metaState, int32_t buttonState, int32_t edgeFlags, uint32_t deviceTimestamp,
        const PointerProperties* properties, const PointerCoords* coords,
        const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId,
        float xPrecision, float yPrecision, nsecs_t downTime) {
    PointerCoords pointerCoords[MAX_POINTERS];
    PointerProperties pointerProperties[MAX_POINTERS];
    uint32_t pointerCount = 0;
    while (!idBits.isEmpty()) {
        uint32_t id = idBits.clearFirstMarkedBit();
        uint32_t index = idToIndex[id];
        pointerProperties[pointerCount].copyFrom(properties[index]);
        pointerCoords[pointerCount].copyFrom(coords[index]);

        if (changedId >= 0 && id == uint32_t(changedId)) {
            action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
        }

        pointerCount += 1;
    }

    ALOG_ASSERT(pointerCount != 0);

    if (changedId >= 0 && pointerCount == 1) {
        // Replace initial down and final up action.
        // We can compare the action without masking off the changed pointer index
        // because we know the index is 0.
        if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) {
            action = AMOTION_EVENT_ACTION_DOWN;
        } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
            action = AMOTION_EVENT_ACTION_UP;
        } else {
            // Can't happen.
            ALOG_ASSERT(false);
        }
    }

	// 这里构造出NotifyMotionArgs 对象
    NotifyMotionArgs args(when, getDeviceId(), source, policyFlags,
            action, actionButton, flags, metaState, buttonState, edgeFlags,
            mViewport.displayId, deviceTimestamp, pointerCount, pointerProperties, pointerCoords,
            xPrecision, yPrecision, downTime);
    // 然后这里又调回到QueuedInputListener中,将这个对象set到mArgsQueue队列里面
    getListener()->notifyMotion(&args);
}

frameworks/native/services/inputflinger/InputListener.cpp

void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
    mArgsQueue.push(new NotifyMotionArgs(*args));
}

接着看
mQueuedListener->flush();

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        这个mInnerListener就是我们的InputDispatcher对象
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
	// 调用到InputDispatcher的notifyMotion
    listener->notifyMotion(this);
}

从上面的文章我们就看到了整个从InputReaderThread --> EventHub读取设备节点 --> InputReader --> inputDispatcher

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

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

相关文章

图文详解:内存总是不够,我靠HBase说服了Leader为新项目保驾护航

最近在工作中用到了 Hbase 这个数据库&#xff0c;也顺便做了关于 Hbase 的知识记录来分享给大家。其实 Hbase的内容体系真的很多很多&#xff0c;这里介绍的是小羽认为在工作中会用到的一些技术点&#xff0c;希望可以帮助到大家。 可以这么说互联网都是建立在形形色色的数据…

剑指offer----C语言版----第十天

目录 1. 二进制中 1 的个数 1.1 题目描述 1.2 可能引起错误的解法 1.3 常规解法 1.4 思路优化 1. 二进制中 1 的个数 原题链接: 剑指 Offer 15. 二进制中1的个数 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof…

电路方案分析(十五)符合 EMC 标准的汽车制动灯和尾灯设计方案

符合 EMC 标准的汽车制动灯和尾灯设计方案 tips&#xff1a;TI设计方案参考分析&#xff1a;TI Designs&#xff1a;TIDA-01374 1.系统描述 1.1关键参数 2.系统概述 2.1系统框图 2.2关键元器件 3.设计原理 3.1双重亮度设计 3.2 电荷泵设计 3.4 LED故障设计 3.3 MOSFET驱动电…

LeetCode Hot 100 笔记

文章目录链表21. 合并两个有序链表栈20. 有效的括号Java栈链表 链表的题目一般都不太难&#xff0c;画图&#xff0c;别怕麻烦 21. 合并两个有序链表 解法一&#xff1a;迭代 用一个指针cur跟踪当前节点&#xff0c;每次从list1和list2中选取小的节点&#xff0c;链接起来建…

什么是轻量化,轻量化模型is all your need hhh

其实学了几个小月&#xff0c;我们肯定知道&#xff0c;MLP有多deeper &#xff0c;卷积层有多少层呀 抑或是Transformer架构&#xff0c;大量的参数&#xff0c;只能用huge 来描述&#xff0c; 可实际上我们的设备&#xff0c;有时候并没有服务器那么厉害&#xff0c;所以人…

阿里云认证为什么那么多人考?考试内容难不难?

我国人口密集&#xff0c;每年有大量的毕业生涌进社会&#xff0c;除此之外还有很多进入社会很久的打工人&#xff0c;想要跳槽&#xff0c;到更加挣钱的岗位&#xff0c;待遇更好的公司去。为了能够早日买房、买车&#xff0c;很多人会选择社会热门行业去学习&#xff0c;甚至…

sqli-labs 第七关 多命通关攻略

sqli-labs 第七关 多命通关攻略描述字符串与数值之间的转换判断注入类型返回结果正常输入不正常输入错误输入总结判断注入类型判断是否为字符型注入判断是否为单引号字符型注入判断是否为双引号字符型注入判断是否为数值型注入总结判断注入类型&#xff08;修正版&#xff09;字…

二十六、Docker (2)

&#x1f33b;&#x1f33b; 目录一、Docker的常用命令 (阶段A)1.1 帮助命令1.2 镜像命令1.3 容器命令1.3.1 新建容器并启动1.3.2 列出所有运行的容器1.3.3 退出容器1.3.4 删除容器1.3.5 启动和停止容器的操作1.4 常用的其它命令1.4.1 后台启动容器1.4.2 查看日志1.4.3 查看容器…

如何搭建私域流量?

如今已经进入存量用户时代&#xff0c;越来越多的企业也明白了存量用户的重要性&#xff0c;因此企业都非常重视私域流量的搭建&#xff0c;以挖掘客户的价值。 前言 如今已经进入存量用户时代&#xff0c;越来越多的企业也明白了存量用户的重要性&#xff0c;因此企业都非常重…

安卓搭建好的模拟机,为调试准备

​ 这一节直接分享制作好的虚拟机&#xff0c;镜像系统&#xff0c;以及安卓源码&#xff0c;直接节省你的时间去配置&#xff0c;编译。 下来我来分享下搭建步骤&#xff1a; 1 虚拟机下载vm 12 pro &#xff0c;这个网上百度就可以&#xff0c;原则13,14也都是可以的。 2 下…

移动魔百盒CM311-3-YST-晨星MSO9385-语音首页正常-TTL刷机包

移动魔百盒CM311-3-YST-晨星MSO9385-语音首页正常-TTL刷机包 固件特点&#xff1a; 1、三网通用&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、无开机广告&#xff0c;无系统更新&#xff0c;不在被强制升级&#xff1b; 4、大量精简内置的没用…

熬夜搞了 17000 字,终于把你这个 ES 玩明白了

平常经常用ES(ElasticSearch), 觉得这真是个好玩意儿&#xff0c;所以来分享一篇文章&#xff0c;希望通过这篇文章能让读者大致了解ES是做什么的以及它的使用和基本原理。 可能有的读者航海不知道ES是个啥玩儿&#xff0c;别着急&#xff0c;看完本文后&#xff0c;相信你会了…

import...from... 和 require 如何找到模块位置?

import Vue from "vue"; 为什么不用写相对地址和绝对地址就能够导出 Vue 呢&#xff1f;似乎也没有配置路径&#xff1f;也没有配置映射&#xff0c;那么究竟 from "vue"; 对应的究竟是那个路径呢&#xff1f; 先提出两个可能的方案 1.VS Code/WebStorm …

【每天学习一点新知识】nmap端口扫描

nmap所识别的6个端口状态open(开放的)应用程序正在该端口接收TCP 连接或者UDP报文。发现这一点常常是端口扫描 的主要目标。安全意识强的人们知道每个开放的端口 都是攻击的入口。攻击者或者入侵测试者想要发现开放的端口。 而管理员则试图关闭它们或者用防火墙保护它们以免妨碍…

Java封装公共Result结果返回类

前言 在使用Java开发接口请求中&#xff0c;我们需要对请求进行进行统一返回值&#xff0c;这时候我们自己封装一个统一的Result返回类&#xff0c;下面就介绍下我用的这种的这个类 当然&#xff0c;也可以使用第三方库封装的Result结果返回类&#xff0c;根据个人喜好选择即可…

学习笔记之Vue脚手架(三)

&#xff08;三&#xff09;使用Vue脚手架 使用Vue脚手架&#xff08;三&#xff09;使用Vue脚手架一、创建Vue脚手架1.1 说明1.2 具体步骤二、分析脚手架结构2.1 配置文件2.2 src文件夹2.3 public文件夹一、创建Vue脚手架 1.1 说明 1.Vue脚手架是Vue官方提供的标准开发工具&…

电脑技巧:分享常用的电脑快捷键

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

Nutanix 替代专题 | SmartX 与 Nutanix 超融合市场、技术与性能对比

2022 年 8 月 19 日&#xff0c;Nutanix&#xff08;路坦力&#xff09;宣布中国市场自 2023 财年起将转型为合作伙伴销售主导模式&#xff0c;引起了广泛关注&#xff1b;同时结合当前 IT 基础架构的国产化趋势背景&#xff0c;不少正在使用和考虑使用 Nutanix 产品的企业开始…

js 跨域访问问题解决方法

什么引起了ajax不能跨域请求的问题&#xff1f; ajax本身实际上是通过XMLHttpRequest对象来进行数据的交互&#xff0c;而浏览器出于安全考虑&#xff0c;不允许js代码进行跨域操作&#xff0c;所以会警告。 有什么完美的解决方案么&#xff1f; 解决方案有不少&#xff0c;但…

数字化门店管理|如何让门店数字化管理,更加贴合日常运营细节?

在赋能品牌门店数字化管理的过程中&#xff0c;帷幄既注重前沿 AI 算法带来的技术驱动力&#xff0c;也注重基于门店管理中的真实场景与需求&#xff0c;让算法更贴合业务实际需求&#xff0c;从而带来运营优化与降本增效。 1 月&#xff0c;「帷幄数智空间 Whale SpaceSight」…