首页 > 其他分享 >Input text流程

Input text流程

时间:2023-07-15 18:56:03浏览次数:42  
标签:status return -- text 流程 connection env Input event

input text 流程


sendMessage前

调用命令 input text 'helo world',会进入到函数 nativeInjectInputEvent


static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputEventObj, jint displayId, jint injectorPid, jint injectorUid,
        jint syncMode, jint timeoutMillis, jint policyFlags) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {  //KeyEvent类型
        KeyEvent keyEvent;
        // Java KeyEvent 转换 native层的
        status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
        if (status) {
            jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
            return INPUT_EVENT_INJECTION_FAILED;
        }
        // injectInputEvent
        return (jint) im->getInputManager()->getDispatcher()->injectInputEvent(
                & keyEvent, displayId, injectorPid, injectorUid, syncMode, timeoutMillis,
                uint32_t(policyFlags));
    } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {  //MotionEvent类型
        const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
        if (!motionEvent) {
            jniThrowRuntimeException(env, "Could not read contents of MotionEvent object.");
            return INPUT_EVENT_INJECTION_FAILED;
        }

        return (jint) im->getInputManager()->getDispatcher()->injectInputEvent(
                motionEvent, displayId, injectorPid, injectorUid, syncMode, timeoutMillis,
                uint32_t(policyFlags));
    } else {
        jniThrowRuntimeException(env, "Invalid input event type.");
        return INPUT_EVENT_INJECTION_FAILED;
    }
}

input text是KeyEvent类型,所以我们关注函数android_view_KeyEvent_toNative

// frameworks/base/core/jni/android_view_KeyEvent.cpp

status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj,
        KeyEvent* event) {
    jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId);
    jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource);
    jint metaState = env->GetIntField(eventObj, gKeyEventClassInfo.mMetaState);
    jint action = env->GetIntField(eventObj, gKeyEventClassInfo.mAction);
    jint keyCode = env->GetIntField(eventObj, gKeyEventClassInfo.mKeyCode);
    jint scanCode = env->GetIntField(eventObj, gKeyEventClassInfo.mScanCode);
    jint repeatCount = env->GetIntField(eventObj, gKeyEventClassInfo.mRepeatCount);
    jint flags = env->GetIntField(eventObj, gKeyEventClassInfo.mFlags);
    jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime);
    jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);

    event->initialize(deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
            milliseconds_to_nanoseconds(downTime),
            milliseconds_to_nanoseconds(eventTime));
    return OK;
}

android_view_KeyEvent_toNative函数实际是将Java的KeyEvent转换成Native的KeyEvent类型

// frameworks/native/services/inputflinger/InputDispatcher.cpp

int32_t InputDispatcher::injectInputEvent(const InputEvent* event, int32_t displayId,
        int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
        uint32_t policyFlags) {

    ...

    EventEntry* firstInjectedEntry;
    EventEntry* lastInjectedEntry;
    switch (event->getType()) {
    case AINPUT_EVENT_TYPE_KEY: {
        const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
        int32_t action = keyEvent->getAction();
        if (! validateKeyEvent(action)) {
            return INPUT_EVENT_INJECTION_FAILED;
        }

        int32_t flags = keyEvent->getFlags();
        if (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY) {
            policyFlags |= POLICY_FLAG_VIRTUAL;
        }

        if (!(policyFlags & POLICY_FLAG_FILTERED)) {
            mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
        }

        mLock.lock();
        firstInjectedEntry = new KeyEntry(keyEvent->getEventTime(),
                keyEvent->getDeviceId(), keyEvent->getSource(),
                policyFlags, action, flags,
                keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
                keyEvent->getRepeatCount(), keyEvent->getDownTime());
        lastInjectedEntry = firstInjectedEntry;
        break;
    }
    case AINPUT_EVENT_TYPE_MOTION: {
        ...
    }

    bool needWake = false;
    for (EventEntry* entry = firstInjectedEntry; entry != NULL; ) {
        EventEntry* nextEntry = entry->next;
        needWake |= enqueueInboundEventLocked(entry);
        entry = nextEntry;
    }  

    if (needWake) {
        mLooper->wake();
    }

    ...
}

injectInputEvent 函数中创建 KeyEntry, 调用enqueueInboundEventLocked(entry),将 entry放入队列

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    ...
    case EventEntry::TYPE_KEY: {
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
        if (isAppSwitchDue) {
            if (isAppSwitchKeyEventLocked(typedEntry)) {
                resetPendingAppSwitchLocked(true);
                isAppSwitchDue = false;
            } else if (dropReason == DROP_REASON_NOT_DROPPED) {
                dropReason = DROP_REASON_APP_SWITCH;
            }
        }
        if (dropReason == DROP_REASON_NOT_DROPPED
                && isStaleEventLocked(currentTime, typedEntry)) {
            dropReason = DROP_REASON_STALE;
        }
        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
            dropReason = DROP_REASON_BLOCKED;
        }
        // dispatch事件
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);                                                                                                         
        break;
    }
    ...

    if (done) {
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;

        releasePendingEventLocked();
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    } 
}

前面只是添加到队列,但是处理队列的是 dispatchOnceInnerLocked 函数根据事件类型,再调用 dispatchKeyLocked

bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
        DropReason* dropReason, nsecs_t* nextWakeupTime) {
    
    ...

    // Identify targets.
    Vector<InputTarget> inputTargets;
    int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
            entry, inputTargets, nextWakeupTime); 
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }  

    setInjectionResultLocked(entry, injectionResult);
    if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
        return true;
    }  

    addMonitoringTargetsLocked(inputTargets);

    // Dispatch the key.
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;                                                                                                                                                                                
}

dispatchKeyLocked 中会根据某些条件判断是否丢掉事件, 如果不丢掉事件会继续向下调用 dispatchEventLocked 函数

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    ...
    
    pokeUserActivityLocked(eventEntry);

    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);

        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        } else {
#if DEBUG_FOCUS
            ALOGD("Dropping event delivery to target with channel '%s' because it "
                    "is no longer registered with the input dispatcher.",
                    inputTarget.inputChannel->getName().string());
#endif
        }
    }
}  

从代码来看dispatchEventLocked中有两个函数,分别是pokeUserActivityLocked和prepareDispatchCycleLocked

其中pokeUserActivityLocked(eventEntry)方法最终会调用到Java层的PowerManagerService.java中的userActivityFromNative()方法

根据inputChannel的fd从mConnectionsByFd队列中查询目标connection.然后调用prepareDispatchCycleLocked

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
    bool wasEmpty = connection->outboundQueue.isEmpty();

    // Enqueue dispatch entries for the requested modes.
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_IS);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);

    // If the outbound queue was previously empty, start the dispatch cycle going.
    if (wasEmpty && !connection->outboundQueue.isEmpty()) {
        startDispatchCycleLocked(currentTime, connection);
    }
}

prepareDispatchCycleLocked 最终会调用到 startDispatchCycleLocked

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
    
    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        dispatchEntry->deliveryTime = currentTime;

        // Publish the event.
        status_t status;
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
            KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);

            // Publish the key event.
            status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
                    keyEntry->deviceId, keyEntry->source,
                    dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
                    keyEntry->keyCode, keyEntry->scanCode,
                    keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                    keyEntry->eventTime);
            break;
        }
        ...

        // Check the result.
        if (status) {  //正常情况应该为null
            // 错误检查                                                                                                                                                                                
            return;
        }

        // Re-enqueue the event on the wait queue.
        connection->outboundQueue.dequeue(dispatchEntry);
        traceOutboundQueueLengthLocked(connection);
        connection->waitQueue.enqueueAtTail(dispatchEntry);
        traceWaitQueueLengthLocked(connection);
    }
}

如果publishKeyEvent没有失败,则从outboundQueue中取出事件,放入到waitQueue队列中

// frameworks/native/libs/input/InputTransport.cpp

status_t InputPublisher::publishKeyEvent(
        uint32_t seq,
        int32_t deviceId,
        int32_t source,
        int32_t action,
        int32_t flags,
        int32_t keyCode,
        int32_t scanCode,
        int32_t metaState,
        int32_t repeatCount,
        nsecs_t downTime,
        nsecs_t eventTime) {
#if DEBUG_TRANSPORT_ACTIONS
    ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
            "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
            "downTime=%lld, eventTime=%lld",
            mChannel->getName().string(), seq,
            deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
            downTime, eventTime);
#endif

    if (!seq) {
        ALOGE("Attempted to publish a key event with sequence number 0.");
        return BAD_VALUE;
    }

    InputMessage msg;
    msg.header.type = InputMessage::TYPE_KEY;
    msg.body.key.seq = seq;
    msg.body.key.deviceId = deviceId;
    msg.body.key.source = source;
    msg.body.key.action = action;
    msg.body.key.flags = flags;
    msg.body.key.keyCode = keyCode;
    msg.body.key.scanCode = scanCode;
    msg.body.key.metaState = metaState;
    msg.body.key.repeatCount = repeatCount;
    msg.body.key.downTime = downTime;
    msg.body.key.eventTime = eventTime;
    return mChannel->sendMessage(&msg);
}

sendMessage 向UI主线程发送input事件, 利用安卓消息机制,到nativePollOnce中

sendMessage前调用流程

// 接收event 放入Inbound队列
nativeInjectInputEvent  --> android_view_KeyEvent_toNative --> InputDispatcher::injectInputEvent() 

// 处理key event
dispatchKeyLocked  --> dispatchEventLocked(currentTime, entry, inputTargets);  -->  dispatchEventLocked --> 

        prepareDispatchCycleLocked --> startDispatchCycleLocked  -->  publishKeyEvent  -->   mChannel->sendMessage(&msg)  //到UI线程

sendMessage后

Android 消息机制流程

// system/core/libutils/Looper.cpp
// android_os_MessageQueue.cpp

MessageQueue.next() --> android_os_MessageQueue_nativePollOnce --> NativeMessageQueue::pollOnce() -- > Looper::pollOnce() --> Looper::pollInner() //轮询
int Looper::pollInner(int timeoutMillis) {

    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            // callback 初始化在
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq);
            }

            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;


}

response.request.callback的赋值在Looper::addFd() 函数中,这段在完成InputManagerService.registerInputChannel()中调用,具体流程

WindowManagerService.addWindow() -->   mInputManager.registerInputChannel() -->  nativeRegisterInputChannel() 
    -->  NativeInputManager::registerInputChannel()  --> InputDispatcher::registerInputChannel()  --> mLooper->addFd()

inputChannel 在 nativeRegisterInputChannel中创建的,其实取得是inputChannelObj的一个字段


static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    // inputChannel在这创建的
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);

    sp<InputWindowHandle> inputWindowHandle =
            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
    
    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);
    ...

    if (! monitor) {
        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
                handleInputChannelDisposed, im);
    }
}

// InputDispatcher::registerInputChannel
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
#if DEBUG_REGISTRATION
    ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
            toString(monitor));
#endif

    { // acquire lock
        AutoMutex _l(mLock);

        if (getConnectionIndexLocked(inputChannel) >= 0) {
            ALOGW("Attempted to register already registered input channel '%s'",
                    inputChannel->getName().string());
            return BAD_VALUE;
        }

        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
        // 从inputChannel取出fd
        int fd = inputChannel->getFd();
        mConnectionsByFd.add(fd, connection);

        if (monitor) {
            mMonitoringChannels.push(inputChannel);
        }

        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock
    // Wake the looper because some connections have changed.
    mLooper->wake();
    return OK;
}

handleEvent 实际是NativeInputEventReceiver.handleEvent();

NativeInputEventReceiver的创建流程

ViewRootImpl.setView() --> new WindowInputEventReceiver() -->  new InputEventReceiver() -->  nativeInit() -->  new NativeInputEventReceiver()
// frameworks/base/core/jni/android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    ...
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }

    if (events & ALOOPER_EVENT_OUTPUT) {
        for (size_t i = 0; i < mFinishQueue.size(); i++) {
            const Finish& finish = mFinishQueue.itemAt(i);
            status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
            ...
        }

        mFinishQueue.clear();
        setFdEvents(ALOOPER_EVENT_INPUT);
        return 1;
    }
    ...
}

handleEvent中有2个函数 consumeEventssendFinishedSignal


status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    ...

    ScopedLocalRef<jobject> receiverObj(env, NULL);
    bool skipCallbacks = false;
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        int32_t displayId;
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent, &displayId);
        if (status) {
            if (status == WOULD_BLOCK) {
                if (!skipCallbacks && !mBatchedInputEventPending
                        && mInputConsumer.hasPendingBatch()) {
                    if (!receiverObj.get()) {
                        receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
                        if (!receiverObj.get()) {
                            ALOGW("channel '%s' ~ Receiver object was finalized "
                                    "without being disposed.", getInputChannelName());
                            return DEAD_OBJECT;
                        }
                    }

                    mBatchedInputEventPending = true;
                    // 调用java层的dispatchBatchedInputEventPending
                    env->CallVoidMethod(receiverObj.get(),
                            gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);
                    if (env->ExceptionCheck()) {
                        ALOGE("Exception dispatching batched input events.");
                        mBatchedInputEventPending = false; // try again later
                    }
                }
                return OK;
            }
            ALOGE("channel '%s' ~ Failed to consume input event.  status=%d",
                    getInputChannelName(), status);
            return status;
        }
        assert(inputEvent);

        if (!skipCallbacks) {
            ...
            jobject inputEventObj;
            switch (inputEvent->getType()) {
            case AINPUT_EVENT_TYPE_KEY: 
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
                }
                inputEventObj = android_view_KeyEvent_fromNative(env,
                        static_cast<KeyEvent*>(inputEvent));
                break;
            ...
            if (inputEventObj) {
                // //执行Java层的InputEventReceiver.dispachInputEvent
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
                        displayId);
                if (env->ExceptionCheck()) {
                    ALOGE("Exception dispatching input event.");
                    skipCallbacks = true;
                }
                env->DeleteLocalRef(inputEventObj);
            } else {      
                skipCallbacks = true;
            }             
        }                 

        if (skipCallbacks) {
            mInputConsumer.sendFinishedSignal(seq, false);
        }                 
    }                     
}

如果inputEventObj不为null, 调用java层的InputEventReceiver.dispachInputEvent()

//  frameworks/base/core/java/android/view/InputEventReceiver.java
    // Called from native code.    
    @SuppressWarnings("unused")    
    private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(eventdisplayId); 
    } 

sendMessage后的流程

NativeInputEventReceiver::handleEvent() -->   consumeEvents --> WindowInputEventReceiver.onInputEvent()
--> ViewRootImpl.enqueueInputEvent() --> ViewRootImpl.doProcessInputEvents()  --> ViewRootImpl.deliverInputEvent() 

标签:status,return,--,text,流程,connection,env,Input,event
From: https://www.cnblogs.com/tangshunhui/p/17556683.html

相关文章

  • 芯片设计全流程
    芯片设计全流程(包括每个流程需要用到的工具以及需要参与的工作人员)  设计rtl设计工程师要懂低功耗设计方法,跨时钟域的解决方案,面积,功耗,逻辑综合验证检验rtl级的HDL设计是否实现了Spec需要的功能等;验证工程师要懂设计,能看懂rtl,能够理解设计的各种方法;作为soc验证......
  • Java流程控制
    Java流程控制Scanner对象之前我们学的基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户的输入。Java.util.Scanner是Java5的新特性,我们可以通过Scanner类来获取用户的输入。基本语法:Scanners=newScanner(System.in);通......
  • go context
    使用场景在协程之间传递上下文context接口typeContextinterface{//返回绑定当前context的任务取消的截止时间//如果没有设定期限,将返回ok==falseDeadline()(deadlinetime.Time,okbool)//绑定当前context的任务取消时返回一个关闭的channel......
  • 在React之下,Context 或者 React-query库该如何选择
    首先,如果是服务器的状态需要用React-query库最方便,如果是客户端的状态用localstate+Context就可以了。 TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChineseSimplifiedHungarianRuss......
  • go text模板
    packageinstallimport("bytes""fmt""strings""text/template""github.com/fanux/sealos/pkg/logger""sigs.k8s.io/yaml")varConfigTypestringfuncsetKubeadmAPI(versionstring){maj......
  • 网站搭建流程分享
    购买服务器(新人都有免费的)–>去控制台到实例管理里重置实例密码–>回到概况点击远程连接,然后输入刚刚设置的密码–>去宝塔官方复制Linux面板的安装脚本(看操作系统进行选择)–>粘贴到远程连接台–>安装完成后输入bt14查看地址和账号和记住端口–>安全组添加ATP对应端口和80端口......
  • SQL注入问题、视图、触发器、事务、存储过程、函数、流程控制、索引、测试索引
    SQL注入问题连接MySQL服务器conn=pymysql.connect(host=‘127.0.0.1’port=3306user=‘root’password='1234'......
  • 2023年iOS App Store上架流程详解(上)
    ​ 很多开发者在开发完iOSAPP、进行内测后,下一步就面临上架AppStore,不过也有很多同学对APP上架AppStore的流程不太了解,下面我们来说一下iOSAPP上架AppStore的具体流程,如有未涉及到的部分,大家可以及时咨询,共同探讨。内容:在完成iOSAPP开发和内部测试后,下一个步骤就是将应用......
  • iOS App Store上架流程详解
    ​ 很多开发者在开发完iOSAPP、进行内测后,下一步就面临上架AppStore,不过也有很多同学对APP上架AppStore的流程不太了解,下面我们来说一下iOSAPP上架AppStore的具体流程,如有未涉及到的部分,大家可以及时咨询,共同探讨。内容:在完成iOSAPP开发和内部测试后,下一个步骤就是将应用......
  • 【全流程管理解决方案】奥威BI金蝶云星空SaaS版:重新定义企业管控
    金蝶云星空是一套全面覆盖供应链、采购、生产、销售、财务等业务流程,实现了全链条的闭环管理的综合性管理软件。但在云时代,仅仅覆盖业务流程还不够,还需要有一套全流程管理解决方案,实现对全业务流程数据的深度挖掘,为运营决策提供支持。奥威BI金蝶云星空SaaS版,一套覆盖全流程业务管理......