1. 程式人生 > >應用程式請求註冊對Input事件的監聽

應用程式請求註冊對Input事件的監聽

一、應用程式在繪製View時註冊監聽事件

[/frameworks/base/core/java/android/view/ViewRootImpl.java]
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ...
            requestLayout();
            //建立InputChannel例項,建立後的InputChannel是未初始化的,可以通過從Parcel物件中讀取資訊初始化
            //或者呼叫transferTo(InputChannel outParameter)從另一個InputChannel例項中獲取初始化資訊。
            if
((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } try { ... //addToDisplay()方法會呼叫WindowManagerService的addWindow()方法。 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { ...
mView = null; mInputChannel = null; ... throw new RuntimeException("Adding window failed", e); } finally { ... } if (mInputChannel != null) { if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue); } //建立WindowInputEventReceiver例項,使用前面建立好的InputChannel例項作引數。 mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); } } } }

Session類的addToDisplay()方法會呼叫WindowManagerService的addWindow()方法,這個方法非常重要。

[/frameworks/base/services/java/com/android/server/wm/WindowManagerService.java]
public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        InputChannel outInputChannel) {
    ...
    synchronized(mWindowMap) {
        ...
        if (outInputChannel != null && (attrs.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            String name = win.makeInputChannelName();
            //建立名為name的socketpair例項,將socket[0](服務端)註冊到InputManagerService中,
            //將socket[1](客戶端)傳遞給應用程序。
            InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
            win.setInputChannel(inputChannels[0]);
            inputChannels[1].transferTo(outInputChannel);

            mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
        }
    }//synchronized

    return res;
}

二、InputManagerService對socketpair服務端的處理

2.1 InputManagerService的初始化和啟動

public InputManagerService(Context context) {
    this.mContext = context;
    //InputManagerService的Handler使用的是DisplayThread的Looper。
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

    mUseDevInputEventForAudioJack =
            context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);

    //在native層完成初始化, nativeInit()返回的是JNI層建立的NativeInputManager物件的指標。Java層儲存Native的物件指標,這個是Android中非常常見的用法。
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

    LocalServices.addService(InputManagerInternal.class, new LocalService());
}

[com_android_server_input_InputManagerService.cpp]
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, “MessageQueue is not initialized.”);
return 0;
}

//new一個NativeInputManager例項,
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);

}

NativeInputManager類

NativeInputManager類的設計

class NativeInputManager : public virtual RefBase,
    public virtual InputReaderPolicyInterface,
    public virtual InputDispatcherPolicyInterface,
    public virtual PointerControllerPolicyInterface {
protected:
    virtual ~NativeInputManager();

public:
    NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper);
    inline sp<InputManager> getInputManager() const { return mInputManager; }
    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
            const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
    status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
    ...
private:
    sp<InputManager> mInputManager;

    jobject mContextObj;
    jobject mServiceObj;
    sp<Looper> mLooper;
}

NativeInputManager的建構函式

NativeInputManager::NativeInputManager(jobject contextObj,
    jobject serviceObj, const sp<Looper>& looper) :
    mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();

    mContextObj = env->NewGlobalRef(contextObj);
    mServiceObj = env->NewGlobalRef(serviceObj);

    {
        AutoMutex _l(mLock);
        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
        mLocked.pointerSpeed = 0;
        mLocked.pointerGesturesEnabled = true;
        mLocked.showTouches = false;
    }
    mInteractive = true;

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

“`

“`####InputManagerService的啟動####
主要的工作是把Native層的InputReaderThread和InputDispatcherThread兩個執行緒啟動起來。

public void start() {
    //在native層啟動
    nativeStart(mPtr);

    // Add ourself to the Watchdog monitors.
    Watchdog.getInstance().addMonitor(this);

    registerPointerSpeedSettingObserver();
    registerShowTouchesSettingObserver();

    mContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            updatePointerSpeedFromSettings();
            updateShowTouchesFromSettings();
        }
    }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);

    updatePointerSpeedFromSettings();
    updateShowTouchesFromSettings();
}

com_android_server_input_InputManagerService.cpp

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}

InputManager的設計

介面:

class InputManagerInterface : public virtual RefBase {
protected:
    InputManagerInterface() { }
    virtual ~InputManagerInterface() { }

public:
    /* Starts the input manager threads. */
    virtual status_t start() = 0;

    /* Stops the input manager threads and waits for them to exit. */
    virtual status_t stop() = 0;

    /* Gets the input reader. */
    virtual sp<InputReaderInterface> getReader() = 0;

    /* Gets the input dispatcher. */
    virtual sp<InputDispatcherInterface> getDispatcher() = 0;
};

具體類:

class InputManager : public InputManagerInterface {
protected:
    virtual ~InputManager();

public:
    InputManager(
            const sp<EventHubInterface>& eventHub,
            const sp<InputReaderPolicyInterface>& readerPolicy,
            const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);

    // (used for testing purposes)
    InputManager(
            const sp<InputReaderInterface>& reader,
            const sp<InputDispatcherInterface>& dispatcher);

    virtual status_t start();
    virtual status_t stop();

    virtual sp<InputReaderInterface> getReader();
    virtual sp<InputDispatcherInterface> getDispatcher();

private:
    sp<InputReaderInterface> mReader;
    sp<InputReaderThread> mReaderThread;

    sp<InputDispatcherInterface> mDispatcher;
    sp<InputDispatcherThread> mDispatcherThread;

    void initialize();
};

socketpair服務端的註冊

最終呼叫的是InputDispatcher的registerInputChannel()函式。

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, 
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
  1. 以socketFd作為索引,將Connection例項新增到:KeyedVector

InputDispatcher的分發執行緒

分發流程:
1. 輪詢mCommandQueue;
2. 輪詢mLooper。
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();

        //如果佇列mCommandQueue不為空,則呼叫dispatchOnceInnerLocked()函式進行分發。
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
    } // release lock

    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

bool InputDispatcher::runCommandsLockedInterruptible() {
    //如果佇列mCommandQueue為空,直接返回flase。
    if (mCommandQueue.isEmpty()) {
        return false;
    }

    do {
        //依次從佇列mCommandQueue中出列一個元素,呼叫command()函式,直到佇列為空為止,返回true。
        CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();

        Command command = commandEntry->command;
        (this->*command)(commandEntry);

        commandEntry->connection.clear();
        delete commandEntry;
    } while (! mCommandQueue.isEmpty());
    return true;
}

三、應用程式對SocketPair客戶端事件的處理

3.1 Native層的InputEventReceiver的設計

    [/frameworks/base/core/jni/android_view_InputEventReceiver.cpp]
    class NativeInputEventReceiver : public LooperCallback {
    public:
        NativeInputEventReceiver(JNIEnv* env,
                jobject receiverWeak, const sp<InputChannel>& inputChannel,
                const sp<MessageQueue>& messageQueue);

        status_t initialize();
        void dispose();
        status_t finishInputEvent(uint32_t seq, bool handled);
        status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
                bool* outConsumedBatch);

    protected:
        virtual ~NativeInputEventReceiver();

    private:
        struct Finish {
            uint32_t seq;
            bool handled;
        };

        jobject mReceiverWeakGlobal;
        InputConsumer mInputConsumer;
        sp<MessageQueue> mMessageQueue;
        PreallocatedInputEventFactory mInputEventFactory;
        bool mBatchedInputEventPending;
        int mFdEvents;
        Vector<Finish> mFinishQueue;

        void setFdEvents(int events);

        const char* getInputChannelName() {
            return mInputConsumer.getChannel()->getName().string();
        }

        virtual int handleEvent(int receiveFd, int events, void* data);
    };

status_t NativeInputEventReceiver::initialize() {
        //監聽型別為ALOOPER_EVENT_INPUT
        setFdEvents(ALOOPER_EVENT_INPUT);
        return OK;
    }

    //NativeInputEventReceiver繼承自LooperCallback,有事件發生時會回撥handleEvent()函式。
    void NativeInputEventReceiver::setFdEvents(int events) {
        if (mFdEvents != events) {
            mFdEvents = events;
            int fd = mInputConsumer.getChannel()->getFd();
            if (events) {
                mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
            } else {
                mMessageQueue->getLooper()->removeFd(fd);
            }
        }
    }

    int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
            return 0; // remove the callback
        }

        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);
                if (status) {
                    mFinishQueue.removeItemsAt(0, i);

                    if (status == WOULD_BLOCK) {
                        return 1; // keep the callback, try again later
                    }

                    ALOGW("Failed to send finished signal on channel '%s'.  status=%d",
                            getInputChannelName(), status);
                    if (status != DEAD_OBJECT) {
                        JNIEnv* env = AndroidRuntime::getJNIEnv();
                        String8 message;
                        message.appendFormat("Failed to finish input event.  status=%d", status);
                        jniThrowRuntimeException(env, message.string());
                        mMessageQueue->raiseAndClearException(env, "finishInputEvent");
                    }
                    return 0; // remove the callback
                }
            }
            mFinishQueue.clear();
            setFdEvents(ALOOPER_EVENT_INPUT);
            return 1;
        }

        return 1;
    }

    status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
            bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
        if (consumeBatches) {
            mBatchedInputEventPending = false;
        }
        if (outConsumedBatch) {
            *outConsumedBatch = false;
        }

        ScopedLocalRef<jobject> receiverObj(env, NULL);
        bool skipCallbacks = false;
        for (;;) {
            uint32_t seq;
            InputEvent* inputEvent;
            status_t status = mInputConsumer.consume(&mInputEventFactory,
                    consumeBatches, frameTime, &seq, &inputEvent);
            if (status) {
                if (status == WOULD_BLOCK) {
                    ...
                }
                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:
                    inputEventObj = android_view_KeyEvent_fromNative(env,
                            static_cast<KeyEvent*>(inputEvent));
                    break;

                case AINPUT_EVENT_TYPE_MOTION: {
                    MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                    if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                        *outConsumedBatch = true;
                    }
                    inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                    break;
                }

                default:
                    assert(false); // InputConsumer should prevent this from ever happening
                    inputEventObj = NULL;
                }

                if (inputEventObj) {
                    //呼叫Java層的dispatchInputEvent()方法。
                    env->CallVoidMethod(receiverObj.get(),
                            gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
                    if (env->ExceptionCheck()) {
                        ALOGE("Exception dispatching input event.");
                        skipCallbacks = true;
                    }
                    env->DeleteLocalRef(inputEventObj);
                } else {
                    ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());
                    skipCallbacks = true;
                }
            }

            /* 前面執行成功時,skipCallbacks為false, 最終會呼叫sendFinishedSignal()函式,
               它會構建InputMessage,格式:
                 InputMessage msg;
                 msg.header.type = InputMessage::TYPE_FINISHED;
                 msg.body.finished.seq = seq;
                 msg.body.finished.handled = handled;
                 mChannel->sendMessage(&msg);
               socketpair的服務端會收到該訊息後,就知道該訊息已經成功分發了。
            */
            if (skipCallbacks) {
                mInputConsumer.sendFinishedSignal(seq, false);
            }
        }
    }

四、InputDispatcher事件傳送成功後的處理

int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
        InputDispatcher* d = static_cast<InputDispatcher*>(data);

        { // acquire lock
            AutoMutex _l(d->mLock);

            ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd);
            if (connectionIndex < 0) {
                ALOGE("Received spurious receive callback for unknown input channel.  "
                        "fd=%d, events=0x%x", fd, events);
                return 0; // remove the callback
            }

            bool notify;
            sp<Connection> connection = d->mConnectionsByFd.valueAt(connectionIndex);
            if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
                if (!(events & ALOOPER_EVENT_INPUT)) {
                    ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
                            "events=0x%x", connection->getInputChannelName(), events);
                    return 1;
                }

                nsecs_t currentTime = now();
                bool gotOne = false;
                status_t status;
                for (;;) {
                    uint32_t seq;
                    bool handled;
                    status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
                    if (status) {
                        break;
                    }
                    d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
                    gotOne = true;
                }
                if (gotOne) {
                    d->runCommandsLockedInterruptible();
                    if (status == WOULD_BLOCK) {
                        return 1;
                    }
                }

                notify = status != DEAD_OBJECT || !connection->monitor;
                if (notify) {
                    ALOGE("channel '%s' ~ Failed to receive finished signal.  status=%d",
                            connection->getInputChannelName(), status);
                }
            } else {
                // Monitor channels are never explicitly unregistered.
                // We do it automatically when the remote endpoint is closed so don't warn
                // about them.
                notify = !connection->monitor;
                if (notify) {
                    ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred.  "
                            "events=0x%x", connection->getInputChannelName(), events);
                }
            }

            // Unregister the channel.
            d->unregisterInputChannelLocked(connection->inputChannel, notify);
            return 0; // remove the callback
        } // release lock
    }


    status_t InputDispatcher::unregisterInputChannelLocked(const sp<InputChannel>& inputChannel,
            bool notify) {
        ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
        if (connectionIndex < 0) {
            ALOGW("Attempted to unregister already unregistered input channel '%s'",
                    inputChannel->getName().string());
            return BAD_VALUE;
        }

        //移除mConnectionsByFd中與connIndex對應的鍵值對。
        sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
        mConnectionsByFd.removeItemsAt(connectionIndex);

        if (connection->monitor) {
            removeMonitorChannelLocked(inputChannel);
        }

        //移除該InputChannel包含的socketFd。
        mLooper->removeFd(inputChannel->getFd());

        nsecs_t currentTime = now();
        abortBrokenDispatchCycleLocked(currentTime, connection, notify);

        connection->status = Connection::STATUS_ZOMBIE;
        return OK;
    }

相關推薦

應用程式請求註冊Input事件

一、應用程式在繪製View時註冊監聽事件 [/frameworks/base/core/java/android/view/ViewRootImpl.java] public void setView(View view, WindowManager.La

Spring的事件應用

最近公司在重構廣告系統,其中核心的打包功由廣告系統呼叫,即對apk打包的呼叫和打包完成之後的回撥,需要提供相應的介面給廣告系統。因此,為了將apk打包的核心流程和對接廣告系統的業務解耦,利用了spring的事件監聽特性來滿足需求。以下說明spring的事件機制的相關內容。   1.觀察

如何vue中的元件進行點選事件

<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-default/index.css"> </head> <body> <div id="

如何判斷一個HTTP請求是瀏覽器請求還是應用程式請求

1、獲取請求的request HttpServletRequest request=ServletActionContext.getRequest(); 2、攔截器中判斷請求頭 通常判斷來自手機端的請求還是PC端的請求只需要判斷: request.getHea

觀察者模式的程式例項C++ 以及觀察者模式與事件的區別

一、什麼是觀察者模式      Observer模式也叫觀察者模式,它的作用是當一個物件的狀態發生變化時,可以自己主動通知其它關聯物件,自己主動重新整理物件狀態。 舉個樣例,使用者介面能夠作為一個觀察者,業務資料是被觀察者,使用者介面觀察業務資料的變化,發現數據變化後,

事件註冊事件的相容性問題

關於時間監聽問題: 標準: 元素.addEventListener(‘事件型別’,事件處理程式); 在IE低版本中,該鍵的值是undefined,所以無法當做方法或函式呼叫。所以會報錯。 IE低版本: 元素.attachEvent(‘事件型別’,事件處理程式);

Android應用程式請求SurfaceFlinger服務渲染Surface的過程分析

                        在前面一篇文章中,我們分析了Android應用程式請求SurfaceFlinger服務建立Surface的過程。有了Surface之後,Android應用程式就可以在上面繪製自己的UI了,接著再請求SurfaceFlinger服務將這個已經繪製好了UI的Surf

input標籤事件總結

監聽事件對於文字標籤元素很常見,如input、select這些,監聽其實就是當他們的值發生改變時去觸發相應事件。input常見的事件如下: 1.onfocus  當input 獲取到焦點時觸發 2.onblur  當input失去焦點時觸發 3.onchange 當i

如何將應用程式exe註冊成服務,直接從後臺執行

方法一:使用windows自帶的命令sc      使用sc create 方法建立。      如:sc create CaptureScreen binpath= "F:\zwmei-project\decklink-learning\OutputBitmap\Deb

Spring中的事件機制在專案中的應用

最經在做專案的時候,呼叫某個介面的時候為了呼叫地圖,而不希望因為呼叫超時影響到主執行緒,使用了spring的時間監聽機制。 Spring中提供一些Aware相關的介面,BeanFactoryAware、 ApplicationContextAware、Reso

利用C#開發web應用程式時,登錄檔進行操作提示沒有許可權的解決辦法

因為公司專案需要對web程式新增一套限制客戶惡意傳播的方案。沒辦法,東西放在客戶的伺服器或者電腦裡面。鑑於本人菜鳥一個,也就能想到利用兩種方案,具體的實現的方式,將會在之後的博文中寫出。 我寫這篇文章

輸入框事件(一):keydown、keyup、input

當輸入框的值發生變化時,我們可以通過keydown、keyup、input、onchange、blur事件觀察到其值的變化,但它們的應用時機與應用場景存在顯著的差異。 1. 實時觀察 需要觀察到使用者每次鍵盤輸入的變化,必須要用keydown、keyup

jquery 自定義input輸入事件

網上一段JS,考來的,自定義監聽: $.event.special.valuechange = { teardown: function (namespaces) { $(this).unbind('.valuechange')

addEventListener註冊事件事件

註冊事件的簡單方式:: bth.on事件 = function{ }; 如果重複註冊相同的事件,後面的事件會把前面的事件覆蓋掉。 例子: 輸入一下程式碼,點選事件,控制檯輸出 document.onclick = function () { console.log("厲害"

事件處理程式事件繫結、事件事件器)

相應某個事件的函式叫做事件處理程式(或事件偵聽器)。 1、TTML事件處理程式 <script type="text/javascript"> function showMessage() { alert('hello world!')

25 API-GUI(事件機制,介面卡模式),Netbeans的概述和使用(模擬登陸註冊GUI版)

 1:GUI(瞭解) (1)使用者圖形介面GUI:方便直觀CLI:需要記憶一下命令,麻煩(2)兩個包:java.awt:和系統關聯較強  ,屬重量級控制元件javax.swing:純Java編寫,增強了移植性,屬輕量級控制元件。(3)GUI的繼承體系元件:元件就是物件容器元

mfc 如何捕獲應用程式視窗以外的滑鼠事件

一般應用程式當中的滑鼠事件只能爭對應用程式視窗內部有效,如果點選應用程式以外的視窗,例如點選其它應用程式視窗等,這個時候,滑鼠訊息是不會發送給我們的應用程式視窗,更不會激發事件。這樣怎麼處理呢。我們可以利用mfc視窗中的windows訊息處理函式,來處理我們的滑鼠或鍵盤事件操作如下: 首先在我們需要開啟捕獲

Spring事件模式應用場景和思路

什麼是事件  程式中的事件其實和現實差不多,例如:Js中的事件有很多 如滑鼠的單擊事件onclick。 當點選某個按鈕時--觸發某個方法。當你不去觸發這個事件、這個事件就永遠的在等待 喚醒事件的人; 事件三要素 1、定義一個事件(火災事件、碰撞事件、收到資訊事件。。。

springBoot事件 在專案實際業務中的非同步應用

第一步 :在啟動類添加註解@EnableAsync,自定義執行緒池 第二步 : 編寫實體繼承ApplicationEvent 第三步:編寫事件處理 注入spring容器 方法名上添加註解@Async和@EventListener(非同步處理和事件監聽) 第四步 :

jquery實現級聯遇到的ajax同步請求、動態DOM元素事件

記錄一次實現級聯選單選項遇到的一系列問題 實現動態生成select下拉選項 json資料格式example: [ { "eventTyp