kafka初识

news2024/10/5 20:26:39

安装kafka

下载

下载window的kafka地址

window的kafka只是为了方便学习

安装地址:kafka.apache.org/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D9ioszWL-1674737826993)(./assets/image-20230107213923144.png)]

安装

解压zip为文件夹

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q6wfA4io-1674737804952)(./assets/image-20230107215808252.png)]

启动kafka

kafka服务器的功能相当于RocketMQ中的broker,kafka运行还需要一个类似于命名服务器的服务。在kafka安装目录中自带一个类似于命名服务器的工具,叫做zookeeper,它的作用是注册中心,相关知识请到对应课程中学习。

zookeeper-server-start.bat ..\..\config\zookeeper.properties
//启动zookeeper

kafka-server-start.bat ..\..\config\server.properties
//启动kafka

先启动zookeeper在启动kafka

管理员方式打开cmd,启动zookeeper之后启动kafka

运行bin目录下的windows目录下的zookeeper-server-start命令即可启动注册中心,默认对外服务端口2181

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ok4a1hws-1674737804953)(./assets/image-20230107220157426.png)]

另启动一个cmd窗口启动kafka

运行bin目录下的windows目录下的kafka-server-start命令即可启动kafka服务器,默认对外服务端口9092

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PHT63lxj-1674737804953)(./assets/image-20230107220308314.png)]

kafka命令操作

**注意根据kafka的版本来决定操作命令 版本,操作系统window或Linux等都决定kafka的命令操作 **

运行bin目录下的windows目录下的zookeeper-server-start命令即可启动注册中心,默认对外服务端口2181

运行bin目录下的windows目录下的kafka-server-start命令即可启动kafka服务器,默认对外服务端口9092

和之前操作其他MQ产品相似,kakfa也是基于主题操作,操作之前需要先初始化topic

# 创建topic
kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic qil0820
# 查询topic
kafka-topics.bat --zookeeper 127.0.0.1:2181 --list					
# 删除topic
kafka-topics.bat --delete --zookeeper localhost:2181 --topic qil0820

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jYqhUdKe-1674737804954)(./assets/image-20230107222744167.png)]

windows下的kafka命令操作

创建一个生产者,在订阅一个消费者来进行监听消费

kafka-console-producer.bat --broker-list localhost:9092 --topic wslKafkaTest						
# 测试生产消息
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic wslKafkaTest --from-beginning	
# 测试消息消费

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3TqizZ8T-1674737804954)(./assets/image-20230108010849106.png)]

kafkaTool

Offset Explorer (kafkatool.com)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oX4FsTaJ-1674737804955)(./assets/image-20230107224124314.png)]

在这里插入图片描述

kafka整合SpringBoot

案例

生产者向Topic中发送消息

消费者通过指定监听Topic来进行消费消息

        <!--kafka的依赖-->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>
spring: 
  kafka:
    bootstrap-servers: 127.0.0.1:9092
    consumer:
      group-id: wslKafkaTest // 设置默认的生产者消费者所属组id

生产者

@Api("Kafka生产者")
@RestController
public class KafkaProduceController {

    @Resource
    private KafkaTemplate<String,String> kafkaTemplate;

    @ApiOperation("测试kafka发送消息")
    @PostMapping("/sendMessage")
    public void sendMessage(String str) {
        System.out.println("待发送短信的订单已纳入处理队列(kafka),id:"+str);
        kafkaTemplate.send("wslKafkaTest",str);//使用send方法发送消息,需要传入topic名称
    }
    
}

消费者

@Component
public class MessageListener {

    @KafkaListener(topics = "wslKafkaTest")
    public void onMessage(ConsumerRecord<String,String> record){
        System.out.println("已完成短信发送业务(kafka),id:"+record.value());
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qn6pFXt-1674737804955)(./assets/image-20230108010617847.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A4s9HOkb-1674737804955)(./assets/image-20230108010624785.png)]

kafka的常见Boot配置

spring:
  application:
    name: hello-kafka
  kafka:
    listener:
      #设置是否批量消费,默认 single(单条),batch(批量)
      type: single
    # 集群地址
    bootstrap-servers: 127.0.0.1:9092,127.0.0.1:9093,127.0.0.1:9094
    # 生产者配置
    producer:
      # 重试次数
      retries: 3
      # 应答级别
      # acks=0 把消息发送到kafka就认为发送成功
      # acks=1 把消息发送到kafka leader分区,并且写入磁盘就认为发送成功
      # acks=all 把消息发送到kafka leader分区,并且leader分区的副本follower对消息进行了同步就任务发送成功
      acks: all
      # 批量处理的最大大小 单位 byte
      batch-size: 4096
      # 发送延时,当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
      buffer-memory: 33554432
      # 客户端ID
      client-id: hello-kafka
      # Key 序列化类
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      # Value 序列化类
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      # 消息压缩:none、lz4、gzip、snappy,默认为 none。
      compression-type: gzip
      properties:
        partitioner:
          #指定自定义分区器
          class: top.zysite.hello.kafka.partitioner.MyPartitioner
        linger:
          # 发送延时,当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
          ms: 1000
        max:
          block:
            # KafkaProducer.send() 和 partitionsFor() 方法的最长阻塞时间 单位 ms
            ms: 6000
    # 消费者配置
    consumer:
      # 默认消费者组
      group-id: testGroup
      # 自动提交 offset 默认 true
      enable-auto-commit: false
      # 自动提交的频率 单位 ms
      auto-commit-interval: 1000
      # 批量消费最大数量
      max-poll-records: 100
      # Key 反序列化类
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      # Value 反序列化类
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      # 当kafka中没有初始offset或offset超出范围时将自动重置offset
      # earliest:重置为分区中最小的offset
      # latest:重置为分区中最新的offset(消费分区中新产生的数据)
      # none:只要有一个分区不存在已提交的offset,就抛出异常
      auto-offset-reset: latest
      properties:
        interceptor:
          classes: top.zysite.hello.kafka.interceptor.MyConsumerInterceptor
        session:
          timeout:
            # session超时,超过这个时间consumer没有发送心跳,就会触发rebalance操作
            ms: 120000
        request:
          timeout:
            # 请求超时
            ms: 120000

kafka的常见工具类

生产者KafkaTemplate

// 根据生产者工厂构建kafkaTemplate
@Bean
public KafkaTemplate<String, String> kafkaTemplate(ProducerFactory<String, String> producerFactory) {
    KafkaTemplate<String, String> kafkaTemplate = new KafkaTemplate<>(producerFactory);
    return kafkaTemplate;
}

// 将一个生产者工厂注册到spring容器中
@Bean
public ProducerFactory<String, String> producerFactory() {
    return new DefaultKafkaProducerFactory<>(kafkaProperties.buildProducerProperties());
}

KafkaTemplate提供了几个发送消息的接口如下:

topic:指定要发送消息的topic名称

partition:向指定的partition发送消息

key:消息的key

data:消息的data

timestamp:时间信息,一般默认为当前时间

record:ProducerRecord结构,是对key和value的一层封装,直接发送key和value,也会在内部被封装成ProducerRecord然后再发送出去

message:包含消息头(topic、partition、字符集)等信息和消息的封装格式

// 向默认的topic发送消息
public ListenableFuture<SendResult<K, V>> sendDefault(V data);
public ListenableFuture<SendResult<K, V>> sendDefault(K key, V data);
public ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, K key, V data);
public ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, Long timestamp, K key, V data);
// 向指定的topic发送消息
public ListenableFuture<SendResult<K, V>> send(String topic, V data);
public ListenableFuture<SendResult<K, V>> send(String topic, K key, V data);
public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, V data);
public ListenableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key, V data);
public ListenableFuture<SendResult<K, V>> send(ProducerRecord<K, V> record);
public ListenableFuture<SendResult<K, V>> send(Message<?> message)

SpringKafka消费者

Kafka消息监听器MessageListener

Kafka的消息监听一般可以分为:1.单条数据监听;2.批量数据监听。GenericMessageListenerSpringKafka的消息监听器接口,也是一个函数式接口,利用接口的onMessage方法可以实现消费数据。

public interface GenericMessageListener<T> {
	void onMessage(T data);
    
	default void onMessage(T data, @Nullable Acknowledgment acknowledgment) {
		throw new UnsupportedOperationException("Container should never call this");
	}

	default void onMessage(T data, Consumer<?, ?> consumer) {
		throw new UnsupportedOperationException("Container should never call this");
	}

	default void onMessage(T data, @Nullable Acknowledgment acknowledgment, Consumer<?, ?> consumer) {
		throw new UnsupportedOperationException("Container should never call this");
	}

}

基于此接口可以实现单条数据消息监听器接口MessageListenen、多条数据消息监听器接口BatchMessageListener、带ACK机制的消息监听器AcknowledgingMessageListenerBatchAcknowledgingMessageListener

消息监听容器与容器工厂

消息监听器MessageListener是由消费监听器容器MessageListenerContainer接口来承载,使用setupMessageListenner()方法启动一个监听器。其中还有定义了操作消息的resume()pause()等方法。

public interface MessageListenerContainer extends SmartLifecycle {
    // 启动一个消息监听器
	void setupMessageListener(Object messageListener);
    // 获取消费者的指标信息
    Map<String, Map<MetricName, ? extends Metric>> metrics();
}

spring-kafka提供了两个容器KafkaMessageListenerContainerConcurrentMessageListenerContainer

消息监听器容器由容器工厂KafkaListenerContainerFactory统一创建并管理

public interface KafkaListenerContainerFactory<C extends MessageListenerContainer> {
    // 根据endpoint创建监听器容器
    C createListenerContainer(KafkaListenerEndpoint endpoint);
    // 根据topic、partition和offset的配置
	C createContainer(TopicPartitionOffset... topicPartitions);
	// 根据topic创建监听器容器
    C createContainer(String... topics);
    // 根据topic的正则表达式创建监听器容器
	C createContainer(Pattern topicPattern);
}

spring-kafka提供了监听器容器工厂ConcurrentKafkaListenerContainerFactory,其有两个重要的配置

ContainerPropertiesConsumerFactory

ContainerProperties定义了要消费消息的topic,消息处理的MessageListener等信息。

因此要实现一个消息监听器的流程如下:

非注解式消费监听器

SpringKafka的消费者是由一个消费监听器容器ListenerConatiner去承载的,容器对应一个配置文件为ContainerPropertiesContainerProperties继承自消费者配置类ConsumerProperties,并且承载了消息监听器的设置

首先介绍非注解式的消息监听器,类似于ProducerFactory,消费者需要创建一个ConsumerFactory

@Bean
public ConsumerFactory<String, String> consumerFactory() {
    return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties());
}

然后建立监听器容器工厂ConcurrentKafkaListenerContainerFactory

@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory());
    factory.setConcurrency(KafkaConsts.DEFAULT_PARTITION_NUM);
    factory.setBatchListener(true);
    factory.getContainerProperties().setPollTimeout(3000);
    return factory;
}

有了容器工厂之后,就可以通过注册bean的方式生成一个MessageListenerContainer

@Bean
public KafkaMessageListenerContainer<String, String> kafkaMessageListenerContainer(
    ConsumerFactory<String, String> consumerFactory) {
    ContainerProperties containerProperties = new ContainerProperties("numb");
    containerProperties.setMessageListener(
        (MessageListener<String, String>) data -> System.out.println("收到消息: " + data.value()));
    return new KafkaMessageListenerContainer(consumerFactory, containerProperties);
}

在这个kafkaMessageListenerContainer中,通过ContainerProperties配置了消费的topic和messageListener。之后启动项目后,spring会将kafkaMessageListenerContainer注册到ConcurrentKafkaListenerContainerFactory中,这样获取到数据后会自动调用消息监听器进行数据处理。

测试消费者消费数据

@Test
public void test_send_and_consume() {
    ExecutorService threadPool = Executors.newCachedThreadPool();
    threadPool.submit(() -> {
        while (true) {
            kafkaTemplate.send(KafkaConsts.TOPIC_TEST, UUID.randomUUID().toString(), "kv");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发送完成");
        }
    });
    while (true);
}

输出:

发送完成
发送成功
收到消息: kv
发送完成
发送成功
收到消息: kv

注解式消费监听器@KafkaListener

之前配置了容器监听器工厂ConcurrentKafkaListenerContainerFactory之后,还需要用代码配置MessageListenerContainer, 指定消费的topic、消息监听器处理等。其实上面这步完全可以通过注解@KafkaListener实现。

@Component
@Slf4j
public class MessageHandler {
    @KafkaListener(topics = KafkaConsts.TOPIC_TEST, containerFactory = "kafkaListenerContainerFactory", id = "consumer_numb"
        // , topicPartitions = { @TopicPartition(topic = "numb", partitionOffsets = {@PartitionOffset(partition = "0", initialOffset="1")})}
        )
    public void handleMessage(ConsumerRecord<String, String> record, Acknowledgment acknowledgment) {
        try {
            String message = (String) record.value();
            log.info("收到消息: {}", message);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            // 手动提交 offset
            acknowledgment.acknowledge();
        }
    }
}

@KafkaListener的主要属性

  • id:监听器的id - groupId:消费组id - idIsGroup:是否用id作为groupId,如果置为false,并指定groupId时,消费组ID使用groupId;否则默认为true,会使用监听器的id作为groupId - topics:指定要监听哪些topic(与topicPattern、topicPartitions 三选一) - topicPattern: 匹配Topic进行监听(与topics、topicPartitions 三选一) - topicPartitions: 显式分区分配,可以为监听器配置明确的主题和分区(以及可选的初始偏移量)
@KafkaListener(id = "thing2", topicPartitions =
        { @TopicPartition(topic = "topic1", partitions = { "0", "1" }),
          @TopicPartition(topic = "topic2", partitions = "0",
             partitionOffsets = @PartitionOffset(partition = "1", initialOffset = "100"))
        })

oupId - topics:指定要监听哪些topic(与topicPattern、topicPartitions 三选一) - topicPattern: 匹配Topic进行监听(与topics、topicPartitions 三选一) - topicPartitions: 显式分区分配,可以为监听器配置明确的主题和分区(以及可选的初始偏移量)

@KafkaListener(id = "thing2", topicPartitions =
        { @TopicPartition(topic = "topic1", partitions = { "0", "1" }),
          @TopicPartition(topic = "topic2", partitions = "0",
             partitionOffsets = @PartitionOffset(partition = "1", initialOffset = "100"))
        })

  • containerFactory:指定监听器容器工厂 - errorHandler: 监听异常处理器,配置BeanName - beanRef:真实监听容器的BeanName,需要在 BeanName前加 “__” - clientIdPrefix:消费者Id前缀 - concurrency: 覆盖容器工厂containerFactory的并发配置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/179951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows10添加WebDav地址时报错“输入的文件夹无效,请选择另一个”

一、问题描述在使用Windows10添加WebDav网络地址时&#xff0c;报错“输入的文件夹无效&#xff0c;请选择另一个”&#xff0c;如下图所示&#xff1a;二、问题分析这是由于Windows10的WebDav默认只支持https协议&#xff0c;没有支持http协议导致的。三、解决办法3.1、修改注…

计算机科学领域中里程牌式的算法

计算机科学中伟大的算法前言搜索引擎的索引PageRank公钥加密 --- 用明信片传输秘密纠错码数据压缩无损压缩有损压缩数据库 --- 追求一致性的历程事务和待办事项&#xff08;预写日志记录&#xff09;数字签名用挂锁签名用指数挂锁签名RSA的安全性前言 我肯定不是一位天文学专家…

redis 消息队列方案

redis 消息队列方案 观察角度&#xff1a;消息有序&#xff0c;重复消息处理&#xff0c;消息可靠性保证 pub/sub 发布订阅机制 list集合 消息有序&#xff1a;lpush和rpop可以保证消息顺序的被消费 重复消息处理&#xff1a;list没有为消息提供唯一标识&#xff0c;需要生产者…

利用matlab求解非线性目标函数

文章目录函数介绍设置函数参数步骤结果分析函数介绍 使用fmincon函数来进行求解&#xff0c;格式为 [x,y] fmincon(f,x0,A,b,Aeq,beq,lb,ub,nonlcon,options)其中&#xff0c;f表示所求的目标函数&#xff0c;x0表示初始值&#xff0c;A,b,Aeq,beq表示线性的约束&#xff0c…

SpringBoot06:整合JDBC、Druid、MyBatis

整合JDBC 1、创建一个新的工程&#xff0c;勾选JDBC API和MySQL Driver 2、导入web启动器 3、编写yaml配置文件&#xff0c;连接数据库 spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/mybatis?useUnicodetrue&characterEncodingut…

力扣 2299. 强密码检验器 II

题目 如果一个密码满足以下所有条件&#xff0c;我们称它是一个 强 密码&#xff1a; 它有至少 8 个字符。 至少包含 一个小写英文 字母。 至少包含 一个大写英文 字母。 至少包含 一个数字 。 至少包含 一个特殊字符 。特殊字符为&#xff1a;“!#$%^&*()-” 中的一个。…

面向对象编程范式

目录 1.概述 1.1.面向对象编程的核心诉求 1.2.面向对象的世界观 2.类和对象 3.如何寻找类和对象 3.1.概述 3.2.示例 4.如何表示类 4.1.什么是UML 4.1.关系 4.2.权限 4.3.依赖 4.4.泛化&#xff08;继承&#xff09; 4.5.实现 4.6.关联 4.7.聚合 4.8.组合 1.概…

自定义类型之枚举和联合

该文章将详细介绍除结构体外的另外两种自定义类型--------枚举类型与联合类型。1.枚举1.1枚举类型的定义1.2枚举的优点1.3枚举的使用2.联合&#xff08;共用体&#xff09;2.1联合类型的定义2.2联合的特点2.3联合大小的计算1.枚举 枚举顾名思义就是------一一列举。 把所有可能…

ADAS HiL系统测试方案

1、什么是ADAS ADAS&#xff08;Advanced Driving Assistance System&#xff09;也就是高级驾驶辅助系统&#xff0c;是无人驾驶的过渡。 ADAS利用安装在车上的各式各样传感器&#xff08;毫米波雷达、激光雷达、单\双目摄像头以及卫星导航&#xff09;&#xff0c;在汽车行驶…

[前端笔记——CSS] 12.处理不同方向文本

[前端笔记——CSS] 12.盒模型背景与边框1.书写模式2.书写模式、块级布局和内敛布局3.逻辑属性和逻辑值1.书写模式 CSS 中的书写模式是指文本的排列方向是横向还是纵向的。writing-mode 属性使我们从一种模式切换到另一种模式。例如&#xff0c;我们使用writing-mode: vertical…

stack、queue、priority_queue

容器适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该种模式是将一个类的接口转换成客户希望的另外一个接口。 其中stack和queue都是容器适配器&#xff0c;其中stack可以封装vector、list以及我们…

ffmpeg无损裁剪、合并视频

我用的版本是 ffmpeg version git-2020-06-23-ce297b4 官方文档 https://ffmpeg.org/ffmpeg-utils.html#time-duration-syntax 时间格式 [-][HH:]MM:SS[.m...] 或 [-]S[.m...][s|ms|us]裁剪视频 假设需要裁剪视频aaa.mp4&#xff0c;第5秒到第15秒 ffmpeg -ss 5 -to 15 -i…

使用gazebo对scara机械臂进行仿真

本文主要介绍如何仿真一个scara机械臂&#xff0c;以及在网上看了一些项目以后&#xff0c;有了一些感想&#xff0c;不想看的可以直接跳到机械臂部分。 目录感想(自己的理解&#xff0c;不一定对。)Scara机械臂的开发运动学计算如何使用机械臂工作图一个例子: 在start_pose抓起…

【Hadoop】MapReduce分布式计算实践(统计文本单词数量)

文章目录1. 前言2. Mapper代码3. Reducer代码4. Main代码5. 项目打包6. Hadoop运行7. 运行结果查看7.1 输出文件查看7.2 日志查看1. 前言 在博客【Hadoop】MapReduce原理剖析&#xff08;Map&#xff0c;Shuffle&#xff0c;Reduce三阶段&#xff09;中已经分析了MapReduce的运…

ASP.NET Core+Element+SQL Server开发校园图书管理系统(二)

随着技术的进步&#xff0c;跨平台开发已经成为了标配&#xff0c;在此大背景下&#xff0c;ASP.NET Core也应运而生。本文主要基于ASP.NET CoreElementSql Server开发一个校园图书管理系统为例&#xff0c;简述基于MVC三层架构开发的常见知识点&#xff0c;前一篇文章&#xf…

Linux C编程一站式学习笔记6

Linux C编程一站式学习笔记 chap6 循环结构 文章目录Linux C编程一站式学习笔记 chap6 循环结构一.while语句递归 VS 循环函数式编程&#xff08;Functional Programming&#xff09; & 命令式编程&#xff08;Imperative Programming&#xff09;无限递归 & 无限循环习…

光流估计(二) FlowNet 系列文章解读

在上篇文章中&#xff0c;我们学习并解了光流&#xff08;Optical Flow&#xff09;的一些基本概念和基本操作&#xff0c;但是传统的光流估计方法计算比较复杂、成本较高。近些年来随着CNN卷积神经网络的不断发展和成熟&#xff0c;其在各种计算机视觉任务中取得了巨大成功&am…

docker-基础实战第六课镜像挂载

镜像挂载: docker run --namemynginx -d --restartalways -p 8088:80 -v /usr/local/docker/data/html:/usr/share/nginx/html:ro nginx访问403 原因: /usr/local/docker/data/html 没有创建index.html 需要创建目录并且创建index.html docker命令补充&#xff1a; 如果有一…

向QAbstractItemView子类如:QTreeView、QTableView等子项单元格插入窗体小部件的功能实现(第1种方法)

1.前言 工作中经常会遇到这样的需求&#xff1a;向QAbstractItemView子类如QTreeView、QTableView单元格插入窗体小部件&#xff0c;如&#xff1a;进度条、按钮、单行编辑框等。下面链接的系列博文就是讲解如何实现该功能的。《向QAbstractItemView子类如:QTreeView、QTableVi…

Android音频播放有杂音?原来是这个JAVA API接口惹的祸

最近在调试一个基于十年前Android版本的多媒体应用软件时&#xff0c;遇到了音频播放的问题&#xff0c;这里记录问题的发现、分析和处理过程。 有人可能会好奇&#xff0c;十年前的Android版本是什么版本&#xff1f;大家可以去Google网站上查查&#xff0c;就是目前Android网…