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

USB子系统学习(四)用户态下使用libusb读取鼠标数据

文章目录

  • 1、声明
  • 2、HID协议
    • 2.1、描述符
    • 2.2、鼠标数据格式
  • 3、应用程序
  • 4、编译应用程序
  • 5、测试
  • 6、其它

1、声明

本文是在学习韦东山《驱动大全》USB子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。

韦老师的《驱动大全》:商品详情

其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git

libusb api:https://libusb.sourceforge.io/api-1.0/libusb_api.html

2、HID协议

HID:Human Interface Devices, 人类用来跟计算机交互的设备。就是鼠标、键盘、游戏手柄等设备。对于USB接口的HID设备,有一套协议。

2.1、描述符

HID设备有如下描述符:

  • HID设备的"设备描述符"并无实际意义,没有使用"设备描述符"来表示自己是HID设备。
  • HID设备只有一个配置,所以只有一个配置描述符。
  • 接口描述符:
    • bInterfaceClass为3,表示它是HID设备。
    • bInterfaceSubClass是0或1,1表示它支持"Boot Interface"(表示PC的BIOS能识别、使用它),0表示必须等操作系统启动后通过驱动程序来使用它。
    • bInterfaceProtocol:0-None, 1-键盘, 2-鼠标。
  • 端点描述符:HID设备有一个控制端点、一个中断端点。

对于鼠标,HOST可以通过中断端点读到数据。

2.2、鼠标数据格式

通过中断传输可以读到鼠标数据,它是8字节的数据,格式如下:

偏移大小描述
01字节
11字节按键状态
22字节X位移
42字节Y位移
61字节或2字节滚轮

按键状态里,每一位对应鼠标的一个按键,等1时表示对应按键被点击了,格式如下:

长度描述
01鼠标的左键
11鼠标的右键
21鼠标的中间键
35保留,设备自己定义bit3: 鼠标的侧边按键bit4:

X位移、Y位移都是8位的有符号数。对于X位移,负数表示鼠标向左移动,正数表示鼠标向右移动,移动的幅度就使用这个8位数据表示。对于Y位移,负数表示鼠标向上移动,正数表示鼠标向下移动,移动的幅度就使用这个8位数据表示。

3、应用程序

本次应用程序使用的是同步接口读取鼠标数据。

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libusb-1.0/libusb.h>int main(int argc, char **argv)
{int err;libusb_device *dev, **devs;int num_devices;int endpoint;int interface_num;int transferred;int count = 0;unsigned char buffer[8];struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;int found = 0;/* libusb init */err = libusb_init(NULL);if (err < 0) {fprintf(stderr, "failed to initialise libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* get device list */if ((num_devices = libusb_get_device_list(NULL, &devs)) < 0)    // 获取设备描述符列表(函数返回设备描述符数量){fprintf(stderr, "libusb_get_device_list() failed\n");libusb_exit(NULL);exit(1);}   fprintf(stdout, "libusb_get_device_list() ok\n");/* for each device, get config descriptor */for (int i = 0; i < num_devices; i++){dev = devs[i];err = libusb_get_config_descriptor(dev, 0, &config_desc);   // 获取配置描述符if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}fprintf(stdout, "libusb_get_config_descriptor() ok\n");/* parse interface descriptor, find usb mouse */for (int interface = 0; interface < config_desc->bNumInterfaces; interface++)   // 枚举所有接口描述符{const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];     // 获取配置描述符里的第一个接口描述符interface_num = intf_desc->bInterfaceNumber;        // 记录该接口描述符的编号(编号是从0开始)if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)  // 判断是否是HID设备和是否是鼠标协议continue;/* 找到了USB鼠标 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++)   // 枚举所有端点描述符{// 判断是否是中断传输,是否是输入端点(输入输出都是以USB Host来讨论,所以该端点是USB Device输出到USB Host)if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT || (intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN){/* 找到了输入的中断端点 */fprintf(stdout, "find in int endpoint\n");endpoint = intf_desc->endpoint[ep].bEndpointAddress;found = 1;break;}}if (found)break;}libusb_free_config_descriptor(config_desc);if (found)break; }if (!found){/* free device list */libusb_free_device_list(devs, 1);libusb_exit(NULL);exit(1);}/* libusb open */if (found){err = libusb_open(dev, &dev_handle);if (err){fprintf(stderr, "failed to open usb mouse\n");exit(1);}fprintf(stdout, "libusb_open ok\n");}/* free device list */libusb_free_device_list(devs, 1);/* claim interface */libusb_set_auto_detach_kernel_driver(dev_handle, 1);  err = libusb_claim_interface(dev_handle, interface_num);if (err){fprintf(stderr, "failed to libusb_claim_interface\n");exit(1);}fprintf(stdout, "libusb_claim_interface ok\n");/* libusb interrupt transfer */while (1){err = libusb_interrupt_transfer(dev_handle, endpoint, buffer, 8, &transferred, 5000);		// 发起中断传输,阻塞等待,5s超时时间if (!err) {/* parser data */printf("%04d datas: ", count++);printf("recv datas len = %d\n", transferred);for (int i = 0; i < transferred; i++){printf("%02x ", buffer[i]);}printf("\n");} else if (err == LIBUSB_ERROR_TIMEOUT){fprintf(stderr, "libusb_interrupt_transfer timout\n");} else {const char *errname = libusb_error_name(err);fprintf(stderr, "libusb_interrupt_transfer err : %d, %s\n", err, errname);//exit(1);}}/* libusb close */libusb_release_interface(dev_handle, interface_num);libusb_close(dev_handle);libusb_exit(NULL);
}

4、编译应用程序

假设你的开发板是ubuntu系统:

# 安装libusb库
$ sudo apt install libusb-1.0-0-dev# 编译程序
$ gcc -o readmouse readmouse.c -lusb-1.0

5、测试

将usb鼠标插入开发板:

执行程序:

$ sudo ./readmouse

移动鼠标:

滚轮滑动:

按键状态:

另外,每个鼠标的数据格式是不一样的。以上测试结果只是我使用的鼠标。

6、其它

以下是使用异步接口读取鼠标数据的测试程序。

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libusb-1.0/libusb.h>struct usb_mouse {struct libusb_device_handle *handle;int interface;int endpoint;unsigned char buf[16];int transferred;struct libusb_transfer *transfer;struct usb_mouse *next;
};static struct usb_mouse *usb_mouse_list;void free_usb_mouses(struct usb_mouse *usb_mouse_list)
{struct usb_mouse *pnext;while (usb_mouse_list){pnext = usb_mouse_list->next;free(usb_mouse_list);usb_mouse_list = pnext;}
}int get_usb_mouse(libusb_device **devs, int num_devices, struct usb_mouse **usb_mouse_list)
{int err;libusb_device *dev;int endpoint;int interface_num;struct libusb_config_descriptor *config_desc;struct libusb_device_handle *dev_handle = NULL;struct usb_mouse *pmouse;struct usb_mouse *list = NULL;int mouse_cnt = 0;/* for each device, get config descriptor */for (int i = 0; i < num_devices; i++) {dev = devs[i];/* parse interface descriptor, find usb mouse */        err = libusb_get_config_descriptor(dev, 0, &config_desc);       // 获取配置描述符if (err) {fprintf(stderr, "could not get configuration descriptor\n");continue;}fprintf(stdout, "libusb_get_config_descriptor() ok\n");for (int interface = 0; interface < config_desc->bNumInterfaces; interface++) {     // 枚举所有接口描述符const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];interface_num = intf_desc->bInterfaceNumber;        // 记录该接口描述符的编号(编号是从0开始)if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2)      // 判断是否是HID设备和是否是鼠标协议    continue;else{/* 找到了USB鼠标 */fprintf(stdout, "find usb mouse ok\n");for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++)   // 枚举所有端点描述符{// 判断是否是中断传输,是否是输入端点(输入输出都是以USB Host来讨论,所以该端点是USB Device输出到USB Host)if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT ||(intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) {/* 找到了输入的中断端点 */fprintf(stdout, "find in int endpoint\n");endpoint = intf_desc->endpoint[ep].bEndpointAddress;/* libusb_open */err = libusb_open(dev, &dev_handle);if (err){fprintf(stderr, "failed to open usb mouse\n");return -1;}fprintf(stdout, "libusb_open ok\n");/* 记录下来: 放入链表 */pmouse = malloc(sizeof(struct usb_mouse));if (!pmouse){fprintf(stderr, "can not malloc\n");return -1;}pmouse->endpoint  = endpoint;pmouse->interface = interface_num;pmouse->handle    = dev_handle;pmouse->next      = NULL;if (!list)list = pmouse;else{pmouse->next = list;list = pmouse;}mouse_cnt++;break;}}}}libusb_free_config_descriptor(config_desc);}*usb_mouse_list = list;return mouse_cnt;
}static void mouse_irq(struct libusb_transfer *transfer)
{static int count = 0;if (transfer->status == LIBUSB_TRANSFER_COMPLETED){/* parser data */printf("%04d datas: ", count++);for (int i = 0; i < transfer->actual_length; i++){printf("%02x ", transfer->buffer[i]);}printf("\n");}if (libusb_submit_transfer(transfer) < 0){fprintf(stderr, "libusb_submit_transfer err\n");}
}int main(int argc, char **argv)
{int err;libusb_device **devs;int num_devices, num_mouse;struct usb_mouse *pmouse;/* libusb init */err = libusb_init(NULL);if (err < 0) {fprintf(stderr, "failed to initialise libusb %d - %s\n", err, libusb_strerror(err));exit(1);}/* get device list */if ((num_devices = libusb_get_device_list(NULL, &devs)) < 0)    // 获取设备描述符列表(函数返回设备描述符数量){fprintf(stderr, "libusb_get_device_list() failed\n");libusb_exit(NULL);exit(1);}   fprintf(stdout, "libusb_get_device_list() ok\n");/* get usb mouse */num_mouse = get_usb_mouse(devs, num_devices, &usb_mouse_list);if (num_mouse <= 0){/* free device list */libusb_free_device_list(devs, 1);libusb_exit(NULL);exit(1);}fprintf(stdout, "get %d mouses\n", num_mouse);/* free device list */libusb_free_device_list(devs, 1);/* claim interface */pmouse = usb_mouse_list;while (pmouse){libusb_set_auto_detach_kernel_driver(pmouse->handle, 1);  err = libusb_claim_interface(pmouse->handle, pmouse->interface);if (err){fprintf(stderr, "failed to libusb_claim_interface\n");exit(1);}pmouse = pmouse->next;}fprintf(stdout, "libusb_claim_interface ok\n");/* for each mouse, alloc transfer, fill transfer, submit transfer */pmouse = usb_mouse_list;while (pmouse){/* alloc transfer */pmouse->transfer = libusb_alloc_transfer(0);/* fill transfer */libusb_fill_interrupt_transfer(pmouse->transfer, pmouse->handle, pmouse->endpoint, pmouse->buf,sizeof(pmouse->buf), mouse_irq, pmouse, 0);/* submit transfer */libusb_submit_transfer(pmouse->transfer);pmouse = pmouse->next;}/* handle events */while (1) {struct timeval tv = { 5, 0 };int r;r = libusb_handle_events_timeout(NULL, &tv);if (r < 0) {fprintf(stderr, "libusb_handle_events_timeout err\n");break;}}/* libusb_close */pmouse = usb_mouse_list;while (pmouse){libusb_release_interface(pmouse->handle, pmouse->interface);libusb_close(pmouse->handle);        pmouse = pmouse->next;}free_usb_mouses(usb_mouse_list);libusb_exit(NULL);}

运行程序前,先把多个鼠标插入开发板,然后运行测试程序,移动鼠标,查看打印结果。

相关文章:

USB子系统学习(四)用户态下使用libusb读取鼠标数据

文章目录 1、声明2、HID协议2.1、描述符2.2、鼠标数据格式 3、应用程序4、编译应用程序5、测试6、其它 1、声明 本文是在学习韦东山《驱动大全》USB子系统时&#xff0c;为梳理知识点和自己回看而记录&#xff0c;全部内容高度复制粘贴。 韦老师的《驱动大全》&#xff1a;商…...

Ansible简单介绍及用法

一、简介 Ansible是一个简单的自动化运维管理工具&#xff0c;基于Python语言实现&#xff0c;由Paramiko和PyYAML两个关键模块构建&#xff0c;可用于自动化部署应用、配置、编排task(持续交付、无宕机更新等)。主版本大概每2个月发布一次。 Ansible与Saltstack最大的区别是…...

目前推荐的优秀编程学习网站与资源平台,涵盖不同学习方式和受众需求

一、综合教程与互动学习平台 菜鸟教程 特点:适合零基础新手,提供免费编程语言教程(Python、Java、C/C++、前端等),页面简洁且包含大量代码示例,支持快速上手。适用人群:编程入门者、需要快速查阅语法基础的学习者。W3Schools 特点:专注于Web开发技术(HTML、CSS、JavaS…...

软件工程-软件需求规格说明(SRS)

基本介绍 目标 便于用户、分析人员、设计人员进行交流 支持目标软件系统的确认&#xff08;验收&#xff09; 控制系统进化过程&#xff08;追加需求&#xff09;&#xff1a;拥有版本记录表 需要在软件分析完成后&#xff0c;编写完成软件需求说明书。 具体标准可参考GB…...

运维_Mac环境单体服务Docker部署实战手册

Docker部署 本小节&#xff0c;讲解如何将前端 后端项目&#xff0c;使用 Docker 容器&#xff0c;部署到 dev 开发环境下的一台 Mac 电脑上。 1 环境准备 需要安装如下环境&#xff1a; Docker&#xff1a;容器MySQL&#xff1a;数据库Redis&#xff1a;缓存Nginx&#x…...

UE5.5 PCGFrameWork--GPU CustomHLSL

在上一篇UE5.5 PCGFrameWork使用入门-CSDN博客 大致介绍了UE5 PCG框架的基本使用. 本篇探索PCGFrame的高级应用--GPU点云。也就是利用GPU HLSL编程对点云进行操纵&#xff0c;可以大幅度提升点云生成效率。 目前在UE5 PCG框架中&#xff0c;点云GPU的应用大致分为三类: Point…...

RabbitMQ 如何设置限流?

RabbitMQ 的限流&#xff08;流量控制&#xff09;主要依赖于 QoS&#xff08;Quality of Service&#xff09; 机制&#xff0c;即 prefetch count 参数。这个参数控制每个消费者一次最多能获取多少条未确认的消息&#xff0c;从而避免某个消费者被大量消息压垮。 1. RabbitMQ…...

json格式,curl命令,及轻量化处理工具

一. JSON格式 JSON&#xff08;JavaScript Object Notation&#xff09; 是一种轻量级的数据交换格式。它基于一个子集的JavaScript编程语言&#xff0c;使用人类易于阅读的文本格式来存储和表示数据。尽管名字中有“JavaScript”&#xff0c;但JSON是语言无关的&#xff0c;几…...

Postman面试问题

在 API 测试领域&#xff0c;Postman 已成为最流行的工具之一。无论是功能测试、自动化测试&#xff0c;还是接口调试&#xff0c;Postman 都扮演着重要角色。而在软件测试面试中&#xff0c;Postman 相关问题更是高频考点。如果你正在准备面试&#xff0c;赶紧看看这些Postman…...

【JVM详解四】执行引擎

一、概述 Java程序运行时&#xff0c;JVM会加载.class字节码文件&#xff0c;但是字节码并不能直接运行在操作系统之上&#xff0c;而JVM中的执行引擎就是负责将字节码转化为对应平台的机器码让CPU运行的组件。 执行引擎是JVM核心的组成部分之一。可以把JVM架构分成三部分&am…...

esp32 udp 客户端 广播

esp32 udp 客户端 广播 #include "bsp_udpc.h"// #include "com_config.h" // #include "com_xqueue.h"#include "bsp_udpc.h" #define TAG "bsp_udpc"#include <string.h> #include <sys/param.h> #include &q…...

nginx日志存储access日志和error保留180天,每晚把前一天的日志文件压缩成tar.gz

‌logrotate日志分割时&#xff0c;rotate参数是必须要加的‌ 在logrotate的配置文件中&#xff0c;rotate参数用于指定保留的旧日志文件数量。如果不配置rotate参数&#xff0c;默认是0个&#xff0c;也就是只允许存在一份日志&#xff0c;刚切分出来的日志会马上被删除‌ l…...

【Java】多线程和高并发编程(四):阻塞队列(上)基础概念、ArrayBlockingQueue

文章目录 四、阻塞队列1、基础概念1.1 生产者消费者概念1.2 JUC阻塞队列的存取方法 2、ArrayBlockingQueue2.1 ArrayBlockingQueue的基本使用2.2 生产者方法实现原理2.2.1 ArrayBlockingQueue的常见属性2.2.2 add方法实现2.2.3 offer方法实现2.2.4 offer(time,unit)方法2.2.5 p…...

C#控件开发6—旋转按钮

按钮功能&#xff1a;手自动旋转&#xff0c;标签文本显示、点击二次弹框确认&#xff08;源码在最后边&#xff09;&#xff1b; 【制作方法】 找到控件的中心坐标&#xff0c;画背景外环、内圆&#xff1b;再绘制矩形开关&#xff0c;进行角度旋转即可获得&#xff1b; 【关…...

在亚马逊云科技上云原生部署DeepSeek-R1模型(下)

在本系列的上篇中&#xff0c;我们介绍了如何通过Amazon Bedrock部署并测试使用了DeepSeek模型。在接下来的下篇中小李哥将继续介绍&#xff0c;如何利用亚马逊的AI模型训练平台SageMaker AI中的&#xff0c;Amazon Sagemaker JumpStart通过脚本轻松一键式部署DeepSeek预训练模…...

C# COM 组件在.NET 平台上的编程介绍

.NET学习资料 .NET学习资料 .NET学习资料 一、COM 组件简介 COM&#xff08;Component Object Model&#xff09;即组件对象模型&#xff0c;是一种微软提出的软件组件技术&#xff0c;它允许不同的软件模块在二进制层面进行交互。COM 组件可以用多种编程语言开发&#xff0…...

火热的大模型: AIGC架构解析

文章目录 一、背景介绍二、架构描述数据层模型层&#xff08;MaaS&#xff09;服务层&#xff08;PaaS&#xff09;基础设施层&#xff08;IaaS&#xff09;应用层 三、架构分析四、应用场景与价值4.1 典型场景4.2 价值体现 五、总结 一、背景介绍 火热的大模型&#xff0c;每…...

Android LifecycleOwner 闪退,java 继承、多态特性!

1. 闪退 同意隐私政策后&#xff0c;启动进入游戏 Activity 闪退 getLifecycle NullPointerException 空指针异常 FATAL EXCEPTION: main Process: com.primer.aa.gg, PID: 15722 java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.primer.aa.…...

PHP 完整表单实例

PHP 完整表单实例 引言 表单是网站与用户交互的重要方式&#xff0c;尤其是在收集用户输入数据时。PHP 作为一种流行的服务器端脚本语言&#xff0c;在处理表单数据方面具有强大的功能。本文将提供一个完整的 PHP 表单实例&#xff0c;涵盖表单创建、数据收集、验证和存储等关…...

深度学习学习笔记(32周)

目录 摘要 abstract 1 Mask R-CNN 2 RoI Align 2.1 RoIPool实验 2.2 RoIAlign实验 3 Mask Branch(FCN) 4 其他细节 4.1 Mask R-CNN损失 4.2 Mask分支损失 4.3 Mask Branch预测使用 摘要 Mask R-CNN是2017年发表的文章&#xff0c;一作是何恺明大神&#xff0c;没错就…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...