当前位置: 首页 > news >正文

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.javapublic 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_INPUTif (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>> mConnectionsByTokenGUARDED_BY(mLock);// InputDispatcher.cppResult<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {std::unique_ptr<InputChannel> serverChannel;std::unique_ptr<InputChannel> clientChannel;// 1.通过CPP层的InputChannel创建serverChannel,clientChannelstatus_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);{std::scoped_lock _l(mLock);const sp<IBinder>& token = serverChannel->getConnectionToken();// 2.得到serverChannel的文件描述符 fdint 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,当有事件发生时,回调callbackmLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);}// 5.唤醒mLoopermLooper->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.cppstatus_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.cppstatus_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.cppstatus_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.javafinal class WindowInputEventReceiver extends InputEventReceiver {public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}        @Overridepublic 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 mInputCompatProcessorfinishInputEvent(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.javapublic 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.cppstatic jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject inputChannelObj, jobject messageQueueObj) {// 获取InputChannelstd::shared_ptr<InputChannel> inputChannel =android_view_InputChannel_getInputChannel(env, inputChannelObj);	// 获取messageQueuesp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);// 1. 实例化NativeInputEventReceiversp<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 objectreturn reinterpret_cast<jlong>(receiver.get())}

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

3.2.1 NativeInputEventReceiver实例化

    // android_view_InputEventReceiver.cppNativeInputEventReceiver::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.cppInputConsumer::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.cppstatus_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.cppint 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,inputEventObjif (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.cppstatus_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.cppstatus_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

相关文章:

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

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

Java时间类(五)-- LocalDate()类

目录 引言: 1. LocalDate的概述: 2. LocalDate的常用方法: 引言: (1)Date存在的缺陷: 如果不格式化,打印出的日期可读性差://获取当前时间Date date = new Date();System.out.println("date = " + date); //date = Wed May 03 22:30:24 CST...

用手机号码归属地 API 开发的应用推荐

引言 手机号码归属地 API是一种提供手机号码归属地信息的接口&#xff0c;通过该接口&#xff0c;可以获取手机号码所属的省份、城市、运营商等信息。它可以帮助企业更好地了解客户&#xff0c;为个性化推荐和精准广告投放提供数据支持。作为一种数据服务&#xff0c;手机号码…...

测试从业第 3 年,我看到了终点......

先说明&#xff0c;今天的内容&#xff0c;是写给想成为高级测试开发、自动化测试专家的人看的&#xff0c;因为&#xff0c;它可能颠覆你的认知。 众所周知&#xff0c;如今无论是大厂还是中小厂&#xff0c;自动化测试基本是标配了&#xff0c;毕竟像双11、618 这种活动中庞…...

结巴分词原理分析

结巴分词器工作原理 结巴分词是一款python写成的开源中文分词器&#xff0c;分词过程大致如下&#xff1a; 首先&#xff0c;结巴使用正则表达式将输入文本切割成若干中文块&#xff0c;接着对每个中文块B做处理&#xff0c;将B转成有向无环图(DAG)。DAG是以{key:list[i,j...…...

JavaEE 第三-四周

计算机Z20-第3-4周作业 总分&#xff1a;100分 得分&#xff1a;74.2分 1 . 填空题 简单 5分 在web.xml文件中&#xff0c;<url-pattern>/xxxxServlet</url-pattern>中的第一个‘/’表示__________。 学生答案 当前web应用程序的根目录 2 . 填空题 简…...

Ububtu20.04 无法连接外屏(显卡驱动问题导致)

Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; Ububtu20.04 无法显示第二个屏幕&#xff08;显卡驱动问题&#xff09; 1. 问题描述2. 解决方案 1. 问题描述 一开始我的ububt…...

配置JDK环境变量

文章目录 查看电脑系统下载及安装JavaSE配置系统环境变量测试环境变量配置是否成功。 查看电脑系统 运行输入框中输入&#xff1a;control 下载及安装JavaSE 这个从网上下载就行&#xff0c;jdk-8u141-windows-x64.exe&#xff0c;不提供下载方式了。 主要讲解安装过程&a…...

保护移动设备免受恶意软件侵害优秀方法

几天前&#xff0c;移动恶意软件攻击增加了500%显然&#xff0c;我们大多数人都不知道不能很好地保护我们的手机下面小编揭秘有效保护移动设备免受恶意软件侵害的最佳方法。 1、使用移动反恶意软件 恶意软件很容易感染智能手机和平板电脑&#xff0c;因此在设备上安装可靠的…...

一个人在家怎么赚钱?普通人如何通过网络实现在家就能赚钱

近年来&#xff0c;随着互联网的飞速发展&#xff0c;嗅觉敏锐的人只要使用互联网就可以快乐地赚钱。一般来说&#xff0c;网上赚钱的投资较少&#xff0c;有时有一台能上网的电脑或手机就够了&#xff0c;所以大家有时称其为“无成本或低成本网赚”。今天就分享一个人在家如何…...

ChatGPT诞生的新岗位:提示工程师(Prompt Engineer)

ChatGPT诞生的新岗位&#xff1a;提示工程师&#xff08;Prompt Engineer&#xff09; Prompt 工程师是什么? 是识别人工智能的错误和隐藏功能&#xff0c;以便开发者可以对这些发现进行处理。 如果你正在寻找科技领域最热门的工作&#xff0c;你可以尝试了解如何与AI聊天机…...

机器学习笔记 使用PPOCRLabel标注自己的OCR数据集

一、PPOCRLabel的安装 最简单的方式就是直接pip安装,如下命令。 pip install PPOCRLabel -i https://pypi.douban.com/simple/ 运行的时候,直接激活安装了PPOCRLabel的环境后,输入PPOCRLabel回车即可运行,不过PPOCRLabel依赖PyQt5,所以会要求安装PyQt5,按要求安装或者提前…...

【C++初阶】类和对象(二)

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C初阶】…...

深入探讨Java、Spring和Dubbo的SPI机制

在Java开发领域中&#xff0c;SPI&#xff08;Service Provider Interface&#xff09;是一种用于实现框架扩展的机制。Java本身提供了SPI机制&#xff0c;Spring和Dubbo也都有自己的SPI机制。本文将介绍Java、Spring、Dubbo三者SPI机制的原理和区别。 一、Java SPI机制 Java…...

使用机器人为无线传感器网络提供服务(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 无线传感器网络是一种无线网络&#xff0c;包括大量循环的、自定向的、微小的、低功耗的设备&#xff0c;称为传感器节点&…...

QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样

QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样 [1] QT自制软键盘 最完美、最简单、跟自带虚拟键盘一样一、本自制虚拟键盘特点二、windows打开系统自带软键盘三、让键盘界面保持在最上方、不改变底层界面焦点四、长按按键重复输入键盘内容五、模拟键盘点击事件完成虚拟键盘…...

优思学院|8D和DMAIC两种方法应如何选择?

在现代的商业环境中&#xff0c;客户投诉是一个非常常见的问题。当客户不满意产品或服务时&#xff0c;他们往往会向企业发出投诉。质量管理部门是一个负责处理这些投诉的重要部门&#xff0c;因为它们需要确保产品和服务的质量满足客户的期望。改善方法是质量管理部门用来解决…...

回归预测 | MATLAB实现MLR多元线性回归预测(多指标评价)

回归预测 | MATLAB实现MLR多元线性回归预测(多指标评价) 目录 回归预测 | MATLAB实现MLR多元线性回归预测(多指标评价)预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 回归预测 | MATLAB实现MLR多元线性回归预测(多指标评价) 模型描述 多元线性回归(Multip…...

PHP 二维数组相关函数:二维数组指定key排序,二维数组转一维数组,两个二维数组取差集,对象转数组,判断元素是否在多维数组中

目录 一、二维数组转一维数组 二、对二维数组进行指定key排序 三、二维数组转一维数组 四、两个二维数组取差集 五、对象转数组 六、判断元素是否在多维数组中 PHP 二维数组相关函数&#xff1a;二维数组转一维数组&#xff0c;二维数组指定key排序&#xff0c;两个二维数…...

演出剧院门票售票预约小程序开发制作功能介绍

基于微信小程序的票务预约小程序&#xff0c;广泛适用于演出主办方、剧院、艺术中心、活动中心售票、景区门票售票、儿童游乐园售票、会务签到、展会售票签到、教育培训报名预约、健身预约功能。 多场景售票支持&#xff1a; 售票软件支持多种场景的售票&#xff0c;支持选座、…...

JUC之Java内置锁的核心原理

文章目录 JUC之Java内置锁的核心原理Java对象结构对象头对象体对齐字节 Mark Word的结构信息64位Mark Word的构成 偏向锁偏向锁的设置偏向锁的重偏向偏向锁的撤销偏向锁的膨胀 轻量级锁执行过程轻量级锁的分类普通自旋锁自适应自旋锁 重量级锁偏向锁、轻量级锁与重量级锁的对比…...

【项目经理】论项目经理的自我修养

项目经理的非职权领导力 文章目录 项目经理的非职权领导力一、权利的类型二、构成权利的三要素三、沟通是实施影响力的重要手段3.1 沟通的主要类型3.2 沟通的内容和形式3.3 沟通的主要困难 四、综合沟通协调的技巧4.1 常见的负面反馈4.2 沟通技巧 五、论项目经理的自我修养5.1 …...

知识图谱学习笔记03-知识图谱的作用

语义搜索 知识图谱在语义搜索方面扮演着非常重要的角色。传统的文本搜索引擎基本上是基于关键词匹配的方式进行搜索&#xff0c;这种方式容易受到搜索词语的表述方式和不同语言之间的差异的影响&#xff0c;而无法深入理解用户的意图和查询目的。而知识图谱则提供了一种更加精…...

刚进公司就负责项目,把老弟整蒙了!

刚进公司就负责项目&#xff0c;把老弟整蒙了&#xff01; 大家好&#xff0c;我是鱼皮&#xff0c;先把封面图送给大家&#xff1a; 又快到周末了&#xff0c;今天分享一些轻松的编程经验~ 还记得我学编程的老弟小阿巴么&#xff1f;他目前大二&#xff0c;听说最近刚刚找到…...

【Python基础入门学习】Python高级变量你了解多少?

认识高级变量 1. 列表 list1.1 列表的定义1.2 列表常用操作关键字、函数和方法 1.3 循环遍历1.4 列表嵌套1.5 应用场景 2. 元组 tuple2.1 元组的定义2.2 元组常用操作2.3 应用场景 3. 字典 dictionary3.1 字典的含义3.2 字典常用操作3.3 应用场景 4. 字符串 string4.1 字符串的…...

《LearnUE——基础指南:上篇—3》——GamePlay架构WorldContext,GameInstance,Engine之间的关系

目录 平行世界是真实存在的吗&#xff1f; 1.3.1 引言 1.3.2 世界管理局&#xff08;WorldContext&#xff09; 1.3.3 司法天神&#xff08;GameInstance&#xff09; 1.3.4 上帝&#xff08;Engine&#xff09; 1.4 总结 平行世界是真实存在的吗&#xff1f; 1.3.1 引言 …...

重大问题,Windows11出现重大BUG(开始菜单掉帧,卡顿)

重大问题&#xff0c;Windows11出现重大BUG 这种Windows11操作系统出现BUG已经可以说是非常常见的&#xff0c;但是&#xff0c;今天我将代表所有微软用户&#xff0c;解决一个关于UI设计非常不舒服的功能 关闭多平面覆盖 事情叙述问题 微软社区解决方案自己发现的解决方案解决…...

修改系统语言字体的方法及注意事项

Android修改系统语言字体 随着我们生活品质的提升&#xff0c;现在人们对于手机的依赖越来越高&#xff0c;而且对于手机的功能也有了更高的要求。其中&#xff0c;界面的字体对于我们视觉的体验感受非常重要。而在Android系统中&#xff0c;默认的字体可能并不符合我们的胃口。…...

19.考虑柔性负荷的综合能源系统日前优化调度模型

说明书 MATLAB代码&#xff1a;考虑柔性负荷的综合能源系统日前优化调度模型 关键词&#xff1a;柔性负荷 需求响应 综合需求响应 日前优化调度 综合能源系统 参考文档&#xff1a;《考虑用户侧柔性负荷的社区综合能源系统日前优化调度》参考柔性负荷和基础模型部分&#xf…...

Midjourney关键词分享!附输出AI绘画参考图

Midjourney 关键词是指用于 Midjourney 这个 AI 绘画工具的文本提示&#xff0c;可以影响生成图像的风格、内容、细节等。Midjourney 关键词有一些基本的语法规则和套用公式&#xff0c;也有一些常用的风格词汇和描述词汇&#xff0c;这里我以10张不同风格和类型的美女图为例&a…...

帮朋友做网站不给钱/百度搜索推广多少钱

路由器的基本配置 路由器是网络异种网络互联与多个子网互联的重要设备。简单来说&#xff0c;路由器可以将不同的局域网进行互联和通信。 路由器疑点 相比大家都路由器设备并不陌生&#xff0c;日常生活随处可见。 明明处于三层的路由器设备为何运用会比交换机普遍呢&#xf…...

软件开发工具包sdk/太原seo网站优化

首先看一下epoll的几个函数的介绍。1、epoll_create函数/*** brief 该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间&#xff0c;用来存* 放你想关注的socket fd上是否发生以及发生了什么事件。* param size: size就是你在这个epoll fd上能关注的最大socket fd…...

站群管理系统cms/pc网站优化排名

摘要&#xff1a;随着计算机技术和Internet的飞速发展,人们对web服务器的性能的要求也越来越高,负载均衡集群以其较高的性价比和良好的扩展性在web服务器中得到了广泛的应用。因此如何更好的提高集群服务器的综合处理能力便受到了更多的关注。 集群的负载均衡策略是提高集群整体…...

欧洲大带宽服务器/网站优化方案

字典&#xff08;Dictionary&#xff09; 字典是一种存储多个相同类型的值的容器。每个值&#xff08;value&#xff09;都关联唯一的键&#xff08;key&#xff09;&#xff0c;键作为字典中的这个值数据的标识符。和数组中的数据项不同&#xff0c;字典中的数据项并没有具体顺…...

怎样通过网址浏览自己做的网站/贵阳网站优化公司

移动硬盘格式化后数据怎么恢复?移动硬盘是可移动的大容量存储设备&#xff0c;经常用来存储很多重要的数据文件。但同样使用中遇到的问题也不少&#xff0c;比如说移动硬盘的格式化、误删除、操作失误等。如果遇到不小心把移动硬盘格式化了&#xff0c;所有的数据将会消失&…...

黄冈网站建设费用/网站建站哪家公司好

2011年07月12日16:32 下面介绍在Linux操作系统下安装配置maven和搭建nexus私服。一、安装前的准备下载 jdk http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u26-download-400750.htmljdk-6u26-linux-x64.bin下载maven http://mirrors.geoexpat.com/apache//mav…...