Android10 (.kl)按键布局文件的解析过程分析_kl文件解析_永暮十三的博客-程序员宝宝

技术标签: c++  Android input系统  android  linux  

.kl文件

简述

  kl文件也就是keylayout文件,它的作用是将Linux scancode转换为Android keycode。scancode就是硬件直接扫描到的数字,而这些数字会通过这个kl文件对应到字符串,也就是keycode。
  设备可以拥有自己专属的kl文件,命名规则和idc文件一样,这里就不重复说了。另外系统提供了一个特殊的内置常规按键布局文件,名为 Generic.kl。当找不到专属的kl时候就会用Generic.kl

示例

  下面示例是Generic.kl中一些键值对类型。当然设备的专属kl文件并不需要包含下方的所有类型,只需要包含会用到的就可以了

/odm/usr/keylayout/Generic.kl
/vendor/usr/keylayout/Generic.kl
/system/usr/keylayout/Generic.kl
/data/system/devices/keylayout/Generic.kl
# 键盘
key 1     ESCAPE
key 2     1
key 3     2
key 12    MINUS
key 13    EQUALS
key 14    DEL

# 系统控件
key 114   VOLUME_DOWN
key 115   VOLUME_UP
key 116   POWER

#电容式按钮
key 139    MENU           VIRTUAL
key 172    HOME           VIRTUAL
key 158    BACK           VIRTUAL
key 217    SEARCH         VIRTUAL

#耳机插孔媒体控件
key 163   MEDIA_NEXT
key 165   MEDIA_PREVIOUS
key 226   HEADSETHOOK

#操纵杆
key 304   BUTTON_A
key 305   BUTTON_B
key 307   BUTTON_X
key 308   BUTTON_Y

# Keys defined by HID usages
key usage 0x0c006F BRIGHTNESS_UP
key usage 0x0c0070 BRIGHTNESS_DOWN

EventHub::openDeviceLocked

  这个过程和加载idc一样也是从openDeviceLocked开始的,调用loadKeyMapLocked() 加载Kl文件

status_t EventHub::openDeviceLocked(const char* devicePath) {
    
    char buffer[80];
   
    // devicePath = /dev/input/eventx
    int fd = open(devicePath, O_RDWR | O_CLOEXEC | O_NONBLOCK);
    InputDeviceIdentifier identifier;
    ...

    // Load the key map.
    // We need to do this for joysticks too because the key layout may specify axes.
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
    
        // Load the keymap for the device.
        keyMapStatus = loadKeyMapLocked(device);
    }
    ...
    return OK;
}

  loadKeyMapLocked就直接调用keyMap.load了

status_t EventHub::loadKeyMapLocked(Device* device) {
    
    return device->keyMap.load(device->identifier, device->configuration);
}

KeyMap::load

  还记得我们之前解析idc文件的时候有去解析"keyboard.layout"和"keyboard.characterMap"这两项吗,在这个函数开头我们就是判断在该设备的idc文件中有没有指定kl和kcm文件,如果有的话我们就用idc文件中指定的,如果没有我们再通过probeKeyMap() 去各个目录下查找
  查找的话我们会先通过设备标识符去找它专属的kl文件,找不到就去找Generic,虽然有个最后的选择虚拟键盘映射,不过并不会用到它,因为Generic.kl总是存在的

\frameworks\native\libs\input\Keyboard.cpp
status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
        const PropertyMap* deviceConfiguration) {
    
    // Use the configured key layout if available.
    if (deviceConfiguration) {
    
        String8 keyLayoutName;
        if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
                keyLayoutName)) {
    
            status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName.c_str());
            if (status == NAME_NOT_FOUND) {
    
                ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
                        "it was not found.",
                        deviceIdenfifier.name.c_str(), keyLayoutName.string());
            }
        }

        String8 keyCharacterMapName;
        if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
                keyCharacterMapName)) {
    
            status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName.c_str());
            if (status == NAME_NOT_FOUND) {
    
                ALOGE("Configuration for keyboard device '%s' requested keyboard character "
                        "map '%s' but it was not found.",
                        deviceIdenfifier.name.c_str(), keyLayoutName.string());
            }
        }

        if (isComplete()) {
    
            return OK;
        }
    }

    // Try searching by device identifier.
    if (probeKeyMap(deviceIdenfifier, "")) {
    
        return OK;
    }

    // Fall back on the Generic key map.
    // TODO Apply some additional heuristics here to figure out what kind of
    //      generic key map to use (US English, etc.) for typical external keyboards.
    if (probeKeyMap(deviceIdenfifier, "Generic")) {
    
        return OK;
    }

    // Try the Virtual key map as a last resort.
    if (probeKeyMap(deviceIdenfifier, "Virtual")) {
    
        return OK;
    }

    // Give up!
    ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
            deviceIdenfifier.name.c_str());
    return NAME_NOT_FOUND;
}

KeyMap::probeKeyMap

  接下来看一下probeKeyMap() 的实现,它会分别去加载kl和kcm文件

bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
        const std::string& keyMapName) {
    
    if (!haveKeyLayout()) {
    
        loadKeyLayout(deviceIdentifier, keyMapName);
    }
    if (!haveKeyCharacterMap()) {
    
        loadKeyCharacterMap(deviceIdentifier, keyMapName);
    }
    return isComplete();
}

  先来看下isComplete函数,kl文件和kcm文件都有了才返回true,看load函数,当isComplete返回true,就直接return了,因为kl 和 kcm文件都找到了。

inline bool isComplete() const {
    
        return haveKeyLayout() && haveKeyCharacterMap();
}

  这里我们只看kl文件的加载过程,kcm类似

status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
        const std::string& name) {
    
    std::string path(getPath(deviceIdentifier, name,
            INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
    if (path.empty()) {
    
        return NAME_NOT_FOUND;
    }

    status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
    if (status) {
    
        return status;
    }

    keyLayoutFile = path;
    return OK;
}

KeyMap::getPath

  getPath()就是调用getInputDeviceConfigurationFilePathByDeviceIdentifier()或getInputDeviceConfigurationFilePathByName()去获取路径

std::string KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
        const std::string& name, InputDeviceConfigurationFileType type) {
    
    return name.empty()
            ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
            : getInputDeviceConfigurationFilePathByName(name, type);
}

getInputDeviceConfigurationFilePathByDeviceIdentifier

  kl文件命名有三种方式

Vendor_xxxx_Product_xxxx_Version_xxxx.kl
Vendor_xxxx_Product_xxxx.kl
device-name.kl

  xxxx分别对应的VID、PID和版本号,第一种命名方式不常用

注意:设备名称中除“0-9”、“a-z”、“A-Z”、“-”或“_”之外的所有字符将替换为“_”

  getInputDeviceConfigurationFilePathByDeviceIdentifier中有三个分支是按照三种文件名分别去寻找kl文件,找到了返回配置文件的路径,找不到返回空的String

\frameworks\native\libs\input\InputDevice.cpp
std::string getInputDeviceConfigurationFilePathByDeviceIdentifier(
        const InputDeviceIdentifier& deviceIdentifier,
        InputDeviceConfigurationFileType type) {
    
    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
    
        if (deviceIdentifier.version != 0) {
    
            // Try vendor product version.
            std::string versionPath = getInputDeviceConfigurationFilePathByName(
                    StringPrintf("Vendor_%04x_Product_%04x_Version_%04x",
                            deviceIdentifier.vendor, deviceIdentifier.product,
                            deviceIdentifier.version),
                    type);
            if (!versionPath.empty()) {
    
                return versionPath;
            }
        }

        // Try vendor product.
        std::string productPath = getInputDeviceConfigurationFilePathByName(
                StringPrintf("Vendor_%04x_Product_%04x",
                        deviceIdentifier.vendor, deviceIdentifier.product),
                type);
        if (!productPath.empty()) {
    
            return productPath;
        }
    }

    // Try device name.
    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName(), type);
}

getInputDeviceConfigurationFilePathByName

  传入的参数是要找的文件名和要找的文件类型

std::string getInputDeviceConfigurationFilePathByName(
        const std::string& name, InputDeviceConfigurationFileType type) {
    
    // Search system repository.
    std::string path;

    // Treblized input device config files will be located /odm/usr or /vendor/usr.
    const char *rootsForPartition[] {
    "/odm", "/vendor", getenv("ANDROID_ROOT")};
    for (size_t i = 0; i < size(rootsForPartition); i++) {
    
        if (rootsForPartition[i] == nullptr) {
    
            continue;
        }
        path = rootsForPartition[i];
        path += "/usr/";
        appendInputDeviceConfigurationFileRelativePath(path, name, type);

        if (!access(path.c_str(), R_OK)) {
    
            return path;
        }
    }

    // Search user repository.
    // TODO Should only look here if not in safe mode.
    path = "";
    char *androidData = getenv("ANDROID_DATA");
    if (androidData != nullptr) {
    
        path += androidData;
    }
    path += "/system/devices/";
    appendInputDeviceConfigurationFileRelativePath(path, name, type);

    if (!access(path.c_str(), R_OK)) {
    
        return path;
    }

    // Not found.
    return "";
}

appendInputDeviceConfigurationFileRelativePath

  用传进来的名字和路径加入kl文件目录和后缀,组成完整的文件名
  然后在getInputDeviceConfigurationFilePathByName中判断该路径下是否存在这个kl文件
有返回路径,没有返回空string

static void appendInputDeviceConfigurationFileRelativePath(std::string& path,
        const std::string& name, InputDeviceConfigurationFileType type) {
    
    path += CONFIGURATION_FILE_DIR[type];
    path += name;
    path += CONFIGURATION_FILE_EXTENSION[type];
}
static const char* CONFIGURATION_FILE_DIR[] = {
    
        "idc/",
        "keylayout/",
        "keychars/",
};

static const char* CONFIGURATION_FILE_EXTENSION[] = {
    
        ".idc",
        ".kl",
        ".kcm",
};

  到这里我们已经完成了寻找kl文件的支线任务,回到接下来我们回到loadConfigurationLocked()中,找到kl就返回路径和文件名并调用 KeyLayoutMap::load()

KeyLayoutMap::load

  Tokenizer::open将filename对应的的fd映射到内存中,初始化了一个Tokenizer。若成功则new一个KeyLayoutMap,以该KeyLayoutMap和Tokenizer为参数构造一个Parser对kl文件进行解析。
  KeyLayoutMap和PropertyMap的结构大致相同,都拥有嵌套类Parser。但是Parser的parse解析方法不同。

\frameworks\native\libs\input\KeyLayoutMap.cpp
status_t KeyLayoutMap::load(const std::string& filename, sp<KeyLayoutMap>* outMap) {
    
    outMap->clear();

    Tokenizer* tokenizer;
    status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
    if (status) {
    
        ALOGE("Error %d opening key layout map file %s.", status, filename.c_str());
    } else {
    
        sp<KeyLayoutMap> map = new KeyLayoutMap();
        if (!map.get()) {
    
            ALOGE("Error allocating key layout map.");
            status = NO_MEMORY;
        } else {
    
            Parser parser(map.get(), tokenizer);
            status = parser.parse();
            if (!status) {
    
                *outMap = map;
            }
        }
        delete tokenizer;
    }
    return status;
}

KeyLayoutMap::Parser::parse

  跳过空白行和注释行,如果第一个Token为“key”,将Tokenizer内置指针移到下一个Token起始处,调用parseKey进行解析

status_t KeyLayoutMap::Parser::parse() {
    
    while (!mTokenizer->isEof()) {
    
 		// 跳过空格
        mTokenizer->skipDelimiters(WHITESPACE);
		// 跳过注释行
        if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
    
            String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
            if (keywordToken == "key") {
    
                mTokenizer->skipDelimiters(WHITESPACE);
                status_t status = parseKey();
                if (status) return status;
            } else if (keywordToken == "axis") {
    
                mTokenizer->skipDelimiters(WHITESPACE);
                status_t status = parseAxis();
                if (status) return status;
            } else if (keywordToken == "led") {
    
                mTokenizer->skipDelimiters(WHITESPACE);
                status_t status = parseLed();
                if (status) return status;
            } else {
    
                ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
                        keywordToken.string());
                return BAD_VALUE;
            }

            mTokenizer->skipDelimiters(WHITESPACE);
            if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
    
                ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
                        mTokenizer->getLocation().string(),
                        mTokenizer->peekRemainderOfLine().string());
                return BAD_VALUE;
            }
        }

        mTokenizer->nextLine();
    }
    return NO_ERROR;
}

KeyLayoutMap::Parser::parseKey

  parseKey函数首先会继续通过nextToken函数获取按键scancode,对于scancode为"usage"的情况修改mapUsage 为true,指针指向下一个Token起始处,如下例子,codeToken就等于0x0c006F

key usage 0x0c006F BRIGHTNESS_UP
key usage 0x0c0070 BRIGHTNESS_DOWN

  对于非"usage"的情况,就会直接拿到scan code通过strtol函数进行转换,接着获取按键名称keyCodeToken(如POWER),通过getKeyCodeByLabel() 将Linux的扫描码转为Android的键盘码
  获取特殊功能按键的flag值,最后,将一个key进行键码值,flags值的初始化,以code-key的形式添加到mKeysByUsageCode或mKeysByScanCode中。

status_t KeyLayoutMap::Parser::parseKey() {
    
	//获取该行中第二个空格,tab键或者回车和第一个空格,tab键或者回车之间的所有字符,
    //例:key 116   POWER中,实际作用就是获取scan code为116
    String8 codeToken = mTokenizer->nextToken(WHITESPACE);
    bool mapUsage = false;
    //scan code为usage的情况
    if (codeToken == "usage") {
    // Keys defined by HID usages
        mapUsage = true;
        mTokenizer->skipDelimiters(WHITESPACE);
        // 下一个Token
        // key usage 0x0c006F BRIGHTNESS_UP中的0x0c006F
        codeToken = mTokenizer->nextToken(WHITESPACE);
    }

    char* end;
    // 字符串转十进制数,code为扫描码
    int32_t code = int32_t(strtol(codeToken.string(), &end, 0));//’0’表示十进制
    // UsageCode or ScanCode
    KeyedVector<int32_t, Key>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;

    mTokenizer->skipDelimiters(WHITESPACE);
    //获取key 116   POWER中的POWER
    String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
    //scancdoe转keycode
    int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());

    uint32_t flags = 0;
    for (;;) {
    
        mTokenizer->skipDelimiters(WHITESPACE);
        if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;

        String8 flagToken = mTokenizer->nextToken(WHITESPACE);
        //对于特殊功能按键,获取flag
        uint32_t flag = getKeyFlagByLabel(flagToken.string());

        flags |= flag;
    }
	//构造Key
    Key key;
    key.keyCode = keyCode;
    key.flags = flags;
    map.add(code, key);
    return NO_ERROR;
}
struct Key {
    
        int32_t keyCode;
        uint32_t flags;
    };

getKeyCodeByLabel

  这个函数最大的作用就是通过InputEventLabel.h的getKeyCodeByLabel函数完成scancode到keycode的映射关系。

  另外两个解析函数parseAxis和parseLed的规则和parseKey类似,仅仅是其函数内部调用InputEventLabel.h的函数不一样,如parseAxis内部调用getAxisByLabel,parseLed内部调用getLedByLabel,这两个函数和getKeyCodeByLabel实现也基本一致,也只有用到的宏不同,这里就不去看了。

\frameworks\native\include\input\InputEventLabels.h
static inline int32_t getKeyCodeByLabel(const char* label) {
    
    return int32_t(lookupValueByLabel(label, KEYCODES));
}

  继续调用lookupValueByLabel函数,传了一个重要参数KEYCODES,KEYCODES是一个InputEventLabel结构体数组

struct InputEventLabel {
    
    const char *literal; // 字符串
    int value;// keycode键值
};

static const InputEventLabel KEYCODES[] = {
    
	DEFINE_KEYCODE(UNKNOWN),
    DEFINE_KEYCODE(SOFT_LEFT),
	... 
    DEFINE_KEYCODE(NUM),
    DEFINE_KEYCODE(HEADSETHOOK),
    DEFINE_KEYCODE(FOCUS),   // *Camera* focus
    DEFINE_KEYCODE(PLUS),
    DEFINE_KEYCODE(MENU),
}

// 宏定义如下:
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }

  通过宏DEFINE_KEYCODE将kl文件中的按键名称生成对应的安卓键值,例如POWER通过DEFINE_KEYCODE就可以得到{“POWER”,AKEYCODE_POWER}的对应关系,"POWER"是按键名称,其按键值为AKEYCODE_POWER

lookupValueByLabel

  函数会遍历KEYCODES数组,通过C库函数strcmp查找literal是否包含在KEYCODES数组,返回值是InputEventLabel结构体的value,即根据"POWER"则返回AKEYCODE_POWER

static int lookupValueByLabel(const char* literal, const InputEventLabel *list) {
    
    while (list->literal) {
    
        if (strcmp(literal, list->literal) == 0) {
    
            return list->value;
        }
        list++;
    }
    return list->value;
}

  AKEYCODE_POWER的具体数值定义在keycodes.h中

\frameworks\native\include\android\keycodes.h
/**
 * Key codes.
 */
enum {
    
    AKEYCODE_UNKNOWN         = 0,
    AKEYCODE_SOFT_LEFT       = 1,
    ... 
    /** Power key. */
    AKEYCODE_POWER           = 26,
    ...
}

  我们可以看到这里面定义了很多AKEYCODE_XX的枚举值,AKEYCODE_POWER对应的数值就是26
  最终getKeyCodeByLabel函数就会根据按键名称,返回对应的数值,这即是scancode和keycode的一一映射关系。

getKeyFlagByLabel

另外还有一些按键是这种格式:

key 476   F11               FUNCTION
key 477   F12               FUNCTION
key 478   1                 FUNCTION

  就会继续通过nextToken获取flagToken等于FUNCTION,再调用getKeyFlagByLabel函数获取flag,这个函数和前面getKeyCodeByLabel一样的作用,它会从FLAGS数组中寻找:

static inline uint32_t getKeyFlagByLabel(const char* label) {
    
    return uint32_t(lookupValueByLabel(label, FLAGS));
}
static const InputEventLabel FLAGS[] = {
    DEFINE_FLAG(VIRTUAL),
                                        DEFINE_FLAG(FUNCTION),
                                        DEFINE_FLAG(GESTURE),
                                        DEFINE_FLAG(WAKE),

                                        {
    nullptr, 0}};
#define DEFINE_FLAG(flag) { #flag, POLICY_FLAG_##flag }

  拿到FUNCTION展开宏DEFINE_FLAG得到POLICY_FLAG_FUNCTION,POLICY_FLAG_FUNCTION的值定义在Input.h,指示这是一个特殊功能的按键。

enum {
    
    ...
    // Indicates that the key is the special function modifier.
    POLICY_FLAG_FUNCTION = 0x00000004,
    ...
    }

到这里解析kl文件的过程就分析完啦,接下来我们看一下解析出来的键码值在哪里被应用吧

键码值的应用

  当kernel上报的输入事件是按键类型时,我们会去检查上报的keycode在kl中是否有被定义,再通过我们之前生成的scanCode和Android键盘码的对应关系去将Linux上报的scanCode转化为Android的键盘码
  我们从输入事件中获取linux的扫描码是在KeyboardInputMapper::process中,这个函数前面的调用流程如下,有兴趣可以看看
  InputReader::loopOnce -> InputReader::processEventsLocked ->
InputReader::processEventsForDeviceLocked -> InputDevice::process -> (mapper->process) -> KeyboardInputMapper::process

KeyboardInputMapper::process

  如果输入事件类型是EV_KEY,就去获取Linux的scanCode

\frameworks\native\services\inputflinger\InputReader.cpp
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    
    switch (rawEvent->type) {
    
    case EV_KEY: {
    
        int32_t scanCode = rawEvent->code;
        int32_t usageCode = mCurrentHidUsage;
        mCurrentHidUsage = 0;

        if (isKeyboardOrGamepadKey(scanCode)) {
    
            processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
        }
        break;
    }
    ...
}

  然后调用isKeyboardOrGamepadKey来判断键盘扫描码是否正确,如果正确则调用processKey来进一步处理

bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
    
    return scanCode < BTN_MOUSE
        || scanCode >= KEY_OK //352 
        || (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) // 272
        || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
}

KeyboardInputMapper::processKey

  调用EventHub的mapKey将扫描码转为键盘码,这个函数省略的部分是key事件的处理过程,主要是根据我们转换好的键盘码、newMetaState、按下的时间对事件进行处理,处理好通知Listener,将事件交给Dispatch线程,这里就不详细说明了

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
        int32_t usageCode) {
    
    int32_t keyCode;
    int32_t keyMetaState;
    uint32_t policyFlags;
	// 扫描码转为键盘码
    if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
                              &keyCode, &keyMetaState, &policyFlags)) {
    
        keyCode = AKEYCODE_UNKNOWN;
        keyMetaState = mMetaState;
        policyFlags = 0;
    }
	...
    NotifyKeyArgs args(mContext->getNextSequenceNum(), when, getDeviceId(), mSource,
            getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    getListener()->notifyKey(&args);
}

参考资料

Touch—load kl文件的过程
Android加载按键文件流程
Android6.0 按键kl文件加载过程分析
AndroidR Input子系统(5)解析“.kl“文件

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44904296/article/details/112566710

智能推荐

计算机二级C语言程序填空题+答案解析+源代码(一)_周偏偏偏的博客-程序员宝宝

题目1.使用vc++2010打开考生文件夹下的blank1中的解决方案,此解决方案中包括一个源文件blank1.c。程序通过定义学生结构体变量,存储了学生的学号,姓名,和三门课的成绩。所有学生数据都以二进制方式输入到文件中。函数fun的功能是重写filename所指文件中最后一个学生的数据,即用新的学生数据覆盖原来学生的数据,其他学生数据不变。请在程序下划线处填入正确的答案并把下划线删掉,使得程...

torch.autograd.grad()函数使用_学习要有仪式感呦的博客-程序员宝宝

torch.autograd.grad()函数使用import torch# x = torch.FloatTensor([[0,1,2,3],[1,2,3,4],[2,3,4,5]]).requires_grad_(True)# print(x)x = torch.tensor([[0.,1.,2.,3.],[1.,2.,3.,4.],[2.,3.,4.,5.]]).requires_grad_(True)# print(x)'''tensor([[0., 1., 2., 3.],

智慧煤矿理论篇2-煤矿5G与WiFi6_星空你好的博客-程序员宝宝

煤矿智能化与矿用5G本文针对煤矿井下电气防爆、无线传输衰减大等特点,分析了矿用5G技术和适用范围:矿用5G宜采用本质安全型防爆;用于控制的矿用5G应具有较强的抗干扰能力;采煤工作面和掘进工作面地面远程控制宜选用矿用5G;煤矿井下车辆无人驾驶地面远程控制宜选用矿用5G;没有针对矿井移动通信特点研发的矿用5G性价比低于矿用WiFi移动通信系统;严禁用矿用5G移动通信系统替代矿用有线调度通信系统;没有针对煤矿安全监控特点研发的矿用5G不能替代煤矿安全监控系统;没有针对矿井动目标精确定位特点研发的

Eclipse配置Hadoop开发环境详细步骤+WordCount示例_雷蒙侠的博客-程序员宝宝

说明:Hadoop集群已经搭建完毕,集群上使用的Hadoop-2.5.0。目的:在window10系统上利用Eclipse配置Hadoop开发环境,编写MapReduce关联Hadoop集群。准备:JDK环境变量配置、Eclipse、hadoop-2.7.5.tar、hadoop-eclipse-plugin-2.7.3.jar、hadoop-common-2.7.3-bin-master...

深入理解JVM03--判断对象是否存活(引用计数算法、可达性分析算法,最终判定),Eclipse设置GC日志输出,引用_p312011150的博客-程序员宝宝

本文是基于周志明的《深入理解Java虚拟机》    堆中几乎存放着Java世界中所有的对象实例,垃圾收集器在对堆回收之前,第一件事情就是要确定这些对象哪些还“存活”着,哪些对象已经“死去”(即不可能再被任何途径使用的对象)1、引用计数算法(Reference Counting)    很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它

世界第一咖啡品牌Dunkin&rsquo;Donuts简介_apensu的博客-程序员宝宝

世界第一咖啡品牌Dunkin’Donuts简介      作者:malina_mao 2008-10-28 13:49:00 总部位于美国马萨诸塞州的Dunkin’Donuts自上世纪五十年代起,就供应传统的美式甜甜圈和新鲜的研磨咖啡。2007年,Dunkin’ Donuts名列全美十大快餐连锁品牌,并当选为美国人最喜爱的咖啡品牌。 Dunkin’Donuts上海门店将提供60多种口味的高质量美味甜甜圈。通过本地市场调研,甜甜圈的甜度将根据本地消费者的口味而作调整。此外,Dunkin’ Donuts

随便推点

mysql插入数据优化_mysql 插入数据效率_benben0729的博客-程序员宝宝

插入多条数据时,INSERT INTO `tb_name` (field1,field2,field3) values (val1,val2,val3);INSERT INTO `tb_name` (field1,field2,field3) values (val4,val5,val6);用一条语句替换INSERT INTO `tb_name` (field1,field2,field3) va...

python爬虫系列开发(二)scrapy安装指南_Yang_Farley的博客-程序员宝宝

安装ScrapyScrapy在CPython(默认Python实现)和PyPy(从PyPy 5.9开始)下运行Python 2.7和Python 3.4或更高版本。如果您使用的是Anaconda或Miniconda,您可以从conda- forge通道安装该软件包,该软件包含适用于Linux,Windows和OS X的最新软件包。要使用安装Scrapy conda,请运行:conda in...

android dialog全局变量,Android开发之自定义Dialog二次打开报错问题解决_戴舜的博客-程序员宝宝

之前自定义了一个AlertDialog对话框,第一次点击时正常,但第二次调用时会出现错误:java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child‘s parent first.关于这个错误纠结了我好久,在网上百度了也不少,但感...

测试用例管理工具~JIRA_jira测试用例管理_一只会发光的鱼儿的博客-程序员宝宝

巨好用的测试用例管理工具,强烈安利!工具有:禅道、JIRA、TestCast、PingCode。JIRA、禅道相对比较成熟,另外两个测试管理工具相对有很多实用的新功能,下面主要介绍JIRA案例,下篇介绍其他工具:JIRA是项目管理、测试管理工具,具体如下:功能包括:创建项目支持多种开发方法,常用的Scrum开发方式、Kanban开发方法。Scrum敏捷开发面板比较灵活,可以进行版本管理、模块管理、自动生成报表![在这里插入图片描述](https://img-blog.csdnimg.cn/85a8e1a

Unity GameObject.FindObjectOfType和GameObject.FindObjectsOfType的遍历_主立军的博客-程序员宝宝

 GameObject.FindObjectOfType&amp;lt;&amp;gt;();官方解释是返回Type类型第一个激活的加载的对象。很多人会不理解什么是第一个激活加载的对象。简单地说就是当你添加一个组件或者游戏物体时Unity就会在物体列表中添加。物体列表是栈结构先进后出所以第一个激活的加载的对象就是你最后添加上去的组件或物体。举个例子来说 :在场景中一次添加Cube ,Cub...

向量化简的算法_向量可以化简吗(数字)_梓沂的博客-程序员宝宝

这种理解方式:(h(x1)-y1)* x1把(h(x1)-y1)看成标量,x1看成矢量:1 * [1 2 3](h(x1)-y1)* x1+(h(x2)-y2)* x2[1 2] * [1 2 3; 4 5 6] 分解后 [1 2] * [1;4] [1 2] * [2;5] [1 2] * [3;6]假设:(h(x1)-y1)=1 (h(x2)-y2)=2 x1 = [1 2 3] x2 = [4 5 6]另一种理解比较复杂:(h(xn)−y.