我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Input系统之InputReader概要性实例分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Input系统之InputReader概要性实例分析

InputReader 的创建

从 InputManagerService: 创建与启动 可知,Input 系统的主要功能,主要集中在 native 层,并且Input 系统的 native 层又包含 InputReader, InputClassifer, InputDispatcher 三个子模块。本文来分析 InputReader 从创建到启动的基本流程,为后续分析 InputReader 的每一个功能打好基础。

从 InputManagerService: 创建与启动 可知, InputReader 的创建过程如下

// InputReaderFactory.cpp
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
                                           const sp<InputListenerInterface>& listener) {
    return new InputReader(std::make_unique<EventHub>(), policy, listener);
}

InputReader 依赖 EventHub,因此首先要看下 EventHub 的创建过程

EventHub::EventHub(void)
      : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
        mNextDeviceId(1),
        mControllerNumbers(),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false),
        mNeedToScanDevices(true), // mNeedToScanDevices 初始化为 true,表示需要扫描输入设备
        mPendingEventCount(0),
        mPendingEventIndex(0),
        mPendingINotify(false) {
    ensureProcessCanBlockSuspend();
    // 1. 创建 epoll
    mEpollFd = epoll_create1(EPOLL_CLOEXEC);
    // 2. 初始化 inotify
    mINotifyFd = inotify_init();
    // 监听 /dev/input/ 目录项的创建与删除,其实就是监听输入设备的创建与删除
    mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    // ...
    // 3. epoll 监听 inotify 事件
    // 可读事件,表明有输入设备的创建与删除
    struct epoll_event eventItem = {};
    eventItem.events = EPOLLIN | EPOLLWAKEUP;
    eventItem.data.fd = mINotifyFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    // 4. 创建管道
    int wakeFds[2];
    result = pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    // 设置管道两端为非阻塞
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    // 5. epoll 监听管道读端的事件
    // 可读事件,表明需要唤醒 InputReader 线程,触发条件一般为配置更新
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}

EventHub 创建过程如下

  • 创建 epoll 实例。
  • 初始化 inotify 实例,并用 epoll 监听它的事件。当输入设备添加/删除时,epoll 就会收到 inotify 的可读事件,因此 EventHub 和 InputReader 就可以动态地处理输入设备的添加/删除。
  • 创建管道。
  • epoll 监听管道的读端的事件。当配置更新时,会向管道的写端写入数据,epoll 就会收到管道的可读事件,如果此时 InputReader 线程处于休眠状态,那么 InputReader 将被唤醒来处于配置更新。

epoll, inotify, pipe,它们的作用和使用方式,请读者自行查阅 Unix/Linux 资料。

现在让我们继续看下 InputReader 的创建过程

InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
                         const sp<InputReaderPolicyInterface>& policy,
                         const sp<InputListenerInterface>& listener)
      : mContext(this), // mContext 代表 InputReader 的环境
        mEventHub(eventHub),
        mPolicy(policy),
        mGlobalMetaState(0),
        mLedMetaState(AMETA_NUM_LOCK_ON),
        mGeneration(1),
        mNextInputDeviceId(END_RESERVED_ID),
        mDisableVirtualKeysTimeout(LLONG_MIN),
        mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0) {
    // InputReader 会把加工后的事件添加到 QueuedInputListener 队列中,之后一起分发给 InputClassifier
    mQueuedListener = new QueuedInputListener(listener);
    { // acquire lock
        std::scoped_lock _l(mLock);
        // 刷新配置
        // 其实就是更新 InputReader::mConfig
        refreshConfigurationLocked(0);
        // 更新 InputReader::mGlobalMetaState
        // 与键盘输入设备的meta按键相关
        updateGlobalMetaStateLocked();
    } // release lock
}

InputReader 的构造函数很简单,就是成员变量的初始化。其中需要重点看下 refreshConfigurationLocked(0) 是如何刷新 InputReader 配置

// 注意,此时参数 changes 为 0
void InputReader::refreshConfigurationLocked(uint32_t changes) {
    // 通过 InputReaderPolicyInterface 获取配置,保存到 InputReader::mConfig 中
    mPolicy->getReaderConfiguration(&mConfig);
    // EventHub 保存排除的设备
    mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
    if (!changes) return;
    // ...
}

原来 InputReader::mConfig 代表的就是 InputReader 的配置,并且是通过 InputReaderPolicyInterface mPolicy 获取配置的。

从 InputManagerService: 创建与启动 可知,InputReaderPolicyInterface 接口的实现者是 NativeInputManager ,而 NativeInputManager 是 Input 系统的上层与底层沟通的桥梁,因此 InputReader 必定是通过 NativeInputManager 向上层获取配置

void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
    ATRACE_CALL();
    JNIEnv* env = jniEnv();
    // 1. 通过JNI,向上层 InputManagerService 获取配置,并保存到 outConfig 中
    jint virtualKeyQuietTime = env->CallIntMethod(mServiceObj,
            gServiceClassInfo.getVirtualKeyQuietTimeMillis);
    if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {
        outConfig->virtualKeyQuietTime = milliseconds_to_nanoseconds(virtualKeyQuietTime);
    }
    outConfig->excludedDeviceNames.clear();
    jobjectArray excludedDeviceNames = jobjectArray(env->CallStaticObjectMethod(
            gServiceClassInfo.clazz, gServiceClassInfo.getExcludedDeviceNames));
    if (!checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && excludedDeviceNames) {
        jsize length = env->GetArrayLength(excludedDeviceNames);
        for (jsize i = 0; i < length; i++) {
            std::string deviceName = getStringElementFromJavaArray(env, excludedDeviceNames, i);
            outConfig->excludedDeviceNames.push_back(deviceName);
        }
        env->DeleteLocalRef(excludedDeviceNames);
    }
    // Associations between input ports and display ports
    // The java method packs the information in the following manner:
    // Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
    // Received data: ['inputPort1', '1', 'inputPort2', '2']
    // So we unpack accordingly here.
    outConfig->portAssociations.clear();
    jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
            gServiceClassInfo.getInputPortAssociations));
    if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
        jsize length = env->GetArrayLength(portAssociations);
        for (jsize i = 0; i < length / 2; i++) {
            std::string inputPort = getStringElementFromJavaArray(env, portAssociations, 2 * i);
            std::string displayPortStr =
                    getStringElementFromJavaArray(env, portAssociations, 2 * i + 1);
            uint8_t displayPort;
            // Should already have been validated earlier, but do it here for safety.
            bool success = ParseUint(displayPortStr, &displayPort);
            if (!success) {
                ALOGE("Could not parse entry in port configuration file, received: %s",
                    displayPortStr.c_str());
                continue;
            }
            outConfig->portAssociations.insert({inputPort, displayPort});
        }
        env->DeleteLocalRef(portAssociations);
    }
    outConfig->uniqueIdAssociations.clear();
    jobjectArray uniqueIdAssociations = jobjectArray(
            env->CallObjectMethod(mServiceObj, gServiceClassInfo.getInputUniqueIdAssociations));
    if (!checkAndClearExceptionFromCallback(env, "getInputUniqueIdAssociations") &&
        uniqueIdAssociations) {
        jsize length = env->GetArrayLength(uniqueIdAssociations);
        for (jsize i = 0; i < length / 2; i++) {
            std::string inputDeviceUniqueId =
                    getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i);
            std::string displayUniqueId =
                    getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i + 1);
            outConfig->uniqueIdAssociations.insert({inputDeviceUniqueId, displayUniqueId});
        }
        env->DeleteLocalRef(uniqueIdAssociations);
    }
    jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
            gServiceClassInfo.getHoverTapTimeout);
    if (!checkAndClearExceptionFromCallback(env, "getHoverTapTimeout")) {
        jint doubleTapTimeout = env->CallIntMethod(mServiceObj,
                gServiceClassInfo.getDoubleTapTimeout);
        if (!checkAndClearExceptionFromCallback(env, "getDoubleTapTimeout")) {
            jint longPressTimeout = env->CallIntMethod(mServiceObj,
                    gServiceClassInfo.getLongPressTimeout);
            if (!checkAndClearExceptionFromCallback(env, "getLongPressTimeout")) {
                outConfig->pointerGestureTapInterval = milliseconds_to_nanoseconds(hoverTapTimeout);
                // We must ensure that the tap-drag interval is significantly shorter than
                // the long-press timeout because the tap is held down for the entire duration
                // of the double-tap timeout.
                jint tapDragInterval = max(min(longPressTimeout - 100,
                        doubleTapTimeout), hoverTapTimeout);
                outConfig->pointerGestureTapDragInterval =
                        milliseconds_to_nanoseconds(tapDragInterval);
            }
        }
    }
    jint hoverTapSlop = env->CallIntMethod(mServiceObj,
            gServiceClassInfo.getHoverTapSlop);
    if (!checkAndClearExceptionFromCallback(env, "getHoverTapSlop")) {
        outConfig->pointerGestureTapSlop = hoverTapSlop;
    }
    // 2. 从 NativeInputManager::mLocked 更新配置,保存到 outConfig 中
    // NativeInputManager::mLocked 的数据是上层经由 InputManagerService 传入的
    { // acquire lock
        AutoMutex _l(mLock);
        outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
                * POINTER_SPEED_EXPONENT);
        outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
        outConfig->showTouches = mLocked.showTouches;
        outConfig->pointerCapture = mLocked.pointerCapture;
        outConfig->setDisplayViewports(mLocked.viewports);
        outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
        outConfig->disabledDevices = mLocked.disabledInputDevices;
    } // release lock
}

从整体看,获取 InputReader 配置的方式有两种

  • 通过 JNI 向上层的 InputManagerService 获取配置。
  • NativeInputManager::mLocked 获取配置。

从 InputManagerService: 创建与启动 可知,NativeInputManager::mLocked 是在 NativeInputManager 的构造函数中进行初始化的,但是它并不是不变的,而是上层经由 InputManagerService 进行操控的。

例如,mLocked.showTouches 对应开发者模式下的 Show taps 功能,InputManagerService 会监听这个开关的状态,相应地改变 mLocked.showTouches,并且会通知 InputReader 配置改变了,InputReader 在处理配置改变的过程时,会重新获取 mLocked.showTouches 这个配置。

一部分 的配置是可以通过 adb shell dumpsys input 命令进行查看的

Input Manager State:
  Interactive: true
  System UI Lights Out: false
  Pointer Speed: 0
  Pointer Gestures Enabled: true
  Show Touches: false
  Pointer Capture: Disabled, seq=0

而另外一部分配置,由于会对输入设备进行配置,因此可以从 dump 出的输入设备的信息中查看。

InputReader 的运行

从 InputManagerService: 创建与启动 可知,InputReader 通过线程,循环调用 InputReader::loopOnce() 执行任务

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        std::scoped_lock _l(mLock);
        oldGeneration = mGeneration;
        timeoutMillis = -1;
        // 1. 如果配置有改变,那么就刷新配置
        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            // 刷新配置
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock
    // 2. 从 EventHub 获取事件
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    { // acquire lock
        std::scoped_lock _l(mLock);
        mReaderIsAliveCondition.notify_all();
        // 3. 处理事件
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
        if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (now >= mNextTimeout) {
                mNextTimeout = LLONG_MAX;
                timeoutExpiredLocked(now);
            }
        }
        // 4. 处理输入设备改变
        // 4.1 输入设备改变,重新获取输入设备信息
        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            inputDevices = getInputDevicesLocked();
        }
    } // release lock
    // 4.2 通知监听者,输入设备改变了
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }
    // 5. 刷新队列中缓存的事件
    // 其实就是把事件分发给 InputClassifier
    mQueuedListener->flush();
}

InputReader 所做的事情如下

  • 如果配置改变了,那么就更新配置。
  • 从 EventHub 获取事件,并处理获取到的事件。在处理事件的过程中,InputReader 会对事件进行加工,然后保存到 QueuedInputListener 缓存队列中。
  • 如果设备发生改变,那么重新获取新的设备信息,并通知监听者。
  • QueuedInputListener 刷新缓存的事件,其实就是把 InputReader 加工后的事件分发给 InputClassifer。

EventHub 提供事件

InputReader 的本质就是处理从 EventHub 获取的事件,然后分发给下一环。因为我们必须了解 EventHub::getEvents() 是如何为 InputReader 提供事件的

// EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);
    std::scoped_lock _l(mLock);
    struct input_event readBuffer[bufferSize];
    RawEvent* event = buffer;
    size_t capacity = bufferSize;
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        // Reopen input devices if needed.
        if (mNeedToReopenDevices) {
            // ...
        }
        // Report any devices that had last been added/removed.
        for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
            // ...
        }
        // 扫描输入设备
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
        // 为扫描后打开的每一个输入设备,填充一个类型为 DEVICE_ADDED 的事件
        while (!mOpeningDevices.empty()) {
            std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
            mOpeningDevices.pop_back();
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            event->type = DEVICE_ADDED;
            event += 1;
            // Try to find a matching video device by comparing device names
            for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end();
                 it++) {
                // ...
            }
            // 每次填充完事件,就把设备 Device 保存到 mDevices 中
            auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
            if (!inserted) {
                ALOGW("Device id %d exists, replaced.", device->id);
            }
            // 表明你需要发送设备扫描完成事件
            mNeedToSendFinishedDeviceScan = true;
            if (--capacity == 0) {
                break;
            }
        }
        // 填充设备扫描完成事件
        if (mNeedToSendFinishedDeviceScan) {
            mNeedToSendFinishedDeviceScan = false;
            event->when = now;
            event->type = FINISHED_DEVICE_SCAN;
            event += 1;
            if (--capacity == 0) {
                break;
            }
        }
        // Grab the next input event.
        bool deviceChanged = false;
        // 处理 epoll 事件
        while (mPendingEventIndex < mPendingEventCount) {
            // 处理 inotify 事件,表明输入设备新增或者删除
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
            if (eventItem.data.fd == mINotifyFd) {
                if (eventItem.events & EPOLLIN) {
                    mPendingINotify = true;
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
                }
                continue;
            }
            // 处理管道事件,这是用来唤醒 InputReader 线程
            if (eventItem.data.fd == mWakeReadPipeFd) {
                if (eventItem.events & EPOLLIN) {
                    ALOGV("awoken after wake()");
                    awoken = true;
                    char wakeReadBuffer[16];
                    ssize_t nRead;
                    do {
                        nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer));
                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer));
                } else {
                    ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
                          eventItem.events);
                }
                continue;
            }
            // 接下来是处理设备的输入事件
            Device* device = getDeviceByFdLocked(eventItem.data.fd);
            if (device == nullptr) {
                continue;
            }
            if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) {
                // ...
            }
            if (eventItem.events & EPOLLIN) {
                // 读取输入事件以及数量
                int32_t readSize =
                        read(device->fd, readBuffer, sizeof(struct input_event) * capacity);
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                    // Device was removed before INotify noticed.
                    ALOGW("could not get event, removed? (fd: %d size: %" PRId32
                          " bufferSize: %zu capacity: %zu errno: %d)\n",
                          device->fd, readSize, bufferSize, capacity, errno);
                    deviceChanged = true;
                    closeDeviceLocked(*device);
                } else if (readSize < 0) {
                    if (errno != EAGAIN && errno != EINTR) {
                        ALOGW("could not get event (errno=%d)", errno);
                    }
                } else if ((readSize % sizeof(struct input_event)) != 0) {
                    ALOGE("could not get event (wrong size: %d)", readSize);
                } else {
                    int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
                    // 为每一个输入事件,填充一个事件
                    size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];
                        event->when = processEventTimestamp(iev);
                        event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);
                        event->deviceId = deviceId;
                        event->type = iev.type;
                        event->code = iev.code;
                        event->value = iev.value;
                        event += 1;
                        capacity -= 1;
                    }
                    if (capacity == 0) {
                        // The result buffer is full.  Reset the pending event index
                        // so we will try to read the device again on the next iteration.
                        mPendingEventIndex -= 1;
                        break;
                    }
                }
            } else if (eventItem.events & EPOLLHUP) {
                ALOGI("Removing device %s due to epoll hang-up event.",
                      device->identifier.name.c_str());
                deviceChanged = true;
                closeDeviceLocked(*device);
            } else {
                ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events,
                      device->identifier.name.c_str());
            }
        }
        // 处理设备改变
        // mPendingEventIndex >= mPendingEventCount 表示处理完所有的输入事件后,再处理设备的改变
        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
            mPendingINotify = false;
            readNotifyLocked();
            deviceChanged = true;
        }
        // 设备发生改变,那么跳过当前循环,在下一个循环的开头处理设备改变
        if (deviceChanged) {
            continue;
        }
        // 如果有事件,或者被唤醒,那么终止循环,接下来 InputReader 会处理事件或者更新配置
        if (event != buffer || awoken) {
            break;
        }
        mPendingEventIndex = 0;
        mLock.unlock(); // release lock before poll
        // 此时没有事件,并且也没有被唤醒,那么超时等待 epoll 事件
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        mLock.lock(); // reacquire lock after poll
        if (pollResult == 0) {
            // 处理超时...
        }
        if (pollResult < 0) {
            // 处理错误...
        } else {
            // 保存待处理事件的数量
            mPendingEventCount = size_t(pollResult);
        }
    }
    // 返回事件的数量
    return event - buffer;
}

EventHub::getEvent() 提供事件的过程很长,但是现在我们不必去了解所有的细节,我们要有从整体看局部的眼光。EventHub 其实只生成了两类事件

  • 设备的添加/删除事件。这种事件不是通过操作设备而产生的,系统称之为合成事件。
  • 输入事件。这种事件是通过操作设备产生的,例如手指在触摸屏上滑动,系统称之为元输入事件。

看来我们得分两部分来分析这两类事件的生成以及处理过程,因此下一篇文章,我们分析合成事件的生成以及处理过程。

以上就是Input系统之InputReader概要性实例分析的详细内容,更多关于Input系统InputReader概要性的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Input系统之InputReader概要性实例分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Input系统之InputReader概要性实例分析

这篇文章主要介绍为大家了Input系统之InputReader概要性实例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Linux设备模型之input子系统的示例分析

这篇文章主要介绍Linux设备模型之input子系统的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!本节重点: 输入子系统的框架结构 各层对应内核中的文件位置
2023-06-13

Linuxsar命令详解之分析系统性能案例

sar 命令很强大,是分析系统性能的重要工具之一,通过该命令可以全面地获取系统的 CPU、运行队列、磁盘读写(I/O)、分区(交换区)、内存、CPU 中断和网络等性能数据。 sar 命令的基本格式如下:[root@localhost ~]#
2022-05-27

Linux操作系统实时性学习示例分析

本篇文章为大家展示了Linux操作系统实时性学习示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、实时系统基本概念什么是实时系统,POSIX作了这样的定义:指能够在限定的响应时间内提供所需水
2023-06-16

Vue实例的深入分析:透视其 reactivity 系统和响应性

Vue 实例的深入分析:揭秘其响应性系统的秘密
Vue实例的深入分析:透视其 reactivity 系统和响应性
2024-02-18

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录