zoukankan      html  css  js  c++  java
  • Spring Cloud体系及使用

    SpringCloud体系介绍

    Spring Cloud Netflix

    • Netflix Eureka:服务治理组件,包含服务注册与发现
    • Netflix Ribbon:客户端负载均衡的服务调用组件
    • Netflix Hystrix:容错管理组件,实现了熔断器
    • Netflix Feign:基于Ribbon和Hystrix的声明式、模板化的HTTP服务调用组件
    • Netflix Zuul:网关组件,提供智能路由、访问过滤等功能
    • Netflix hystrix-dashboard:单个服务监控
    • Netflix Turbine:Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况。
    • Netflix Archaius:外部化配置组件
    • ...

    Spring Cloud

    • Spring Cloud Config:配置管理工具,实现应用配置的外部化存储,支持客户端配置信息刷新、加密/解密配置内容等。
    • Spring Cloud Bus:事件、消息总线,用于传播集群中的状态变化或事件,以及触发后续的处理
    • Spring Cloud Security:基于spring security的安全工具包,为我们的应用程序添加安全控制
    • Spring Cloud Consul:封装了Consul操作,Consul是一个服务发现与配置工具(与Eureka作用类似),与Docker容器可以无缝集成
    • Spring Cloud OAuth2:认证鉴权
    • Spring Cloud Task:提供云端计划任务管理、任务调度。
    • Spring Cloud Sleuth:日志收集工具包,封装了Dapper和log-based追踪以及Zipkin和HTrace操作,为SpringCloud应用实现了一种分布式追踪解决方案。
    • Spring Cloud for Cloud Foundry:通过Oauth2协议绑定服务到CloudFoundry,CloudFoundry是VMware推出的开源PaaS云平台。
    • Spring Cloud Cluster:提供Leadership选举
    • Spring Cloud Data Flow:大数据操作工具,作为Spring XD的替代产品,它是一个混合计算模型,结合了流数据与批量数据的处理方式。
    • Spring Cloud Stream:数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。
    • Spring Cloud Zookeeper:操作Zookeeper的工具包,用于使用zookeeper方式的服务发现和配置管理。
    • Spring Cloud Connectors:便于云端应用程序在各种PaaS平台连接到后端,如:数据库和消息代理服务。
    • Spring Cloud Starters:Spring Boot式的启动项目,为Spring Cloud提供开箱即用的依赖管理。
    • Spring Cloud CLI:基于 Spring Boot CLI,可以让你以命令行方式快速建立云组件。

    Eureka注册中心

    创建spring-cloud工程

    构建一个Maven项目:spring-cloud-demo
    在pom.xml文件中加入:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.20.RELEASE</version>
    </parent>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.SR4</spring-cloud.version>
        <spring-boot.version>1.5.20.RELEASE</spring-boot.version>
    </properties>
    
    <!--对子模块依赖包的版本统一控制,子模块需要显示引用,子模块也可以重新声明版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    

    搭建服务注册中心Eureka服务

    在spring-cloud-demo工程中添加module:eureka-server
    在pom.xml文件中加入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>
    

    在resource目录下创建文件:application.properties,内容如下:

    ################### Eureka服务端配置 ####################
    server.port=8761
    eureka.instance.hostname=localhost
    #不向注册中心注册自己
    eureka.client.register-with-eureka=false
    #由于注册中心的职责就是维护服务实例,它并不需要去检索服务, 所以也设置为false
    eureka.client.fetch-registry=false
    eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
    

    创建一个启动类:EurekaServerApplication.java,添加如下内容:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    }
    

    运行EurekaServerApplication类中的main方法,由此Eureka服务端搭建完成
    在浏览器中访问Eureka服务端:http://localhost:8761/

    服务注册与发现:搭建Eureka服务注册者

    在spring-cloud-demo工程中添加module:demo-service
    在pom.xml文件中加入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>
    

    在resource目录下创建文件:application.properties,内容如下:

    ########## Eureka客户端配置 ############
    server.port=9806
    spring.application.name=demo-service
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
    

    创建一个启动类:DemoServiceApplication.java,添加如下内容:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    public class DemoServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoServiceApplication.class, args);
        }
    }
    

    运行DemoServiceApplication类中的main方法,由此Eureka注册者搭建完成
    在浏览器中访问Eureka服务端:http://localhost:8761/,发现demo-service已经注册进来了

    添加一个Controller:PortController.java,添加如下内容:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class PortController {
    
        @Value("${server.port}")
        private String port;
    
        @RequestMapping("/port")
        public String getPort() {
            return "I am demo-service, I'm from port : " + port;
        }
    }
    

    重新运行eureka-client项目,即EurekaClientApplication类中的main方法
    在浏览器中访问:http://localhost:9806/port,即输出:I am demo-service, I'm from port : 9806

    高可用注册中心

    在eureka-server工程的resource目录下创建两个配置文件
    application-peer1.properties

    ################### Eureka服务端配置 ####################
    spring.application.name=eureka-server
    server.port=8761
    eureka.instance.hostname=peer1
    #不向注册中心注册自己
    eureka.client.register-with-eureka=false
    #由于注册中心的职责就是维护服务实例,它并不需要去检索服务, 所以也设置为false
    eureka.client.fetch-registry=false
    #高可用配置:注册到其他配置中心
    eureka.client.serviceUrl.defaultZone=http://peer2:8762/eureka/
    

    application-peer1.properties

    ################### Eureka服务端配置 ####################
    spring.application.name=eureka-server
    server.port=8761
    eureka.instance.hostname=peer2
    #不向注册中心注册自己
    eureka.client.register-with-eureka=false
    #由于注册中心的职责就是维护服务实例,它并不需要去检索服务, 所以也设置为false
    eureka.client.fetch-registry=false
    #高可用配置:注册到其他配置中心
    eureka.client.serviceUrl.defaultZone=http://peer1:8761/eureka/
    

    编辑系统中C:WindowsSystem32driversetc目录下的hosts文件,添加如下内容:

    	127.0.0.1       peer1
    	127.0.0.1       peer2
    

    将eureka-server工程打成jar包,然后用java -jar 命名运行eureka-server.jar两次并指定不同的配置文件:

    java -jar eureka-server-0.0.1.jar --spring.profiles.active=peer1
    java -jar eureka-server-0.0.1.jar --spring.profiles.active=peer2
    

    后台运行

    nohup java -jar eureka-server-0.0.1.jar --spring.profiles.active=peer1 >./log/eureka-1.log &
    

    然后访问:http://localhost:8761/,即发现DS Replicas项下复制项:peer2
    然后访问:http://localhost:8762/,即发现DS Replicas项下复制项:peer1
    即将注册中心注册到其他注册中心上去,达到注册中心高可用的目的

    服务注册者注册到Eureka集群中配置:

    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/
    

    Ribbon负载均衡

    服务消费与Ribbon负载均衡

    在spring-cloud-demo工程中添加module:demo-service-ribbon
    在pom.xml文件中加入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
    
    </dependencies>
    

    在resource目录下增加application.properties文件,添加如下内容:

    ################## 服务配置 ###############
    server.port=9136
    spring.application.name=demo-service-ribbon
    #注册到注册中心
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/
    

    创建一个启动类:ServiceRibbonApplication.java,添加如下内容:

    @SpringBootApplication
    @EnableDiscoveryClient
    public class ServiceRibbonApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceRibbonApplication.class, args);
        }
    
        @Bean
        @LoadBalanced ////负载均衡配置
        RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    

    添加Service类:DemoRibbonService.java,添加如下内容:

    @Service
    public class DemoRibbonService {
    
        @Autowired
        RestTemplate restTemplate;
    
        public String port() {
            return restTemplate.getForObject("http://demo-service/port", String.class);
        }
    }
    

    添加Controller类:DemoRibbonController.java,添加如下内容:

    @RestController
    public class DemoRibbonController {
    
        @Autowired
        DemoRibbonService demoRibbonService;
    
        @RequestMapping("hello")
        public String port() {
            return demoRibbonService.port();
        }
    }
    

    分别使用9806、9807端口运行之前构建的demo-service工程
    在运行demo-service-ribbon项目,访问:http://localhost:9136/hello,显示如下:
    I am demo-service, I'm from port : 9806
    再次访问http://localhost:9136/hello,显示如下:
    I am demo-service, I'm from port : 9807

    说明demo-service-ribbon工程中加了@LoadBalanced注解的restTemplate在访问demo-service服务时使用了Ribbon的负载均衡功能

    Ribbon负载均衡策略配置

    Ribbon的负载均衡常用的策略有

    1. RandomRule:从服务列表中随机取一个服务实例

    2. RoundRobinRule:从服务列表中以轮询的方式取实例

    3. RetryRule:根据maxRetryMillis最大重试时间参数在获取服务实例失败时重试获取服务实例

    4. WeightedResponseTimeRule
      该策略是对RoundRobinRule的扩展, 增加了根据实例的运行情况来计算权重, 并根据权重来挑选实例, 以达到更优的分配效

    5. ClientConfigEnabledRoundRobinRule
      这个策略跟RoundRobinRule功能相同,其作用是可以继承这个策略重写choose()方法,当自定义的选择策略无法实施时可以用父类的策略作为备选方案

    6. BestAvailableRule
      该策略继承自ClientConfigEnabledRoundRobinRule, 在实现中它注入了负载均衡器的统计对象LoadBalancerStats , 同时在具体的choose 算法中利用LoadBalancerStats 保存的实例统计信息来选择满足要求的实例。从源码中看出, 它通过遍历负载均衡器中维护的所有服务实例,会过滤掉故障的实例, 并找出并发发请求数最小的一个, 所以该策略的特性是可选出最空闲的实例。

    7. PredicateBasedRule
      一个抽象策略,基于Google Guava Collection过滤条件接口Predicate实现的策略,基本实现的是“先过滤清单, 再轮询选择”

    8. AvailabilityFilteringRule
      基于PredicateBasedRule实现的,使用线性选择,符合条件就使用,不符合条件就找下一个,而不像父类需要遍历所有服务计算再选择

    9. ZoneAvoidanceRule
      待补充。。

    具体配置步骤

    以配置随机策略为例
    在启动类文件ServiceRibbonApplication.java中加入方法:

    //新增随机策略
    @Bean
    public IRule ribbonRule() {
        return new RandomRule();    //这里选择随机策略,对应配置文件
    }
    

    分别使用9806、9807端口运行之前构建的demo-service工程
    再运行demo-service-ribbon项目,访问:http://localhost:9136/hello,多访问几次就会发现返回的端口就是随机的了
    I am demo-service, I'm from port : 9806
    I am demo-service, I'm from port : 9807
    I am demo-service, I'm from port : 9806
    I am demo-service, I'm from port : 9806

    Feign声明式服务调用

    搭建服务环境

    在spring-cloud-demo工程中添加module:demo-feign-consumer
    在pom.xml文件中加入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
    </dependencies>
    

    在resource目录下增加application.properties文件,添加如下内容:

    ################## 服务配置 ###############
    server.port=9257
    spring.application.name=demo-feign-consumer
    #注册到注册中心
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/,http://localhost:8762/eureka/
    

    创建一个启动类:FeignConsumerApplication.java,添加如下内容:

    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    public class FeignConsumerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(FeignConsumerApplication.class, args);
        }
    }
    

    添加Service类:DemoFeignService.java,添加如下内容:

    @FeignClient(value = "demo-service")
    public interface DemoFeignService {
    
        @RequestMapping(value = "/port", method = RequestMethod.GET)
        String hello();
    }
    

    添加Controller类:DemoFeignController.java,添加如下内容:

    @RestController
    public class DemoFeignController {
        @Autowired
        DemoFeignService demoFeignService;
    
        @RequestMapping(value = "/hello", method = RequestMethod.GET)
        public String port() {
            return demoFeignService.hello();
        }
    }
    

    分别使用9806、9807端口运行之前构建的demo-service工程
    运行demo-feign-consumer项目,访问http://localhost:9257/hello,显示如下:
    I am demo-service, I'm from port : 9808
    如此实现了服务的声明式调用,多次访问按次序返回不同的端口,说明负载均衡已经实现,使用 默认使用的是轮询策略

    负载均衡配置

    在启动类文件FeignConsumerApplication.java中加入方法:
    //新增随机策略
    @Bean
    public IRule ribbonRule() {
    return new RandomRule(); //这里选择随机策略,对应配置文件
    }

    分别使用9806、9807端口运行之前构建的demo-service工程
    再运行demo-feign-consumer项目,访问:http://localhost:9257/hello,多访问几次就会发现返回的端口就是随机的了
    I am demo-service, I'm from port : 9806
    I am demo-service, I'm from port : 9806
    I am demo-service, I'm from port : 9806
    I am demo-service, I'm from port : 9807

    Hystrix服务熔断

    Hystrix是实现是作用是当被调用的服务不可用时实现调用的降级,比如返回一个友好提示

    feign使用hystrix配置

    如果使用的是feign声明式调用,那么feign默认集成了hystrix,只需要写feign客户端的fallbalk类和配置就可以了

    比如有一个xxxFeignClient,写一个xxxFeignClient的fallbalk类:

    @Component
    public class xxxFeignClientFallback implements xxxFeignClient {
    
    }
    

    在xxxFeignClient的@FeignClient()注解的括号中添加:

    fallback = xxxFeignClientFallback.class
    

    在调用的启动类上添加注解

    @EnableFeignClients
    

    如果xxxFeignClient不是在启动类的当前包或者当前包的子包下面,比如xxxFeignClient和它的fallbalk类放在com.xxx.client包下面,调用方的启动类放在com.xxx.ui包下面,则需要在掉用方的启动类上添加如下注解:

    @ComponentScan(basePackages = {"com.xxx.ui","com.xxx.client"})
    @EnableFeignClients(basePackages = {"com.xxx.client"})
    

    注意:basePackages指定之后项目启动时只会扫描指定basePackages指定的包的组件

    最后在调用方的application.properties文件中加入启用feign hystrix配置:

    feign.hystrix.enabled=true
    

    Config配置中心

    config服务端

    在spring-cloud-demo工程中添加module:config-server
    在pom.xml文件中加入依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    

    在resource目录下增加application.properties文件,添加如下内容:

    server.port=8888
    spring.application.name=config-server
    
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
    spring.profiles.active=native
    spring.cloud.config.server.native.searchLocations=classpath:/properties/
    

    在resource目录下创建目录:properties,在目录下增加文件:cloud-config-dev.properties,内容如下

    msg=hello, I'm from config-server
    

    在java目录下创建一个SpringBoot项目启动类:ConfigServer,内容如下:

    /**
     * @CalssName ConfigServer
     * @Description TODO
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableConfigServer
    public class ConfigServer {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigServer.class,args);
        }
    }
    

    config使用端

    在spring-cloud-demo工程中添加module:config-use
    在pom.xml文件中加入依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    

    在resource目录下增加application.properties文件,添加如下内容:

    server.port=9802
    spring.application.name=config-use
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
    
    spring.cloud.config.discovery.enabled=true
    spring.cloud.config.discovery.service-id=config-server
    spring.cloud.config.name=cloud-config
    spring.cloud.config.profile=${config.profile:dev}
    

    在java目录下创建一个SpringBoot项目启动类:ConfigUseApplication,内容如下:

    /**
     * @CalssName ConfigUse
     * @Description TODO
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class ConfigUseApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigUseApplication.class, args);
        }
    }
    

    创建一个Controller:HelloController,内容如下:

    /**
     * @CalssName HelloController
     * @Description TODO
     */
    @RestController
    public class HelloController {
        @Value("${msg}")
        private String msg;
    
        @RequestMapping("/msg")
        public String msg() {
            return msg;
        }
    }
    

    以此启动Eureka服务端、config-server和config-use,浏览器访问:http://localhost:9802/msg,输出如下:

    hello, I'm from config-server

    说明配置中心应用成功

    Zuul路由网关

    zuul应用端

    在spring-cloud-demo工程中添加module:zuul-app
    在pom.xml文件中加入依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    

    在resource目录下增加application.properties文件,添加如下内容:

    server.port=8080
    spring.application.name=zuul-app
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
    
    zuul.routes.zuul-use.path=/api/**
    zuul.routes.zuul-use.serviceId=zuul-use
    

    在java目录下创建一个SpringBoot项目启动类:ZuulApplication,内容如下:

    /**
     * @CalssName ZuulApplication
     * @Description TODO
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableZuulProxy
    public class ZuulApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulApplication.class, args);
        }
    }
    

    zuul使用端

    在spring-cloud-demo工程中添加module:zuul-use
    在pom.xml文件中加入依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    

    在resource目录下增加application.properties文件,添加如下内容:

    server.port=8903
    spring.application.name=zuul-use
    eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
    

    在java目录下创建一个SpringBoot项目启动类:ZuulUseApplication,内容如下:

    /**
     * @CalssName ZuulUseApplication
     * @Description TODO
     */
    @SpringBootApplication
    @EnableDiscoveryClient
    public class ZuulUseApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulUseApplication.class,args);
        }
    }
    

    创建一个Controller:HelloController,内容如下:

    /**
     * @CalssName controller
     * @Description TODO
     */
    @RestController
    public class HelloController {
    
        @RequestMapping(value = "/hello")
        public String hello(){
            return "hello,I'm from zuul-use...";
        }
    }
    

    以此启动Eureka服务端、zuul-app和zuul-use,浏览器访问:http://localhost:8080/api/hello,输出如下:

    hello,I'm from zuul-use...

    说明zuul应用成功

    Zuul高级使用

    Zuul过滤器的使用示例,以token过滤为例,在zuul-app下建TokenFilter过滤器,内容如下:

    /**
     * @CalssName TokenFilter
     * @Description Token Zuul过滤器
     * @Author Alyshen
     */
    public class TokenFilter extends ZuulFilter {
        private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);
    
        @Override
        public String filterType() {
            return "pre"; // 可以在请求被路由之前调用
        }
    
        @Override
        public int filterOrder() {
            return 0; // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
        }
    
        @Override
        public boolean shouldFilter() {
            return true;// 是否执行该过滤器,此处为true,说明需要过滤
        }
    
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
    
            logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());
    
            String token = request.getParameter("token");// 获取请求的参数
    
            if (StringUtils.isNotBlank(token)) {
                ctx.setSendZuulResponse(true); //对请求进行路由
                ctx.setResponseStatusCode(200);
                ctx.set("isSuccess", true);
                return null;
            } else {
                ctx.setSendZuulResponse(false); //不对其进行路由
                ctx.setResponseStatusCode(400);
                ctx.setResponseBody("token is empty");
                ctx.set("isSuccess", false);
                return null;
            }
        }
    }
    

    在zuul-app的启动类下加如下方法:

    @Bean
    public TokenFilter tokenFilter() {
        return new TokenFilter();
    }
  • 相关阅读:
    【C++】基础及引用
    gradle打包分编译环境
    gradle
    MediaPlayer滑动不准的问题
    python初步入门
    音频播放服务
    《深入理解Android2》读书笔记(二)
    缓存(LruCache)机制
    handler机制
    监听网络状态
  • 原文地址:https://www.cnblogs.com/yhongyin/p/12003983.html
Copyright © 2011-2022 走看看