Android下USB Accessory的实现分析

2023-11-02 04:20

本文主要是介绍Android下USB Accessory的实现分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android下USB Accessory的实现分析

 

摘要:本文介绍了USB Accessory的一些背景知识,并从Linux驱动到android Framework层,阐述了USB accessory的整个实现过程。

关键词: Android,USB,Accessory, ADK

1.  背景介绍

自Android 3.1之后的版本,Google引入了USB Accessories的概念,并提供了相关的开发库。下面是developer.android.com上的一段相关说明:

Android 3.1 Platform Highlights

http://developer.android.com/sdk/android-3.1-highlights.html

●     Connectivity for USBaccessories

Android 3.1 addsbroad platform support for a variety of USB-connected peripherals andaccessories. Users can attach many types of input devices (keyboards, mice,game controllers) and digital cameras. Applications can build on the platform’sUSB support to extend connectivity to almost any type of USB device.

The platformalso adds new support for USB accessories — external hardware devices designedto attach to Android-powered devices as USB hosts. When an accessory isattached, the framework will look for a corresponding application and offer tolaunch it for the user. The accessory can also present a URL to the user, fordownloading an appropriate application if one is not already installed. Userscan interact with the application to control powered accessories such asrobotics controllers; docking stations; diagnostic and musical equipment;kiosks; card readers; and much more.

The platform’sUSB capabilities rely on components in device hardware, so support for USB onspecific devices may vary and is determined by device manufacturers.

 

上面一段文字说得比较清楚:Android3.1之后的版本不仅可以让Android设备作为USB Host的角色支持USB鼠标、键盘、游戏手柄等,还可以以USB Device的角色与一些具有USB Host功能,但却扮演着配件角色的设备相连,Google把这种设备称为“Accessory”(附件)。这类Accessory可能是如下设备:

机器人控制器、Dock(基座)、诊断设备、音响设备、配电设备、读卡器等等。

Google引入USB Accessory概念的原因应该主要有如下:

一、非常多的Android设备不具有USB Host的功能而只具有USB Device功能(例如绝大部分Android手机),或者即使具备USB Host的功能,也承担不起对USB外设供电的任务,因为便携式Android设备本身的电池容量就很有限。

二、原来的Android设备,作为USB Device所实现的功能相对比较简单,内置的功能只有U盘或ADB调试设备等,Google希望提供应用层的USB开发库,让更多的软硬件厂商来开发新的功能,比如说安装一个APK应用,然后通过USB连接到一个与电视机配套的Dock上,就可以让一台Android手机变身为一个电视机遥控器。

图1-1显示了USB Host和Accessory两种不同模式的区别。当Android设备作为Host模式时为USB总线供电;当Android设备连接到一个USB Accessory设备时,USB Accessory设备以Host身份为USB总线供电。


图1-1 USB Host和Accessory两种模式

 

如果要在Android 3.1之前的Android 2.3.4版本上支持USB Accessory功能,可以通过添加“com.android.future.usb”add-on library的方式来支持,详情可见:http://developer.android.com/guide/topics/connectivity/usb/accessory.html

另外Android 4.1后的版本还增加了对USB AudioAccessory设备的支持,可实现USB接口的音箱功能,并在Google I/O 2012开发者年会上作了产品展示,见如下链接:

http://www.engadget.com/2012/06/30/gear4-speaker-dock-supports-usb-audio-for-jelly-bean-at-google-i/


图1-2 USB Audio Accessory设备在Google I/O 2012上的展示

对于硬件开发者如何开发出USB Accessory设备,Google官方也给出了指导,并提供了ADK(The Accessory Development Kit)开发环境,详情见:

http://source.android.com/accessories/

http://developer.android.com/tools/adk/index.html

在Google官方文档的指导下,硬件设计爱好者可以很方便地在Arduino(一个开源硬件平台)等硬件平台上开发出自己感兴趣的Usb Accessory设备。

本文在此不准备深入探讨如何在其他硬件平台上开发Usb Accessory设备,而是旨在分析Android平台下对USB Accessory设备的支持具体是如何实现的。这里选择了比较主流的Android 4.1平台进行分析(事实上Android 4.1之后这部分代码变化不大)。

2.Android下Usb Accessory的设计实现

Android下对Usb Accessory设备的支持包括内核驱动层的支持和Android Framework层的支持,如下是设计架构图:


图2-1 USB Accessory设计架构图

下面就分别从linux内核驱动,Android Framework层,以及相关的应用层代码来进行分析。

2.1 USB Accessory底层驱动的设计实现

2.1.1 什么是USB composite设备

因为目前的Android平台设备在与PC进行连接时,大都表现为USB Composite设备,因此这里有必要对USB Composite设备进行一下介绍。

什么是USB Composite设备呢?对于大部分USB Device设备来说,它仅仅只有一个功能,比如大部分U盘,单个的USB鼠标等;但是也有些USB设备不止实现一个功能,比如某些USB上网卡有无线上网的功能,同时还有U盘存储的功能;又比如有的鼠标和键盘二合一设备,它只有一个USB接口,却同时支持了鼠标和键盘两个功能。

这种一个USB接口扩展出多个设备功能的实现方法有两种,一种是在设备外部或内部加Hub扩展;另一种就是以Usb Composite Device方式实现(一般称为复合设备)。复合设备其实只是一个USB设备,只有一个USB设备地址,它实现多个功能的原因主要在于它扩展实现了多个USB接口,每个接口具有不同的设备类型。这里涉及到USB协议的一些知识,有兴趣的读者可以去找资料了解一下,这里我们重点是要知道Android下采用了USB Composite Device这种方式来实现一个USB口的情况下扩展出多个功能设备,这种情况下一个USB接口(Interface)便对应一种类型的功能设备,需要实现与之对应的功能驱动。

 

图2-2 USB Composite Device描述符结构

 

2.1.2 Android下USB Composite设备驱动实现

Android下的USBComposite驱动实现代码在Linux内核的drivers/usb/gadget/目录下,编译出来的驱动模块为g_android.ko,主要相关的代码有android.c和f_xxx.c一类的接口驱动文件,包括实现USB Accessory驱动的f_accessory.c,f_audio_source.c文件等。f_xxx.c以从属于android.c的USB Composite设备接口驱动的形式被include在android.c文件中,见android.c代码:

...

#include "f_audio_source.c"

#include "f_mass_storage.c"

#include "u_serial.c"

#include "f_acm.c"

#include "f_adb.c"

#include "f_mtp.c"

#include "f_accessory.c"

#include "f_rndis.c"

#include "rndis.c"

#include "u_ether.c"

...

代码组织架构如下:


图2-3 Android下USB Composite设备驱动代码架构

 

同时,在android.c中,定义了一个非常重要的数据结构的指针数组,见如下:

static struct android_usb_function *supported_functions[] = {

    &adb_function,

    &acm_function,

    &mtp_function,

    &ptp_function,

    &rndis_function,

    &mass_storage_function,

    &accessory_function,

    &audio_source_function,

    NULL

};

从上我们可以看到Android设备上每一个所支持的USB Device功能都可以在其中看到定义,包括如下功能:

●          adb(USB调试功能);

●          acm(USB串口功能);

●          mtp(mtp多媒体设备功能);

●          ptp(数码相机一类的多媒体存储设备功能);

●          rndis(USB Net功能);

●          mass_storage(这个就是我们最常见的U盘功能);

●          accessory(USB Accessory功能);

●          audio_source(USB Audio Accessory功能);

    structandroid_usb_function是为实现USB Composite设备的每个Interface功能所定义的数据结构,其中包括init功能初始化接口,enable和bin_config接口等,这些接口的实现在对应的f_xxx.c文件中。structandroid_usb_function结构定义如下:

struct android_usb_function {

    char *name;

    void *config;

 

    struct device *dev;

    char *dev_name;

    struct device_attribute **attributes;

 

    /* for android_dev.enabled_functions */

    struct list_head enabled_list;

 

    /* Optional: initialization during gadget bind */

    int (*init)(struct android_usb_function *, struct usb_composite_dev *);

    /* Optional: cleanup during gadget unbind */

    void (*cleanup)(struct android_usb_function *);

    /* Optional: called when the function is added the list of

     *      enabled functions */

    void (*enable)(struct android_usb_function *);

    /* Optional: called when it is removed */

    void (*disable)(struct android_usb_function *);

 

    int (*bind_config)(struct android_usb_function *,

               struct usb_configuration *);

 

    /* Optional: called when the configuration is removed */

    void (*unbind_config)(struct android_usb_function *,

                  struct usb_configuration *);

    /* Optional: handle ctrl requests before the device is configured */

    int (*ctrlrequest)(struct android_usb_function *,

                    struct usb_composite_dev *,

                    const struct usb_ctrlrequest *);

};

 

    g_android.ko驱动内部涉及到的主要数据结构和相互之间关系见如下图示说明:


图2-4 g_android.ko主要数据结构和引用关系

    在g_android.ko驱动初始化的时候,会调用usb_composite_probe,进而调用usb_gadget_probe_driver来向udc-core注册一个usb_gadget_driver(即composite_driver),并与当前的udc(USB Device Controller)关联起来。

    作为一个USB Gadget功能驱动,g_android.ko工作的主要流程和普通的USB Gadget功能驱动类似,主要区别的地方在于它内部定义了很多android_usb_function结构体,并维护了一个usb_composite_dev数据结构,在需要的时候可以从supported_functions中选择需要的function链接到android_dev的enabled_functions链表上,并添加信息到usb_composite_dev的usb configuration中。

2.1.3 如何使能某一Interface功能

    前面讲到g_android驱动包含了对USB Accessory功能在内的众多USB功能Interface的支持,那么具体是如何提供接口供应用层来选择使能的呢?其实在g_android驱动初始化的时候,已经通过调用device_create(android_class, dev->dev,MKDEV(0, index), f,f->dev_name),创建了"/sys/class/android_usb/android0"目录和下面的众多文件节点,包括functions和enable文件节点,对应的功能Interface正是通过往此文件节点写入信息来实现的。下面摘取Android系统下init.usb.rc中相关代码片段来举例说明:

......

# adb only USB configuration

# This should only be used during device bringup

# and as a fallback if the USB manager fails to set a standard configuration

on property:sys.usb.config=adb

    write /sys/class/android_usb/android0/enable 0

    write /sys/class/android_usb/android0/idVendor 18d1

    write /sys/class/android_usb/android0/idProduct D002

    write /sys/class/android_usb/android0/functions ${sys.usb.config}

    write /sys/class/android_usb/android0/enable 1

    start adbd

    setprop sys.usb.state ${sys.usb.config}

 

# USB accessory configuration

on property:sys.usb.config=accessory

    write /sys/class/android_usb/android0/enable 0

    write /sys/class/android_usb/android0/idVendor 18d1

    write /sys/class/android_usb/android0/idProduct 2d00

    write /sys/class/android_usb/android0/functions ${sys.usb.config}

    write /sys/class/android_usb/android0/enable 1

    setprop sys.usb.state ${sys.usb.config}

 

# USB accessory configuration, with adb

on property:sys.usb.config=accessory,adb

    write /sys/class/android_usb/android0/enable 0

    write /sys/class/android_usb/android0/idVendor 18d1

    write /sys/class/android_usb/android0/idProduct 2d01

    write /sys/class/android_usb/android0/functions ${sys.usb.config}

    write /sys/class/android_usb/android0/enable 1

    start adbd

    setprop sys.usb.state ${sys.usb.config}

......

    从上面我们可以看到,当“sys.usb.config”属性值发生变化时候,属性服务进程会针对不同的设置来向“/sys/class/android_usb/android0/”目录下的文件节点写入对应的配置值,比如当“sys.usb.config”设置为accessory时,会执行如下动作:

    write /sys/class/android_usb/android0/enable 0

    write /sys/class/android_usb/android0/idVendor 18d1

    write /sys/class/android_usb/android0/idProduct 2d00

    write /sys/class/android_usb/android0/functions ${sys.usb.config}

    write /sys/class/android_usb/android0/enable 1

setprop sys.usb.state ${sys.usb.config}

    其中idVendor和idProduct节点记录了设备的VID和PID号,functions节点记录了当前设置使能的功能(比如accessory),enable节点写入1的时候,驱动内部会调用android_enable函数,真正使能并让USB Host端识别到对应的USB功能设备。

2.1.4 Android Open AccessoryProtocol

为了支持USB Accessory,让USB主从设备双方能够互相识别和兼容对方,Google定义了一套Android OpenAccessory Protocol(简称AOA),此协议目前有两个版本:Version 1.0和Version2.0。2.0版本是对对1.0版本的补充,增加了对Audio和HID类Accessory设备的支持。具体协议内容,可参见如下链接:

http://source.android.com/accessories/aoa.html

http://source.android.com/accessories/aoa2.html

协议内容其实比较简单,主要是定义了一套USB控制传输命令,Accessory设备可以发起这些控制传输命令来获取和设置对应的Accessory功能。另外,在Accessory模式下,USB Device端上报的VID和PID是Google指定的,VID固定为Google的官方VID -- 0x18D1,PID则在不同的模式下定义如下:

●          0x2D00 - accessory

●          0x2D01 - accessory + adb

●          0x2D02 - audio

●          0x2D03 - audio + adb

●          0x2D04 - accessory + audio

●          0x2D05 - accessory + audio + adb

 

USB Accessory设备和Android设备两者双方整个枚举识别工作过程如下:

一、USB Accessory设备发起USB控制传输进行正常的USB设备枚举,获取设备描述符和配置描述符等信息,此时大部分Android设备上报的还只是普通的U盘或MTP设备;

二、接下来USB Accessory设备发起Vendor类型,request值为51(0x33)的控制传输命令(ACCESSORY_GET_PROTOCOL),看看该Android设备是否支持USB Accessory功能,如果支持的话会返回所支持的AOA协议版本;

三、USB Accessory判断到该Android设备支持Accessory功能后,发起request值为52(0x34)的控制传输命令(ACCESSORY_SEND_STRING),并把该Accessory设备的相关信息(包括厂家,序列号等)告知Android设备;

四、如果是USB Audio Accessory设备,还会发起request值为58(0x3A)的控制传输命令(SET_AUDIO_MODE命令),通知Android设备进入到Audio Accessory模式;

    五、最终,USB Accessory设备发起request值为53(0x35)的控制传输命令(ACCESSORY_START),通知Android设备切换到Accessory功能模式开始工作。接下来Android设备收到此信息后,会先把sys.usb.config设置为包含accessory功能;

六、剩下的事情就是如前小节所述,按init.usb.rc的设置进行了,此时Android设备先断开与Accessory设备连接,/sys/class/android_usb/android0/functions节点写入accessory字符串,然后重新连接,使能Accessory接口,正式工作在USB Accessory模式;

下图是USB协议分析仪抓取的协议数据:


图2-5 AOA协议通讯过程

    AOA 2.0协议中还有部分和HID设备有关的控制传输命令,具体可参见前面的Google官方链接,这里不再详叙。

2.1.5 f_accessory.c和f_audio_source.c驱动文件

f_accessory.c和f_audio_source.c文件为实现USB Accessory和USB Audio Accessory功能所对应的驱动代码。我们先看f_accessory.c文件的处理内容。

f_accessory.c文件内所实现的功能主要有如下:

1、负责accessory_function结构体中接口函数的具体实现,这些接口函数见如下代码:

static struct android_usb_function accessory_function = {

    .name       = "accessory",

    .init       = accessory_function_init,

    .cleanup    = accessory_function_cleanup,

    .bind_config    = accessory_function_bind_config,

    .ctrlrequest    = accessory_function_ctrlrequest,

};

 

2、实现Android Open Accessory (AOA) protocol。AOA协议的相关处理代码主要在控制传输处理代码中,具体可见acc_ctrlrequest函数:

static int acc_ctrlrequest(struct usb_composite_dev *cdev,

                const struct usb_ctrlrequest *ctrl)

{

    struct acc_dev  *dev = _acc_dev;

    int value = -EOPNOTSUPP;

    struct acc_hid_dev *hid;

    int offset;

    u8 b_requestType = ctrl->bRequestType;

    u8 b_request = ctrl->bRequest;

    u16 w_index = le16_to_cpu(ctrl->wIndex);

    u16 w_value = le16_to_cpu(ctrl->wValue);

    u16 w_length = le16_to_cpu(ctrl->wLength);

    unsigned long flags;

 

    if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) {

        if (b_request == ACCESSORY_START) {

            dev->start_requested = 1;

            schedule_delayed_work(&dev->start_work, msecs_to_jiffies(10));

            value = 0;

        } else if (b_request == ACCESSORY_SEND_STRING) {

            dev->string_index = w_index;

            cdev->gadget->ep0->driver_data = dev;

            cdev->req->complete = acc_complete_set_string;

            value = w_length;

        } else if (b_request == ACCESSORY_SET_AUDIO_MODE &&

                w_index == 0 && w_length == 0) {

            dev->audio_mode = w_value;

            DAMN_WARN_ACC_0906("audio_mode=%d\n",dev->audio_mode);

            value = 0;

        } else if (b_request == ACCESSORY_REGISTER_HID) {

        ......

以“ACCESSORY_START”命令为例,收到该命令后驱动会调用schedule_delayed_work(&dev->start_work,msecs_to_jiffies(10))来延时10毫秒后发出"ACCESSORY=START"的UEVENT消息,dev->start_work对应的处理函数如下:

static void acc_start_work(struct work_struct *data)

{

    char *envp[2] = { "ACCESSORY=START", NULL };

    kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp);

}

 

3、注册实现字符设备“/dev/usb_accessory”相关的驱动接口,并调用misc_register(&acc_device)注册该字符设备驱动,对应的file_operations结构体定义如下:

/* file operations for /dev/acc_usb */

static const struct file_operations acc_fops = {

    .owner = THIS_MODULE,

    .read = acc_read,

    .write = acc_write,

    .unlocked_ioctl = acc_ioctl,

    .open = acc_open,

    .release = acc_release,

};

“/dev/usb_accessory”向应用层提供了read、write和ioctl接口,通过这些接口,应用层可以获取Accessory设备信息,实现Android设备与Accessory设备的数据交互工作。

 

4、定义hid_driver,并调用hid_register_driver(&acc_hid_driver)向内核注册:

static struct hid_driver acc_hid_driver = {

    .name = "USB accessory",

    .id_table = acc_hid_table,

    .probe = acc_hid_probe,

};

并在收到控制传输命令ACCESSORY_REGISTER_HID和ACCESSORY_UNREGISTER_HID时,调用acc_register_hid和acc_unregister_hid进行相应处理。

 

以上是f_accessory.c文件主要的处理内容,f_audio_source.c文件则针对Audio Accessory设备来进行处理,实现了音频相关接口函数,并调用snd_card_register(card)函数向内核注册声卡设备。其中snd_pcm_ops结构体定义如下:

static struct snd_pcm_ops audio_playback_ops = {

    .open       = audio_pcm_open,

    .close      = audio_pcm_close,

    .ioctl      = snd_pcm_lib_ioctl,

    .hw_params  = audio_pcm_hw_params,

    .hw_free    = audio_pcm_hw_free,

    .prepare    = audio_pcm_prepare,

    .trigger    = audio_pcm_playback_trigger,

    .pointer    = audio_pcm_pointer,

};

f_audio_source.c文件主要涉及音频处理相关代码,这里不再深入研究。

接下来,我们再看在连接到USB Accessory设备时Android上层的整个工作流程。

 

2.2 Android上层对USBAccessory设备的处理流程

下图是g_android.ko驱动检测到USB Accessory设备之后大致的处理流程:


图2-6Android下USB Accessory设备连接后处理流程

首先g_android.ko驱动发现USB Accessory设备发送了“ACCESSORY_START”控制传输命令后,会发出一个UEVENT消息("ACCESSORY=START")。

    UsbDeviceMannager内部实现了一个UEventObserver类成员(mUEventObserver),会监测usb_accessory相关的UEVENT信息,当收到到"ACCESSORY=START"信息时,开始重新设置sys.usb.config属性,并由后台的property service进程执行由init.usb.rc文件所指定的设置动作,往“/sys/class/android_usb/android0/”路径下写入对应的配置信息。

    配置完成后,“/sys/class/android_usb/android0/enable”文件会被写入“1”,Android设备切换打开USB Accessory功能接口,重新执行枚举动作。枚举完成后,再发出UEVENT信息“USB_STATE=CONFIGURED”。

    接下来该UEVENT信息再次被mUEventObserver所监测到,并经过几次调用,最终由UsbSettingsManager发出定义为USB_ACCESSORY_ATTACHED的Intent。如果安装有相应的应用程序,则该Intent则会激活对应的Activity开始执行。

    Framework中相关的代码路径如下:

Ø        frameworks\base\services\Java\com\android\server\usb

Ø        frameworks\base\packages\SystemUI\src\com\android\systemui\usb

Ø        frameworks\base\services\jni\ com_android_server_UsbDeviceManager.cpp

 

Android对外提供的Accessory设备编程接口见如下package:

"com.android.future.usb"

“android.hardware.usb”

对应的代码路径如下:

Ø        frameworks\base\libs\usb\src\com\android\future\usb

Ø        frameworks\base\core\java\android\hardware\usb

 

至于如何编写应用与Accessory设备交互,可参见:

http://developer.android.com/guide/topics/connectivity/usb/accessory.html

Google官方也提供了示例代码,在源代码包的如下路径:

Ø        device\google\accessory\demokit\app\src\com\google\android\DemoKit

3. 小结

Android系统对USBAccessory设备的支持,为扩展Android平台的外围设备功能,吸引更多的硬件厂商和开发者开发出更多有趣和有创新意义的应用产品提供了机会,也会让整个Android阵营变得更加丰富多彩。本文从USB功能驱动到Android Framework层对USB Accessory的整个工作流程作了大致的说明,希望有助于有兴趣的开发者更好地理解USB Accessory的工作原理和流程。

这篇关于Android下USB Accessory的实现分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/328064

相关文章

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

C#实现文件读写到SQLite数据库

《C#实现文件读写到SQLite数据库》这篇文章主要为大家详细介绍了使用C#将文件读写到SQLite数据库的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录1. 使用 BLOB 存储文件2. 存储文件路径3. 分块存储文件《文件读写到SQLite数据库China编程的方法》博客中,介绍了文

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

JAVA利用顺序表实现“杨辉三角”的思路及代码示例

《JAVA利用顺序表实现“杨辉三角”的思路及代码示例》杨辉三角形是中国古代数学的杰出研究成果之一,是我国北宋数学家贾宪于1050年首先发现并使用的,:本文主要介绍JAVA利用顺序表实现杨辉三角的思... 目录一:“杨辉三角”题目链接二:题解代码:三:题解思路:总结一:“杨辉三角”题目链接题目链接:点击这里

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

SpringBoot实现基于URL和IP的访问频率限制

《SpringBoot实现基于URL和IP的访问频率限制》在现代Web应用中,接口被恶意刷新或暴力请求是一种常见的攻击手段,为了保护系统资源,需要对接口的访问频率进行限制,下面我们就来看看如何使用... 目录1. 引言2. 项目依赖3. 配置 Redis4. 创建拦截器5. 注册拦截器6. 创建控制器8.