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

一文了解 Android 车机如何处理中控的旋钮输入?

前言

上篇文章《从实体按键看 Android 车载的自定义事件机制》带大家了解了 Android 车机支持自定义输入的机制 CustomInputService。事实上,除了支持自定义事件,对于中控上常见的音量控制、焦点控制的旋钮事件,Android 车机也是支持的。

那本篇文章带大家看下 Android 车机处理旋钮事件的内在原理:

  1. 定义
  2. 监听和订阅
  3. 接收
  4. 处理
  5. 模拟

1. 定义

和自定义输入所支持的事件一致,支持旋钮输入的事件类型也在如下文件 types.hal 中定义。

// hardware/interfaces/automotive/vehicle/2.0/types.hal/*** Property to feed H/W rotary events to android* ...*/HW_ROTARY_INPUT = (0x0A20| VehiclePropertyGroup:SYSTEM| VehiclePropertyType:INT32_VEC| VehicleArea:GLOBAL),enum RotaryInputType : int32_t {ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION = 0,ROTARY_INPUT_TYPE_AUDIO_VOLUME = 1,
};

HW_ROTARY_INPUT 代表该事件在底层的 Property 定义,供 VehicleHal 对其发起监听。

该事件涵盖了一些旋钮所必须的数据:

  • 第 0 位代表哪种旋钮硬件,由 RotaryInputType 枚举细分,包括控制焦点的旋钮 TYPE_SYSTEM_NAVIGATION 和控制音量的旋钮 TYPE_AUDIO_VOLUME
  • 第 1 位代表旋转计数,正数代表顺时针计数 clockwise,负数代表逆时针计数 counterclockwise
  • 第 2 位代表旋钮事件的目标屏幕 VehicleDisplay,默认是 MAIN,即 center console,中控屏幕
  • 第 3 位及以后代表持续计数事件之间的时间差,单位为 ns

2. 监听和订阅

上层处理事件输入的 CarInputService 在初始化的时候,会向调度车机输入的中间层 InputHalService 注册监听。

// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService ... {...@Overridepublic void init() {if (!mInputHalService.isKeyInputSupported()) {return;}mInputHalService.setInputListener(this);...}...
}

InputHalService 判断支持旋钮输入的话,向和 HAL 层交互的 VehicleHal 注册 HW_ROTARY_INPUT Property 的订阅。

// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {...public void setInputListener(InputListener listener) {...boolean rotaryInputSupported;synchronized (mLock) {mListener = listener;...rotaryInputSupported = mRotaryInputSupported;}...if (rotaryInputSupported) {mHal.subscribeProperty(this, HW_ROTARY_INPUT);}...}public boolean isRotaryInputSupported() {synchronized (mLock) {return mRotaryInputSupported;}}...
}

3. 接收

当旋钮事件发生,将通过 HAL 层抵达上述订阅该 Property 的 VehicleHal,其将找出处理方 HalServiceBaseInputHalService 并继续分发。

// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
public class VehicleHal implements HalClientCallback {...@Overridepublic void onPropertyEvent(ArrayList<HalPropValue> propValues) {synchronized (mLock) {for (int i = 0; i < propValues.size(); i++) {HalPropValue v = propValues.get(i);int propId = v.getPropId();HalServiceBase service = mPropertyHandlers.get(propId);if (service == null) {continue;}service.getDispatchList().add(v);mServicesToDispatch.add(service);VehiclePropertyEventInfo info = mEventLog.get(propId);if (info == null) {info = new VehiclePropertyEventInfo(v);mEventLog.put(propId, info);} else {info.addNewEvent(v);}}}for (HalServiceBase s : mServicesToDispatch) {s.onHalEvents(s.getDispatchList());s.getDispatchList().clear();}mServicesToDispatch.clear();}...
}

InputHalService 首先确保上层的 InputListener 确实存在,此后再检查该 HalProperty 是何种类型。HW_ROTARY_INPUT 旋钮事件的话调用 dispatchRotaryInput() 继续。

public class InputHalService extends HalServiceBase {...@Overridepublic void onHalEvents(List<HalPropValue> values) {InputListener listener;synchronized (mLock) {listener = mListener;}if (listener == null) {return;}for (int i = 0; i < values.size(); i++) {HalPropValue value = values.get(i);switch (value.getPropId()) {case HW_ROTARY_INPUT:dispatchRotaryInput(listener, value);break;...}}}...
}

dispatchRotaryInput() 将执行如下步骤:

  1. 检查必要数据是否齐全,即起码包括旋钮硬件类型、旋钮计数、目标屏幕这 3 位
  2. 按照 index 取出这三位数据
  3. 检查旋钮计数是否为 0,因为无法判断 0 是顺时针还是逆时针
  4. 检查目标屏幕是否为中控屏幕 MAIN、仪表屏幕 INSTRUMENT_CLUSTER 中的一个
  5. 检查旋钮计数的时间差数值位数是否匹配(比如:旋转了 3 格的话,那么时间差必须要占 2 位)
  6. 根据旋钮硬件类型转化为 CarInputManager 中定义的事件类型
    • 焦点控制的话转换为 INPUT_TYPE_ROTARY_NAVIGATION
    • 音量控制的话转换为 INPUT_TYPE_ROTARY_VOLUME
  7. 提取持续计数的时间差到 timestamps 数组中
  8. 根据旋钮计数方向,转换到的事件类型以及时间差数组封装 RotaryEvent 对象交由 InputListener 继续分发
public class InputHalService extends HalServiceBase {...private void dispatchRotaryInput(InputListener listener, HalPropValue value) {int timeValuesIndex = 3;  // remaining values are time deltas in nanosecondsif (value.getInt32ValuesSize() < timeValuesIndex) {return;}int rotaryInputType = value.getInt32Value(0);int detentCount = value.getInt32Value(1);int vehicleDisplay = value.getInt32Value(2);long timestamp = value.getTimestamp();  // for first detent, uptime nanosecondsboolean clockwise = detentCount > 0;detentCount = Math.abs(detentCount);if (detentCount == 0) { // at least there should be one eventreturn;}if (vehicleDisplay != VehicleDisplay.MAIN&& vehicleDisplay != VehicleDisplay.INSTRUMENT_CLUSTER) {return;}if (value.getInt32ValuesSize() != (timeValuesIndex + detentCount - 1)) {return;}int carInputManagerType;switch (rotaryInputType) {case ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION:carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION;break;case ROTARY_INPUT_TYPE_AUDIO_VOLUME:carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_VOLUME;break;default: ...}long[] timestamps = new long[detentCount];long uptimeToElapsedTimeDelta = CarServiceUtils.getUptimeToElapsedTimeDeltaInMillis();...RotaryEvent event = new RotaryEvent(carInputManagerType, clockwise, timestamps);listener.onRotaryEvent(event, convertDisplayType(vehicleDisplay));}...
}

4. 处理

监听章节里提到 InputListener 为 CarInputService,所以将传递到 CarInputService 的 onRotaryEvent() 进行处理。

onRotaryEvent() 先检查是否有使用 InputEventCapture 监听旋钮事件的 Service 存在:

  • 如果有监听,交由 Capture 该事件的 Service 专门处理
  • 如果没有,转换为 Android 标准 KeyEvent 进行处理
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService ... {...@Overridepublic void onRotaryEvent(RotaryEvent event, @DisplayTypeEnum int targetDisplay) {if (!mCaptureController.onRotaryEvent(targetDisplay, event)) {List<KeyEvent> keyEvents = rotaryEventToKeyEvents(event);for (KeyEvent keyEvent : keyEvents) {onKeyEvent(keyEvent, targetDisplay);}}}...
}

专门处理

Car App 提供了一个专门控制焦点的 RotaryService,它在绑定时通过 CarInputManager 的 requestInputEventCapture() 申请监听了 INPUT_TYPE_ROTARY_NAVIGATION 类型的旋钮事件。

// packages/apps/Car/RotaryController/src/com/android/car/rotary/RotaryService.java
public class RotaryService ... {/** Input types to capture. */private final int[] mInputTypes = new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION,...};...@Overridepublic void onServiceConnected() {super.onServiceConnected();mCar = Car.createCar(this, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,(car, ready) -> {mCar = car;if (ready) {mCarInputManager =(CarInputManager) mCar.getCarManager(Car.CAR_INPUT_SERVICE);...mCarInputManager.requestInputEventCapture(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,mInputTypes,CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT,/* callback= */ this);}});...}...
}

自然的,RotaryService 的 onRotaryEvent() 会得到调用,首先将检查目标屏幕是否符合预期,必须是 MAIN 即中控屏幕。通过的话,调用 handleRotaryEvent() 继续处理。

public class RotaryService ... {...@Overridepublic void onRotaryEvents(int targetDisplayType, @NonNull List<RotaryEvent> events) {if (!isValidDisplayType(targetDisplayType)) {return;}for (RotaryEvent rotaryEvent : events) {handleRotaryEvent(rotaryEvent);}}private static boolean isValidDisplayType(int displayType) {if (displayType == CarOccupantZoneManager.DISPLAY_TYPE_MAIN) {return true;}return false;}...
}

handleRotaryEvent() 将检查 RotaryEvent 中的硬件 type,确保确实来自于焦点控制旋钮 INPUT_TYPE_ROTARY_NAVIGATION,通过的话调用 handleRotateEvent() 继续。

public class RotaryService ... {...private void handleRotaryEvent(RotaryEvent rotaryEvent) {if (rotaryEvent.getInputType() != CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION) {return;}boolean clockwise = rotaryEvent.isClockwise();int count = rotaryEvent.getNumberOfClicks();long eventTime = rotaryEvent.getUptimeMillisForClick(count - 1);handleRotateEvent(clockwise, count, eventTime);}...
}

handleRotateEvent() 主要是依据屏幕的设置和当前 focus 的 Node 情况来决定是调用 performScrollAction() 执行屏幕滚动,还是寻找到目标 Node 调用 performFocusAction() 来执行焦点的移动。

其本质上是通过 InputManager 向系统注入 SCROLL 触摸事件,或者通过 Accessibility 向上面的或下面的待 focus 的 AccessibilityNode 发送 FOCUS Action 操作。

public class RotaryService ... {...private void handleRotateEvent(boolean clockwise, int count, long eventTime) {int rotationCount = getRotateAcceleration(count, eventTime);if (mInProjectionMode) {injectMotionEvent(DEFAULT_DISPLAY, clockwise ? rotationCount : -rotationCount);return;}if (initFocus() || mFocusedNode == null) {return;}if (mInDirectManipulationMode) {if (DirectManipulationHelper.supportRotateDirectly(mFocusedNode)) {performScrollAction(mFocusedNode, clockwise);} else {AccessibilityWindowInfo window = mFocusedNode.getWindow();if (window == null) {L.w("Failed to get window of " + mFocusedNode);return;}int displayId = window.getDisplayId();window.recycle();injectMotionEvent(displayId, clockwise ? rotationCount : -rotationCount);}return;}int remainingRotationCount = rotationCount;int direction = clockwise ? View.FOCUS_FORWARD : View.FOCUS_BACKWARD;Navigator.FindRotateTargetResult result =mNavigator.findRotateTarget(mFocusedNode, direction, rotationCount);if (result != null) {if (performFocusAction(result.node)) {remainingRotationCount -= result.advancedCount;}Utils.recycleNode(result.node);} else {L.w("Failed to find rotate target from " + mFocusedNode);}if (remainingRotationCount > 0 && isInFocusedWindow(mFocusedNode)) {AccessibilityNodeInfo scrollableContainer =mNavigator.findScrollableContainer(mFocusedNode);if (scrollableContainer != null) {injectScrollEvent(scrollableContainer, clockwise, remainingRotationCount);scrollableContainer.recycle();}}}...
}

标准处理

和导航旋钮事件不同,系统没有 Capture 音量旋钮事件 INPUT_TYPE_ROTARY_VOLUME 的 Service,那么它得执行标准处理。

首先,得将 RotatryEvent 转换为标准的按键编号 Key Code,具体的执行如下逻辑:

  1. 焦点控制按钮的话,依据方向 mapping 顺时针为焦点前进的 KEYCODE_NAVIGATE_NEXT,逆时针为焦点后退的 KEYCODE_NAVIGATE_PREVIOUS
  2. 音量控制按钮的话,mapping 为音量 +/- Key Code,顺时针为 KEYCODE_VOLUME_UP,逆时针则是 KEYCODE_VOLUME_DOWN
  3. 按照计数次数批量调用 createKeyEvent() 创建 KeyEvent 对象,并添加到待处理 keyEvents 列表中。
public class CarInputService ... {...private static List<KeyEvent> rotaryEventToKeyEvents(RotaryEvent event) {int numClicks = event.getNumberOfClicks();int numEvents = numClicks * 2; // up / down per each clickboolean clockwise = event.isClockwise();int keyCode;switch (event.getInputType()) {case CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION:keyCode = clockwise? KeyEvent.KEYCODE_NAVIGATE_NEXT: KeyEvent.KEYCODE_NAVIGATE_PREVIOUS;break;case CarInputManager.INPUT_TYPE_ROTARY_VOLUME:keyCode = clockwise? KeyEvent.KEYCODE_VOLUME_UP: KeyEvent.KEYCODE_VOLUME_DOWN;break;...}ArrayList<KeyEvent> keyEvents = new ArrayList<>(numEvents);for (int i = 0; i < numClicks; i++) {long uptime = event.getUptimeMillisForClick(i);KeyEvent downEvent = createKeyEvent(/* down= */ true, uptime, uptime, keyCode);KeyEvent upEvent = createKeyEvent(/* down= */ false, uptime, uptime, keyCode);keyEvents.add(downEvent);keyEvents.add(upEvent);}return keyEvents;}    ...
}

接着,遍历准备好的 keyEvents 列表,逐个处理。

public class CarInputService ... {...@Overridepublic void onRotaryEvent(RotaryEvent event, @DisplayTypeEnum int targetDisplay) {if (!mCaptureController.onRotaryEvent(targetDisplay, event)) {List<KeyEvent> keyEvents = rotaryEventToKeyEvents(event);// 遍历列表,逐个处理for (KeyEvent keyEvent : keyEvents) {onKeyEvent(keyEvent, targetDisplay);}}}...
}

CarInputService 的 onKeyEvent() 直接处理的 Code 只有激活语音助手的 KEYCODE_VOICE_ASSIST 和拨打电话的 KEYCODE_CALL。其他的 Key Code 执行一般处理:

  1. 如果目标屏幕是 INSTRUMENT_CLUSTER 即仪表屏幕的话,调用 handleInstrumentClusterKey()InstrumentClusterKeyListener 执行仪表上的事件,貌似是 Cluster app 完成,具体不再展开
  2. 检查是否有使用 InputEventCapture 监听 NAVIGATE_ 焦点控制、VOLUME_ 音量控制 KeyEvent 的 Service 存在,有的话回调 onKeyEvent() Callback
  3. 如果没有 Capture 处理的好,告知 KeyEventListener 进行兜底处理
public class CarInputService ... {...@Overridepublic void onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {// Special case key code that have special "long press" handling for automotiveswitch (event.getKeyCode()) {case KeyEvent.KEYCODE_VOICE_ASSIST:handleVoiceAssistKey(event);return;case KeyEvent.KEYCODE_CALL:handleCallKey(event);return;default:break;}assignDisplayId(event, targetDisplayType);// Allow specifically targeted keys to be routed to the clusterif (targetDisplayType == CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER&& handleInstrumentClusterKey(event)) {return;}if (mCaptureController.onKeyEvent(targetDisplayType, event)) {return;}mMainDisplayHandler.onKeyEvent(event);}...
}

KeyEventListener 在 CarInputService 初始化的时候指定,具体的就是通过 InputManagerHelper 注入 KeyEvent。

public class CarInputService ... {...private final KeyEventListener mMainDisplayHandler;public CarInputService( ... ) {this(context, inputHalService, userService, occupantZoneService, bluetoothService,new Handler(CarServiceUtils.getCommonHandlerThread().getLooper()),context.getSystemService(TelecomManager.class),event -> InputManagerHelper.injectInputEvent(context.getSystemService(InputManager.class), event),() -> Calls.getLastOutgoingCall(context),() -> getViewLongPressDelay(context),() -> context.getResources().getBoolean(R.bool.config_callButtonEndsOngoingCall),new InputCaptureClientController(context));}...
}

InputManagerHelper 没啥特别的,直接调用 InputManager 的标准方法 injectInputEvent() 完成注入,后续由 InputManagerService 开始 Dispatch、Transport 等一系列处理。

// packages/services/Car/car-builtin-lib/src/android/car/builtin/input/InputManagerHelper.java
public class InputManagerHelper {...public static boolean injectInputEvent(@NonNull InputManager inputManager,@NonNull android.view.InputEvent event) {return inputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);}
}

5. 模拟

当旋钮按键环境尚未到位的时候,我们可以使用 adb 命令模拟旋钮事件来验证代码链路。

格式:

adb shell cmd car_service inject-rotary [-d display] [-i input_type] [-c clockwise] [-dt delta_times_ms]
  • display,目标屏幕:0 代表中控屏幕,1 代表仪表屏幕,默认是 0
  • input_type,按钮类型: 10 代表焦点控制,11 代表音量控制,默认是 10
  • clockwise,旋钮方向: true 代表顺时针方向,false 代表逆时针,默认是 false
  • delta_times_ms,持续旋转计数的时间间隔:多次旋转事件和当前时刻的间隔列表,按降序排列,默认是 0,表示只有一次旋转

下面将介绍几个命令示例,帮助大家更好地理解该命令的使用。

adb shell cmd car_service inject-rotary

没有指定任何参数,全部都是默认的操作,表示针对中控屏幕发送焦点控制的旋钮事件,方向为逆时针、焦点后退 1 格

adb shell cmd car_service inject-rotary -d 1 -i 11 -c true

表示针对仪表屏幕发送音量控制的旋钮事件,方向为顺时针、调低 1 格

adb shell cmd car_service inject-rotary -c true -dt 100 50

表示针对中控屏幕发送焦点控制的旋钮事件,方向为顺时针、3 次计数、焦点前进 3 格

结语

与自定义输入相比,旋钮事件的处理流程有细微差异,主要体现在 CarInputService 会针对音量、焦点两种的旋钮控制,存在特定的处理逻辑。最后,结合一张图回顾下整体流程:

  1. 支持音量控制焦点控制的两种旋钮硬件产生 HW_ROTARY_INPUT Propery 变化

  2. 由和 HAL 层交互的 VehicleHal 订阅到 Propery 变化,将事件提取为 HalPropValue 类型

  3. 并发送给车机输入的中间服务 InputHalService 接收和进一步地封装为 RotaryEvent 类型

  4. 分发到处理事件输入的专用服务 CarInputService

    a. 如果有 Capture 音量/焦点的 Rotary 事件的交由其专门处理:Car App 的 RotaryService,其将决定通过 InputManager 注入 SCROLL 滚动还是通过 Accessibility 触发焦点 Focus 操作;

    b. 如果没有,则执行标准处理:

    • 首先按照 Rotary 类型和旋钮方向、计数封装为 Android 标准 KeyEvent 列表
    • 如果目标屏幕为仪表的话,列表交由 Cluster App 处理
    • 反之检查是否有 Capture 该 KeyEvent 的 Service 需要处理
    • 最后交由 InputManager 逐个注入该 KeyEvent,继而由系统的 InputManagerService 进行调度

推荐阅读

  • 从实体按键看 Android 车载的自定义事件机制
  • 如何打造车载语音交互:Google Voice Interaction 给你答案
  • Android 车机初体验:Auto,Automotive 傻傻分不清楚?

参考文档

  • https://developer.android.google.cn/training/cars
  • https://source.android.google.cn/docs/devices/automotive/hmi/rotary_controller/app_developers

相关文章:

一文了解 Android 车机如何处理中控的旋钮输入?

前言 上篇文章《从实体按键看 Android 车载的自定义事件机制》带大家了解了 Android 车机支持自定义输入的机制 CustomInputService。事实上&#xff0c;除了支持自定义事件&#xff0c;对于中控上常见的音量控制、焦点控制的旋钮事件&#xff0c;Android 车机也是支持的。 那…...

小红书推广 方法总结

大家好&#xff0c;我是网媒智星&#xff0c;今天跟大家分享一下小红书的推广方法和经验。 一、平台简介 1、什么是小红书&#xff1f; 小红书是一个消费决策/生活方式平台&#xff0c;用户可以通过图片、文案、视频等方式分享美好生活。 2、用户画像 - 2亿月活跃…...

通讯录的实现(超详细)——C语言(进阶)

目录 一、创建联系人信息&#xff08;结构体&#xff09; 二、创建通讯录&#xff08;结构体&#xff09; 三、define定义常量 四、打印通讯录菜单 五、枚举菜单选项 六、初始化通讯录 七、实现通讯的的功能 7.1 增加加联系人 7.2 显示所有联系人的信息 ​7.3 单独查…...

3D 渲染技巧-如何创建高质量写实渲染?

掌握创建高质量建筑渲染和任何 3D 渲染的艺术是一项复杂且需要技巧的工作&#xff0c;通常需要多年的经验和实践。实现逼真的结果需要仔细考虑众多因素&#xff0c;并避免可能导致缺乏真实性的假渲染效果的常见错误。 避免常见错误 - 提升渲染游戏的技巧 在追求创建真正逼真的…...

fastadmin采坑之获取当前登录admin用户的信息

在controller层里想要获取当前登录admin用户的信息 print_r($this->auth->getUserInfo());但是有个问题 我在fa_admin表中添加了新的字段&#xff0c;这个方法获取不到新字段的数值&#xff0c;具体也没有去研究估计跟方法有关 然后我直接用模型去获取数据&#xff0c;简…...

【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换读写分离】—— 案例实战

&#x1f4a7; S p r i n g A O P 主从数据源切换 读写分离 自定义注解案例实战&#xff01; \color{#FF1493}{Spring AOP 主从数据源切换 读写分离 自定义注解 案例实战&#xff01;} SpringAOP主从数据源切换读写分离自定义注解案例实战&#xff01;&#x1f4a7; …...

【LeetCode每日一题合集】2023.7.24-2023.7.30

文章目录 771. 宝石与石头代码1——暴力代码2——位运算集合⭐&#xff08;英文字母的long集合表示&#xff09; 2208. 将数组和减半的最少操作次数&#xff08;贪心 优先队列&#xff09;2569. 更新数组后处理求和查询⭐⭐⭐⭐⭐&#xff08;线段树&#xff09;TODO2500. 删除…...

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(14)-Fiddler断点(breakpoints)实战,篡改或伪造数据

1.简介 上一篇主要就讲解和分享Fiddler断点的理论和操作&#xff0c;今天宏哥就用具体例子&#xff0c;将上一篇中的理论知识实践一下。而且在实际测试过程中&#xff0c;有时候需要修改请求或响应数据&#xff0c;或者直接模拟服务器响应&#xff0c;此时可以使用fiddler进行…...

ELK + Fliebeat + Kafka日志系统

参考&#xff1a; ELKFilebeatKafka分布式日志管理平台搭建_51CTO博客_elk 搭建 ELK 日志分析系统概述及部署&#xff08;上&#xff09;-阿里云开发者社区 ELK是三个开源软件的缩写&#xff0c;分别表示&#xff1a;Elasticsearch , Logstash, Kibana , 它们都是开源软件。…...

Scaling Instruction-Finetuned Language Models

Paper name Scaling Instruction-Finetuned Language Models Paper Reading Note Paper URL: https://arxiv.org/pdf/2210.11416.pdf TL;DR 2022 年谷歌出的文章&#xff0c;对指令微调的影响因素进行分析&#xff0c;提出了一些提升指令微调效果的方案。与该文章一起出品…...

rust 闭包函数

函数有自己的类型&#xff0c;可以像使用基础类型一样使用函数&#xff0c;包括将函数保存在变量中、保存在 vec 中、声明在结构体成员字段中。闭包函数也是函数&#xff0c;也有自己的类型定义。不过&#xff0c;函数实际上是指针类型&#xff0c;在 rust 所有权中属于借用的关…...

MySQL 实现分库和分表的备份 2023.7.29

1、分库备份 [rootlocalhost mysql-backup]# cat db_bak.sh #!/bin/bash k_userroot bak_password123456 bak_path/root/mysql-backup/ bak_cmd"-u$bak_user -p$bak_password" exc_db"Database|information_schema|mysql|performance_schema|sys" dbname…...

20230728----重返学习-跨域-模块化-webpack初步

day-122-one-hundred-and-twenty-two-20230728-跨域-模块化-webpack初步 跨域 跨域 为什么要跨域&#xff1f; 浏览器为了安全&#xff0c;不能让我们的html文件可以随意引用别的服务器中的文件&#xff0c;只允许我们的html或js文件中&#xff0c;请求我们自己服务器。这个…...

[SQL挖掘机] - 多表连接: union all

介绍: sql中的union all是用于合并两个或多个select语句的结果集的操作符。与union不同的是&#xff0c;union all不会自动去除重复的行&#xff0c;它会简单地将多个查询的结果集合并在一起&#xff0c;包括重复的行。 用法: union all的基本语法如下&#xff1a; select_…...

TypeError: run() got an unexpected keyword argument ‘hide_label‘ yolov5最新版本报错

报错展示 解决方法 把detect.py中的如上部分的 --hide-label改为 --hide-labels&#xff0c;成功解决....

什么是Java中的集成测试?

Java中的集成测试&#xff08;Integration Test&#xff09;是一种测试方法&#xff0c;用于测试多个模块或组件之间的交互和集成。在Java中&#xff0c;集成测试通常使用单元测试框架&#xff08;如JUnit&#xff09;编写和运行。 对于初学者来说&#xff0c;集成测试可能有些…...

打卡力扣题目二

#左耳听风 ARST 打卡活动重启# 目录 一、问题 二、 解题方法一 三、enumerate函数介绍 关于 ARTS 的释义 —— 每周完成一个 ARTS&#xff1a; ● Algorithm: 每周至少做一个 LeetCode 的算法题 ● Review: 阅读并点评至少一篇英文技术文章 ● Tips: 学习至少一个技术技巧 …...

【Qt】QML-02:QQuickView用法

1、先看demo QtCreator自动生成的工程是使用QQmlApplicationEngine来加载qml文件&#xff0c;下面的demo将使用QQuickView来加载qml文件 #include <QGuiApplication> #include <QtQuick/QQuickView>int main(int argc, char *argv[]) {QGuiApplication app(argc,…...

【IDEA】idea不自动生成target

文章目录 1. 不生成target2. 仅部分文件不生成target2.1. 一般原因就是资源没有设置2.2. 配置编译src/main/java文件夹下的资源文件2.3. 清理缓存&#xff08;王炸&#xff09; 3. 参考资料 本文描述idea不生成target的几种情况以及处理方法 1. 不生成target 像下图这样根本就…...

从官网认识 JDK,JRE,JVM 三者的关系

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ JVM 是一些大厂面试必问点&#xff0c;要想解决 OOM、性能调优方面的问题&#xff0c;掌握 JVM 知识必不可少&#xff0c;从今天开始&#xff0c;将为大家介绍 JVM 的常用知…...

python 将pdf文件转图片

有小伙伴问了怎么将 pdf文件转图片的问题&#xff0c;我百度了一波儿&#xff0c;搞了以下python代码给他封装成exe工具了。 中途打包踩了个坑&#xff0c;python进程池的问题&#xff0c;本地运行没啥问题&#xff0c;打包好的exe文件双击就会使电脑内存爆破卡死&#xff0c;…...

js原型以及原型链

目录 原型隐式原型显式原型constructornew操作符 重写原型对象原型链继承原型链继承借用构造函数继承组合构造继承 原型继承寄生继承组合寄生继承 原型继承关系 原型 在JavaScript中&#xff0c;每个对象都有一个内置属性[[prototype]]&#xff0c;这个属性指向一个另一个对象…...

Java面向对象编程实战详解(图书管理系统示例)

文章目录 面向编程概念图书管理系统示例需求分析设计阶段编码实现创建目录结构Book类的编码BookList类的编码User类的编码AdminUser类的编码NormalUser类的编码启动类的编写具体的操作实现IOperation接口新增图书的实现借阅图书的实现删除图书的实现显示图书的实现查找图书的实…...

ubuntu设置主机ip

ubuntu 设置ip sudo dhclient -r enp67s0 # 是你的网卡&#xff0c;可以通过ifconfig 查&#xff0c;比如enp0 sudo ifconfig enp67s0 192.168.1.114 netmask 255.255.255.0 Ubuntu显示有线网已连接但无法上网&#xff0c;已经确认网口、交换机&#xff08;路由器&#xff…...

CleanMyMac X4.14.1中文版如何清理 Mac系统?CleanMyMac 真的能断网激活吗?

CleanMyMac X4.14.1中文版如何清理 Mac系统&#xff1f;Mac系统在使用过程中都会产生大量系统垃圾&#xff0c;如不需要的系统语言安装包&#xff0c;视频网站缓存文件&#xff0c;mac软件卸载残留的注册表等。 随着时间推移&#xff0c;mac系统垃圾就会越来越多&#xff0c;电…...

详细介绍 React 中如何使用 redux

在使用之前要先了解它的配套插件&#xff1a; 在React中使用redux&#xff0c;官方要求安装其他插件 Redux Toolkit 和 react-redux Redux Toolkit&#xff1a;它是一个官方推荐的工具集&#xff0c;旨在简化 Redux 的使用和管理。Redux Toolkit 提供了一些提高开发效率的工具…...

VLOOKUP多条件查询

LOOKUP(1,0/((A3:A15A18)*(C3:C15C18)),F3:F15)...

分页插件Mybatis

<plugins><!-- com.github.pagehelper为PageHelper类所在包名 --><plugin interceptor"com.github.pagehelper.PageInterceptor"><!-- 配置方言:告诉分页插件使用底层数据库是什么--><property name"helperDialect" value"…...

AXI协议之AXILite开发设计(四)—Block Design使用

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 2、AXI interconnect互联组件的使用…...

音视频——帧内预测

H264编码(帧内预测) 在帧内预测模式中&#xff0c;预测块P是基于已编码重建块和当前块形成的。对亮度像素而言&#xff0c;P块用于44子块或者1616宏块的相关操作。44亮度子块有9种可选预测模式&#xff0c;独立预测每一个44亮度子块&#xff0c;适用于带有大量细节的图像编码&…...

如何做旅游计划的网站/企业文化ppt

Scala 方法与函数引言1、方法1.1 语法1.2 方法参数1.3 方法调用2、函数3、方法与函数的区别与联系3.1 区别3.2 方法转换为函数引言 Scala 有方法与函数&#xff0c;二者在语义上的区别很小。Scala 方法是类的一部分&#xff0c;而函数是一个对象可以赋值给一个变量。换句话来说…...

网站404页面源码/seo 重庆

1. 安装yum install asciinema 2. 使用录制 asciinema rec filename(可选&#xff0c;方便进行后期的回放play)同时生成一个url 地址方便传递https://asciinema.org/a/xxxxxxx同时绑定账户之后&#xff0c;可以存储历史的信息 asciinema play filename 3. 参考地址https://a…...

佛山 做网站公司有哪些/郑州seo外包公司哪家好

问题 Charles是Mac上面知名的代理软件&#xff0c;类似Windows上面的fiddler。在调试CDN的过程中&#xff0c;需要远程调试某个地区的节点&#xff0c;就只能使用代理服务区进行访问调试。但是这个过程中同时需要Charles对CDN的客户端进行抓包处理&#xff0c;但同时有需要一个…...

邢台移动网站建设费用/seo这个行业怎么样

原题 题目描述 输入一个二叉树的先序串&#xff0c;输出以括号形式表示的而叉树。如果结点的子树为空&#xff0c;先序串的对应位置为空格符。 输入 第1行&#xff1a;先序串 &#xff08;结点数≤26&#xff0c;以单个大写字母表示&#xff09; 输出 第1行&#xff1a;二…...

数字营销证书/高级seo

因为看到很多公司招聘需要有linux平台下的开发经验&#xff0c;所以今天在笔记本上装了一个Kubuntu作为以后学习的平台。本文采用的是win7Kubuntu双系统的模式&#xff0c;尽管最后安装成功了&#xff0c;但是中间有很多波折在此记下来以备以后再次发生同样的事情。首先&#x…...

织梦网站文章内容模板/北京网站优化校学费

本节目标&#xff1a; 1.掌握流的概念2.掌握字节流与字符流的作用3.掌握文件的标准操作步骤4.掌握字节与字符的操作的区别字节流和字符流之间的相互转换&#xff1a; 参考文章&#xff0c;点击这里 1. 流 在程序中&#xff0c;所有的数据都是以流的方式进行传输和保存的&am…...