Android InputChannel事件发送接收系统分析

这篇具有很好参考价值的文章主要介绍了Android InputChannel事件发送接收系统分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文基于Android12。

InputChannel表示其他进程通过文件描述符传递输入事件到View的通道,因为需要跨进程传输,实现了Parcelable序列化接口,所以也能够理解Java层的InputChannel后面为什么使用copyTo()方法初始化。

输入事件的接收方是View,所以InputChannel的创建肯定和View的创建流程有关,关于View的创建流程参考:https://blog.csdn.net/qq_36063677/article/details/129908973。

一、InputChannel初始化

ViewRootImpl在setView()方法实例化了Java层的InputChannle对象,但是正如ViewRootImpl创建的mSurface对象一样,这只是一个引用,一个“空的”对象,后续在WindowManagerService经过实际的初始化,再通过copyTo()方法拷贝到InputChannel引用中。

    // ViewRootImpl.java
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
      	// 1.创建 inputChannel引用。
        InputChannel inputChannel = null;
        if ((mWindowAttributes.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            inputChannel = new InputChannel();
        }
      	// 2.传递引用给mWindowSession,inputChannel在WMS中被初始化,并通过copyTo()全拷贝到inputChannel引用。
        res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(), userId,
                mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                mTempControls, attachedFrame, sizeCompatScale);
      	// 3.通过inputChannel创建WindowInputEventReceiver对象,接收处理输入事件。ALOOPER_EVENT_INPUT
        if (inputChannel != null) {
            mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                    Looper.myLooper());
        }
    }

setView()方法中关于InputChannel的操作主要有3步:

  1. 创建 inputChannel引用。就是实例化InputChannel引用,InputChannel构造方法是个空方法,所以什么实际操作都没有做。
  2. 传递引用给mWindowSession,inputChannel在WMS中被初始化,并通过copyTo()全拷贝到inputChannel引用。
  3. 通过inputChannel创建WindowInputEventReceiver对象,接收处理输入事件。

我们从第2步开始分析。

1.1 openInputChannle

mWindowSession将inputChannel引用传递给WMS.addWindow()方法:

    // WindowManagerService.java    
    		public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
                int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
                InputChannel outInputChannel, InsetsState outInsetsState,
                InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
                float[] outSizeCompatScale) {
          			final WindowState win = new WindowState(this, session, client, token, parentWindow,
                        appOp[0], attrs, viewVisibility, session.mUid, userId,
                        session.mCanAddInternalSystemWindow);
                final boolean openInputChannels = (outInputChannel != null
                        && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
                if  (openInputChannels) {
                    win.openInputChannel(outInputChannel);
                }
        }

addWindow()创建了WindowState对象,调用其openInputChannel(outInputChannel)方法。

    // WindowState.java    
    		void openInputChannel(InputChannel outInputChannel) {
            if (mInputChannel != null) {
                String name = getName();
                mInputChannel = mWmService.mInputManager.createInputChannel(name);
                if (outInputChannel != null) {
                    mInputChannel.copyTo(outInputChannel);
                }
            }
        }

WindowState通过InputMangerService创建InputChannel,经过NativeInputManagerService类的native方法createInputChannel(name),最终到InputDispatcher::createInputChannel(name)方法实际创建。

1.1.1 InputDispatcher::createInputChannel

    // InputDispatcher.h    
    		// All registered connections mapped by input channel token.
        std::unordered_map<sp<IBinder>, sp<Connection>, StrongPointerHash<IBinder>> mConnectionsByToken
                GUARDED_BY(mLock);
    
    // InputDispatcher.cpp
    	Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {
      
        std::unique_ptr<InputChannel> serverChannel;
        std::unique_ptr<InputChannel> clientChannel;
        // 1.通过CPP层的InputChannel创建serverChannel,clientChannel
        status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
        {
            std::scoped_lock _l(mLock);
            const sp<IBinder>& token = serverChannel->getConnectionToken();
          	// 2.得到serverChannel的文件描述符 fd
            int fd = serverChannel->getFd();
          	// 3.将connection对象添加到 mConnectionsByToken管理
            sp<Connection> connection =
                    new Connection(std::move(serverChannel), false /*monitor*/, mIdGenerator);
            mConnectionsByToken.emplace(token, connection);
    
            std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                                this, std::placeholders::_1, token);
          	mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);
          	// 4.监听serverChannel的文件描述符 fd,当有事件发生时,回调callback
            mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
        }
    
        // 5.唤醒mLooper
        mLooper->wake();
        return clientChannel;
    }

InputDispatcher主要做了5件事:

  1. 通过CPP层的InputChannel创建serverChannel,clientChannel,最后返回clientChannel给Java层的InputChannel,到这里Java层InputChannel才被初始化完成,返回创建WindowInputEventReceiver对象。
  2. 得到serverChannel的文件描述符 fd。
  3. 将connection对象添加到 mConnectionsByToken管理,mConnectionsByToken定义在InputDispatcher.h文件,管理所有连接的InputChannel对象,map的key是token,又是使用Binder对象作为token。inputflinger文章中(https://blog.csdn.net/qq_36063677/article/details/130475299)4.3.3小节InputDispatcher在分发事件时就是通过这个mConnectionsByToken获取到具体的connection,发送事件。
  4. 监听serverChannel的文件描述符 fd,ALOOPER_EVENT_INPUT表示为要监听的文件类型,当有事件发生时,回调callback方法handleEvent(),也就是InputDispatcher::handleReceiveCallback()。LooperEventCallback继承了LooperCallback类,LooperCallback是Looper监听文件描述符回调方法的标准类,当文件描述符fd上有事件到来时,LooperCallback的handleEvent()方法会被执行,关于Looper->addFd()更多细节参考:https://blog.csdn.net/chwan_gogogo/article/details/46953563
  5. 唤醒mLooper,mLooper在InputDispatcher构造方法中被初始化,mLooper = new Looper(false);。

查看cpp层InputChannel::openInputChannelPair()具体细节:

1.1.2 InputChannel::openInputChannelPair

    // InputTransport.cpp
    status_t InputChannel::openInputChannelPair(const std::string& name,
                                                std::unique_ptr<InputChannel>& outServerChannel,
                                                std::unique_ptr<InputChannel>& outClientChannel) {
        int sockets[2];
	    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
	        outServerChannel.clear();
	        outClientChannel.clear();
	        return result;
	    }
    
        int bufferSize = SOCKET_BUFFER_SIZE;
        setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
        setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
        setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
        setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    
        sp<IBinder> token = new BBinder();
    
        std::string serverChannelName = name + " (server)";
        android::base::unique_fd serverFd(sockets[0]);
        outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);
    
        std::string clientChannelName = name + " (client)";
        android::base::unique_fd clientFd(sockets[1]);
        outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);
        return OK;
    }
    
    std::unique_ptr<InputChannel> InputChannel::create(const std::string& name,
                                                       android::base::unique_fd fd, sp<IBinder> token) {
      	const int result = fcntl(fd, F_SETFL, O_NONBLOCK);
        return std::unique_ptr<InputChannel>(new InputChannel(name, std::move(fd), token));
    }
    
    InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token)
          : mName(std::move(name)), mFd(std::move(fd)), mToken(std::move(token)) {
    }
  1. InputChannel使用socket通信,openInputChannelPair()方法创建两个socke对象,一个client,一个server。socketpair()函数用于创建一对无名的、相互连接的套接子。如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1],所以outServerChannel和outClientChannel这两个socket在创建的时候就是相互连接的,之后只需要在各自的fd中通过send()发送数据就好。
  2. setsockopt()设置socket属性,在<sys/socket.h>头文件中声明,实例化一个BBinder()对象作为token使用,这里outServerChannel和outClientChannel都使用的是同一个token。
  3. InputChannel::create()通过fcntl()设置socket为非阻塞类型,fcntl()方法可以改变已打开的文件性质。
  4. 封装InputChannel对象。

InputChannel初始化过程终于结束了,那么1.1.1小节中mLooper监听的serverChannel的文件描述符 fd什么时候会触发呢?

答案在上一篇inputflinger文章中(https://blog.csdn.net/qq_36063677/article/details/130475299)4.3.3小节。

二、InputChannel发送事件

InputDispatcher在接收到事件后,InputDispatcher::dispatchEventLocked()从mConnectionsByToken变量中通过token获取到Connection对象,最终在4.3.4.2 小节startDispatchCycleLocked()方法调用

connection->inputPublisher.publishMotionEvent()发送输入事件,这里还是以Motion事件为例:

    // InputTransport.cpp
    status_t InputPublisher::publishMotionEvent(
            uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,
            std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
            int32_t edgeFlags, int32_t metaState, int32_t buttonState,
            MotionClassification classification, const ui::Transform& transform, float xPrecision,
            float yPrecision, float xCursorPosition, float yCursorPosition,
            const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
            uint32_t pointerCount, const PointerProperties* pointerProperties,
            const PointerCoords* pointerCoords) {
      	
      	InputMessage msg;
        msg.header.type = InputMessage::Type::MOTION;
        msg.header.seq = seq;
        msg.body.motion.eventId = eventId;
        msg.body.motion.deviceId = deviceId;
        msg.body.motion.source = source;
      	//.......
      	return mChannel->sendMessage(&msg);
    }

mChannel是之前创建的InputChannel对象serverChannel,查看sendMessage(&msg)方法:

    // InputTransport.cpp
    status_t InputChannel::sendMessage(const InputMessage* msg) {
        const size_t msgLength = msg->size();
        InputMessage cleanMsg;
        msg->getSanitizedCopy(&cleanMsg);
        ssize_t nWrite;
        do {
            nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
        } while (nWrite == -1 && errno == EINTR);
        return OK;
    }

调用socket的send()函数发送数据。

至此输入事件通过socket发送出去了,InputDispatcher执行回调LooperEventCallback,那么事件又是如何被接收的呢?

三、InputEventReceiver

ViewRootImpl在setView()方法实例化InputChannel后,作为参数初始化WindowInputEventReceiver对象,WindowInputEventReceiver继承InputEventReceiver类。

3.1 WindowInputEventReceiver

WindowInputEventReceiver也在ViewRootImpl中定义:

    // ViewRootImpl.java
    final class WindowInputEventReceiver extends InputEventReceiver {
      
            public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
                super(inputChannel, looper);
            }        
      			@Override
            public void onInputEvent(InputEvent event) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
                List<InputEvent> processedEvents;
                try {
                    processedEvents =
                        mInputCompatProcessor.processInputEventForCompatibility(event);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
                if (processedEvents != null) {
                    if (processedEvents.isEmpty()) {
                        // InputEvent consumed by mInputCompatProcessor
                        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);
                }
            }
    }
    
    // InputEventReceiver.java
    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                    mInputChannel, mMessageQueue);
    
        mCloseGuard.open("InputEventReceiver.dispose");
    }

WindowInputEventReceiver构造方法中将参数传递给了父类InputEventReceiver,实现了onInput()方法,后续接收到事件后通过这个方法处理Java层的分发逻辑,Eventlooper是ViewRoolImpl的当前线程Looper.myLooper(),也就是主线程,InputEventReceiver调用nativeInit()继续下一步操作,创建NativeInputEventReceiver。

3.2 NativeInputEventReceiver

NativeInputEventReceiver定义在JNI文件中,方便后续回调JAVA方法。

    // android_view_InputEventReceiver.cpp
    static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
            jobject inputChannelObj, jobject messageQueueObj) {
      	// 获取InputChannel
        std::shared_ptr<InputChannel> inputChannel =
                android_view_InputChannel_getInputChannel(env, inputChannelObj);	
      	// 获取messageQueue
        sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
      	// 1. 实例化NativeInputEventReceiver
        sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
                receiverWeak, inputChannel, messageQueue);
      	// 2. receiver->initialize()
        status_t status = receiver->initialize();	
        receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
        return reinterpret_cast<jlong>(receiver.get())
    }

nativeInit()主要做了两件事,实例化NativeInputEventReceiver,并且调用其initialize()方法。

3.2.1 NativeInputEventReceiver实例化

    // android_view_InputEventReceiver.cpp
    NativeInputEventReceiver::NativeInputEventReceiver(
            JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel,
            const sp<MessageQueue>& messageQueue)
          : mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
            mInputConsumer(inputChannel),
            mMessageQueue(messageQueue),
            mBatchedInputEventPending(false),
            mFdEvents(0) {
    }

NativeInputEventReceiver构造方法中持有Java层InputEventRecevier对象的引用mReceiverWeakGlobal, 将inputChannel作为参数实例化mInputConsumer对象。

InputConsumer类定义在InputTransport.h头文件,从InputChannel消费事件。

    // InputTransport.cpp
    InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
          : mResampleTouch(isTouchResamplingEnabled()), mChannel(channel), mMsgDeferred(false) {}

client端的inputChannel被赋值给InputConsumer的mChannel变量。

回到NativeInputEventReceiver初始化流程中。

3.2.2 NativeInputEventReceiver::initialize

    // android_view_InputEventReceiver.cpp
    status_t NativeInputEventReceiver::initialize() {
        setFdEvents(ALOOPER_EVENT_INPUT);
        return OK;
    }
    
    void NativeInputEventReceiver::setFdEvents(int events) {
        if (mFdEvents != events) {
            mFdEvents = events;
            int fd = mInputConsumer.getChannel()->getFd();
            if (events) {
                mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
            } else {
                mMessageQueue->getLooper()->removeFd(fd);
            }
        }
    }

initialize()监听client InputChannel的文件描述符 fd,文件类型文件类型和发送端一样,也是ALOOPER_EVENT_INPUT。在上述1.1.1小节中我们知道后续会回调LooperCallback的handleEvent()方法,NativeInputEventReceiver也继承了LooperCallback类,实现了自己的handleEvent():

3.3 handleEvent

    // 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, nullptr);
            mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
            return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
        }
        if (events & ALOOPER_EVENT_OUTPUT) {
            const status_t status = processOutboundEvents();
            if (status == OK || status == WOULD_BLOCK) {
                return KEEP_CALLBACK;
            } else {
                return REMOVE_CALLBACK;
            }
        }
      	return KEEP_CALLBACK;
    }

handleEvent只是对事件类型进行区分,如果是ALOOPER_EVENT_INPUT类型,详细具体到处理流程在consumeEvents()方法。

3.4 consumeEvents

    status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
            bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
      	for (;;) {
            uint32_t seq;
          	// 1.InputConsumer从socket读取封装事件
            InputEvent* inputEvent;
            status_t status = mInputConsumer.consume(&mInputEventFactory,
                    consumeBatches, frameTime, &seq, &inputEvent);
          	
          	// 2.区分事件类型,封装为Java层的InputEvent对象
            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;
                }
            }
          	
          	// 3.调用Java层InputEventReceiver对象的dispatchInputEvent方法,参数为seq,inputEventObj
          	if (inputEventObj) {
              	env->CallVoidMethod(receiverObj.get(),
                            gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
                env->DeleteLocalRef(inputEventObj);
            }
        
      	}
    }

consumeEvents()处理了事件接收的主要流程:

  1. 启动一个死循环,不断从InputConsumer获取事件,赋值到inputEvent引用,InputConsumer持有客户端InputChannel的引用,从客户端socket读取数据,将数据解析封装成InputEvent对象。
  2. 将InputEvent类型进行区分,具体共有AINPUT_EVENT_TYPE_KEY,AINPUT_EVENT_TYPE_MOTION,AINPUT_EVENT_TYPE_FOCUS,AINPUT_EVENT_TYPE_CAPTURE,AINPUT_EVENT_TYPE_DRAG,AINPUT_EVENT_TYPE_TOUCH_MODE六种类型,对于Motion事件,封装成Java层的MotionEvent对象。
  3. receiverObj指向NativeInputEventReceiver实例化时传递过来的Java层InputEventReceiver引用,调用Java层InputEventReceiver对象的dispatchInputEvent方法,参数为seq,inputEventObj,至此回到了Java层的处理流程。

在分析Java层的处理流程之前,先看下InputConsumer是如何读取socket数据解析成InputEvent对象的。

3.4.1 InputConsumer.consume

    // InputTransport.cpp
    status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
                                    nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
        while (!*outEvent) {
            status_t result = mChannel->receiveMessage(&mMsg);
          	switch (mMsgmMsg.header.type) {
                case InputMessage::Type::KEY: {
                    KeyEvent* keyEvent = factory->createKeyEvent();
                    if (!keyEvent) return NO_MEMORY;
    
                    initializeKeyEvent(keyEvent, &mMsg);
                    *outSeq = mMsg.header.seq;
                    *outEvent = keyEvent;
                  	break;
                }
                case InputMessage::Type::MOTION: {
                    MotionEvent* motionEvent = factory->createMotionEvent();
                    if (!motionEvent) return NO_MEMORY;
    
                    updateTouchState(mMsg);
                    initializeMotionEvent(motionEvent, &mMsg);
                    *outSeq = mMsg.header.seq;
                    *outEvent = motionEvent;
                  	break;
                }
            }
        }
        return OK;
    }

mChannel是NativeInputEventReceiver传递过去的客户端InputChannel对象,InputConsumer从mChannel读取消息,在上述第二小节:InputChannel发送事件 可知,发送的InputMessage对象封装了mMsg.header.type等事件类型消息,这里通过事件类型消息进行区分,封装成具体对应的输入事件,如KeyEvent,MotionEvent等,赋值给outEvent。

InputChannel通过recv()方法接收socket消息:

    // InputTransport.cpp
    status_t InputChannel::receiveMessage(InputMessage* msg) {
        ssize_t nRead;
        do {
            nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
        } while (nRead == -1 && errno == EINTR);
    }

3.4.2 dispatchInputEvent

事件的读取解析流程到此结束了,接下来看看InputReceiver是如何分发事件的。

    // InputEventReceiver.java
    		// Called from native code.
        @SuppressWarnings("unused")
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        private void dispatchInputEvent(int seq, InputEvent event) {
            mSeqMap.put(event.getSequenceNumber(), seq);
            onInputEvent(event);
        }

InputEventReceiver调用onInputEvent(event)开始处理事件的分发流程,上述3.1小节可知,onInputEvent方法在WindowInputEventReceiver类中被覆盖。Java层的事件分发流程参考:https://editor.csdn.net/md/?articleId=130476234文章来源地址https://www.toymoban.com/news/detail-433124.html

到了这里,关于Android InputChannel事件发送接收系统分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32407 FreeRtos系统下lwip实现发送和接收数据包进行链路测试

    下面是一个基于STM32F407 FreeRTOS系统下lwIP实现发送和接收数据包进行链路测试的案例。 硬件配置和软件环境搭建 首先,需要准备好STM32F407开发板、以太网网线和一台电脑,然后在电脑上安装好STM32CubeMX工具和Keil MDK软件,创建一个新的工程,并按照以下步骤进行配置: 1)选择

    2024年02月12日
    浏览(32)
  • Android 电源键事件流程分析

    电源按键流程处理逻辑在 PhoneWindowManager.java 类中的 dispatchUnhandledKey 方法中 从dispatchUnhandledKey方法开始分析 关于电源按键的核心逻辑在interceptFallback方法里的interceptKeyBeforeQueueing方法里 当按下的时候,执行interceptPowerKeyDown方法 1、Android 按电源键亮屏/息屏流程 1、亮屏 1.1、 P

    2024年02月06日
    浏览(49)
  • Android事件分发-基础原理和场景分析

    作者:京东零售 郭旭锋 和其他平台类似,Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个 View 的范围内,这样就不知道哪个 View 来响应这个事件,为了解决这一问题,就出现了事件分发机制

    2023年04月21日
    浏览(41)
  • Android 13.0 开启禁用系统接收短信功能

     在13.0的系统rom定制化中,在一些wifi产品的机器中,对于系统4g部分的功能需要裁剪 比如拨打电话 接听电话 短信功能等这部分 需要禁用系统对应的功能,接下来就来初步分析下系统中接收短信部分功能,然后实现禁用接收短信功能 对于Mms应用程序来讲主要就是在信息数据

    2024年02月22日
    浏览(60)
  • 高级UI之Android事件分发机制原理及源码分析

    在 Android 中, 事件分发机制是一块很重要的知识点, 掌握这个机制能帮你在平时的开发中解决掉很多的 View 事件冲突问题,这个问题也是面试中问的比较多的一个问题了, 本篇就来总结下这个知识点。 Android 中页面上的 View 是以树型结构显示的,View 会重叠在一起,当我们

    2024年02月08日
    浏览(40)
  • systemserver的inputdispatcher直接产生CANCEL事件原理分析-讨厌的android触摸面试题

    上一个blog已经重点讲解了app层面自己产生的Cancel触摸事件,大概产生的原理如下: 上一个blog地址:https://blog.csdn.net/learnframework/article/details/124086882 即可以看出来,在服务端systemserver其实传递的触摸事件依然是move,只是move事件到了app端后,由于app端自己的业务把这个传递的

    2024年02月09日
    浏览(43)
  • SpringBoot实现WebSocket发送接收消息 + Vue实现SocketJs接收发送消息

    1、https://www.mchweb.net/index.php/dev/887.html 2、https://itonline.blog.csdn.net/article/details/81221103?spm=1001.2101.3001.6661.1utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-81221103-blog-121078449.pc_relevant_aadepth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-81221103-blog-12107

    2024年02月05日
    浏览(46)
  • ARM_uart_发送接收字符 and 发送接收字符串

      include/uart4.h   src/yart4.c   main.c  hal_put_char(hal_get_char()+1); hal_put_string( hal_get_string());  please input 到下一行 结果就是    

    2024年02月13日
    浏览(48)
  • Android 11 定制系统全局监听触摸事件接口

    1.定义创建aidl接口(由于需要回调这里优先需要增加一个回调接口 ) frameworksbasecorejavaandroidappIOnTouchListener.aidl package android.app; oneway interface IOnTouchListener {      void onTouchEvent( int action); }   2.新增调用接口 在 base/core/java/android/view/IWindowManager.aidl 修改如下: import android.ap

    2023年04月08日
    浏览(50)
  • 江科大32——USART串口发送&串口发送+接收代码

     1.程序初始化流程 1)开启USART和GPIO时钟。 2)GPIO初始化,把TX配置成复用输出,RX配置成输入。 3)使用结构体配置USART。 如果只需发送功能,直接开启USART就行了。 如果需要发送和接收,需要在开启USART之前加上 ITConfig 和 NVIC 的代码。 初始化完成,只需调用特定函数就能完

    2024年02月12日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包