Ribbon이란?

  • 클라이언트 사이드 load balancer
  • 로드밸런싱 룰을 커스터마이징 가능

@LoadBalanced

RestTemplate, WebClient가 Bean으로 선언된 것에 한해서 @LoadBalanced 어노테이션을 붙이면 Intercept를 통해서 LoadBalancer를 끼어넣게된다. 따라서 알아서 로드 밸런싱을 하게된다.

이 때, RestTemplate, WebClient에 주는 주소는 IP, Port를 지우고 호출할 서버 군의 이름을 적으면 군에 해당하는 목록을 알아서 LoadBalancer에서 호출한다.

 

서버쪽 설정

서버에 들어올 경우 어디로 들어왔는지 콘솔에 로그를 남기도록 간단하게 구현했다.

pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
package com.example.circuitbreakerbookstore;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;
import java.util.Random;

@RestController
@SpringBootApplication
public class CircuitBreakerBookstoreApplication {
    private static Logger logger = LoggerFactory.getLogger(CircuitBreakerBookstoreApplication.class);

    @RequestMapping(value = "/recommended")
    public String readingList(){
        logger.info("Access /recommended");
        Random random = new Random();
        List<String> bookList = Arrays.asList("Spring in ACtion (Manning)", "Cloud Native Java", "Learning Spring Boot");
        int randomNum = random.nextInt(bookList.size());
        return bookList.get(randomNum);
    }

    @RequestMapping(value = "/")
    public String home(){
        logger.info("Access home");
        return "Welcome home";
    }
    public static void main(String[] args){
        SpringApplication.run(CircuitBreakerBookstoreApplication.class, args);
    }
}

 

클라이언트 쪽 설정

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
@EnableCircuitBreaker
@RestController
@SpringBootApplication
//다른 자바 설정파일 처럼 @Configuration을 사용하면 안된다. @ComponentScan으로 해당 설정이 로딩될 경우 에러갈 발생한다.
@RibbonClient(name="say-book", configuration = SayBookConfiguration.class) //RibbonClient Annotaion은 Controller에 넣어야한다.
public class CircuitBreakerReadingApplication {

    @Autowired
    private BookService bookService;

    @Bean
    @LoadBalanced
    public WebClient.Builder WebClient(){ //공홈에서는 RestTemplate를 썻지만 그냥 Webflux를 써봤다.
        return WebClient.builder();
    }

    @RequestMapping("/to-read")
    public String toRead(@RequestParam(value="name", defaultValue = "Aroon") String name)
    {

        return String.format("%s, %s",name,bookService.readingList());
    }

    public static void main(String[] args) {
        SpringApplication.run(CircuitBreakerReadingApplication.class, args);
    }
}
public class SayBookConfiguration {
    @Autowired
    IClientConfig ribbonClientConfig;

    @Bean // 첫번째 Parameter가 True일 경우 https false일 경우 http. AppendString은 Ping 보낼 URL
    public IPing ribbonPing(IClientConfig config){
        return new PingUrl(false, "/");
    }

    @Bean //LoadBalancing Rule을 설정하는 것
    public IRule ribbonRule(IClientConfig config){
        return new AvailabilityFilteringRule();
    }
}
@Service
public class BookService {
    @Autowired
    WebClient.Builder webClientBuilder;
    /*
        Hystrix Command 설정
        commandKey 별로 Circuit Breaker 생성
        fallbackMethod : 실패시 해당 메소드 실행
     */
    @HystrixCommand(commandKey = "EX", fallbackMethod = "reliable")
    public String readingList(){
        return webClientBuilder.baseUrl("http://say-book").build().get() //get 방식
                .uri("recommended") //endpoint
                .retrieve() // body를 가지고 온다는 뜻
                .bodyToMono(String.class)
                .block()
                ;
    }

    public String reliable(){
        return "Success Fallback";
    }
}

Eureka와 연동하지 않을 것이므로 false를 하고 Server 군의 목록을 지정해준다.

ServerListRefreshInterval마다 IPing이 실행되며 서버 상태를 확인한다.

server.port= 8888
say-book.ribbon.eureka.enabled=false #유레카를 사용하지 않겠다. 
say-book.ribbon.listOfServers=localhost:8090,localhost:9092 #서버군 목록
say-book.ribbon.ServerListRefreshInterval= 15000 #서버리스트 refresh 간격 ms

 

실행방법 :

 

1. 서버를 아래 mvn 명령어도 두개를 실행한다.

SERVER_PORT=9092 mvn spring-boot:run

SERVER_PORT=8090 mvn spring-boot:run

 

*만약 Maven이 설치되어 있지 않은 경우 homebrew insall maven 명령어로 설치해준다.

 

2. Client를 실행 후 접속해서 확인해본다.

 

 

 

참고 

https://gunju-ko.github.io/spring-cloud/netflixoss/2018/12/14/Ribbon.html

 

Ribbon

Ribbon Netflix가 만든 Software Load Balancer를 내장한 REST Library이다. Ribbon은 Client 사이드 LoadBalancer로 Ribbon을 사용하면 L4등과 같이 하드웨어에서 이루어지던 Load Balance를 애플리케이션 영역에서 할 수 있다. Ribbon을 사용하면 애플리케이션 영역에서 서버목록을 가지고 번갈아가면서 호출하게 된다. Spring Cloud에서 Ribbon 클라이언트를 직접 사용하는 경우는

gunju-ko.github.io

https://spring.io/guides/gs/client-side-load-balancing/

 

Spring

Level up your Java code and explore what Spring can do for you.

spring.io

https://piotrminkowski.com/2018/05/04/reactive-microservices-with-spring-webflux-and-spring-cloud/

 

Reactive Microservices with Spring WebFlux and Spring Cloud

I have already described Spring reactive support about one year ago in the article Reactive microservices with Spring 5. At that time project Spring WebFlux has been under active development, and n…

piotrminkowski.com

 

'Spring' 카테고리의 다른 글

SpringBoot Hystrix 적용하기  (0) 2020.03.01
Hystrix 란  (0) 2020.03.01

간단한 Hystrix Circuit Breaker를 실습하기 위해선 두개의 프로젝트를 생성해야한다.

서버 설정

1. pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

2. application.properties에서 server port 설정 (서버, 클라이언트를 한 컴퓨터에서 실행하기 때문에 port를 설정해줘야함. )

server.port=8090

3. Controller 생성

package com.example.circuitbreakerbookstore;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class CircuitBreakerBookstoreApplication {
    @RequestMapping(value = "/recommended")
    public String readingList(){
        return "Spring in ACtion (Manning), Cloud Native Java, Learning Spring Boot";
    }
    public static void main(String[] args){
        SpringApplication.run(CircuitBreakerBookstoreApplication.class, args);
    }
}

 

클라이언트 설정

1. pom.xml

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.M3</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <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>
        </dependencies>
    </dependencyManagement>

2. application.properties

server.port=8080

3.Controller

package com.example.circuitbreakerreading;


import com.example.circuitbreakerreading.service.BookService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.web.client.RestTemplate;


@EnableCircuitBreaker
@RestController
@SpringBootApplication
public class CircuitBreakerReadingApplication {

    @Autowired
    private BookService bookService;

    @Bean
    public RestTemplate rest(RestTemplateBuilder builder) {
        return builder.build();
    }

    @RequestMapping("/to-read")
    public String toRead() {
        return bookService.readingList();
    }

    public static void main(String[] args) {
        SpringApplication.run(CircuitBreakerReadingApplication.class, args);
    }
}

4. Service

package com.example.circuitbreakerreading.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class BookService {
    private final WebClient webClient;

    public BookService(){
        this.webClient = WebClient.builder().baseUrl("http://localhost:8090").build();
    }

    /*
        Hystrix Command 설정
        commandKey 별로 Circuit Breaker 생성
        fallbackMethod : 실패시 해당 메소드 실행
     */
    @HystrixCommand(commandKey = "EX", fallbackMethod = "reliable")
    public String readingList(){
        return webClient.get() //get 방식
                .uri("recommended") //endpoint
                .retrieve() // body를 가지고 온다는 뜻
                .bodyToMono(String.class)
                .block()
                ;
    }

    public String reliable(){
        return "Success Fallback";
    }
}

 

참고 :

https://spring.io/guides/gs/circuit-breaker/

'Spring' 카테고리의 다른 글

Springboot ribbon 적용하기  (1) 2020.03.03
Hystrix 란  (0) 2020.03.01

Hystrix

  • Hystirx는 Netflix에서 만든 마이크로 서비스 아키텍처에서 장애 전파방지 & Resilience 를 위한 라이브러리
  • Hystirx의 주요 4가지 기능
    • Circuit Breaker
    • Fallback
    • Thread Isolation
    • Timeout

Hystrix Command를 호출할 때 벌어지는 일

  1. 실행 메소드를 Intercept 하여 대신 실행한다.
    • Thread Isolation
      • 제가 제 메소드를 호출했는데도 디버그를 찍어보면 쓰레드가 변경되는 걸 볼 수 있다 -> 누군가 가로채서 실행한다
  2. 메소드의 실행 결과 혹은 실패 발생 여부를 기록(인스턴스 단위)하고 통계를 낸다. 통계에 따라 Circuit Open 여부를 결졍한다.
    • Circuit Breaker
  3. 실패한 경우 사용자가 제공한 메소드를 대신 실행한다.
    • Fallback
  4. 특정시간 동안 메소드가 종료되지 않은 경우 Exception을 발생시킨다.
    • Timeout(서비스 레이어에 걸기 적당하다)

Circuit Open(호출차단)

  • 메소드를 호출하지만 디버그로 찍어보면 메소드 바디안에 들어오지 않는다.
  • 즉, 누군가 가로채서 바로 Exception으로 보내버리는 것
  • A가  B, C, D를 호출하는데 B가 장애가 발생할 경우 B를 차단해 버리므로 장애가 전파되지 않는 차단기 역할

Circuit Close(호출허용)

  • 설정된 시간 이후 단 한개의 요청에 대해 호출을 허용하며 이 호출이 성공하면 Circuit Close

Circuit Breaker의 단위

  • Circuit Breaker 인스턴스는 함꼐 통계를 내고 함께 차단한다. 즉, Circuit Breaker 는 CommandKey 단위로 생성된다.
  • 만약, Command Key를 주지 않으면 Annotation이 박혀있는 메소드 별로 Circuit Breaker가 생성되어 모든 메소드가 Circuit Breaker 를 가지게 된다.

* 너무 작은 단위로 Circuit Breaker를 생성할 경우 메소드가 n초에 m번 이상 호출이 안될 가능성이 높다. 따라서 50, 60 퍼 확률로 장애가 발생하더라도 동작을 안할 수 있으니 주의.

 

Hystrix - Fallback(@HystrixCommand( commandKey = "Ex1", fallbackMethod="exampleFallBack")

Fallback으로 지정된 메소드는 다음의 경우에 원본 대신 실행된다. (정상동작하지 않을 경우 실행)

  • Circuit Open
  • Any Exception(HystrixBadRequestException 제외)
  • Semaphore / ThreadPool Rejection
  • Timeout

* HystrixBadRequestException

해당 에러 발생시 FallBack을 실행하지 않으며 Circuit Open을 위한 통계에도 집계되지 않는다.

로직을 호출한 Caller가 잘못한 것일 때 이 Exception으로 Mapping 해야한다.(ex. Parameter 잘못 넘김, 잘못된 시점에 호출 - 자바에서는 illegalArgument, illegalstateexception)

 

Hystrix - Timeout

Circuit Breaker 단위로 Timeout을 설정할 수 있다.

 

Hystrix - Isolation

특정 시스템에 지연이 일어나 전체가 망가지는 것을 막기 위해서 뒷단 시스템에 적정 용량을 제한함으로써 과도한 Request가 들어가려고 할 때 자동으로 Rejection을 발생시킨다.

  • Semaphore
    • Circuit Breaker마다 Semaphore가 붙어있다.
      • 연동 시스템이 3개가 있을 때 Circuit Breaker를 3개 지정하고 Semaphore Isolation이라고 지정했다면, 뒤 연동 시스템에 최대 동시 호출 갯수를 지정할 수 있는 것.
    • Caller Thread와 실제 메소드가 실행되는 스레드가 같다. (Command를 호출한 Caller Thread에서 메소드 실행)
  • Thread(default)
    • Thread Pool이기 때문에 Semaphore와 동일하게 최대 요청 개수가 제한되어있다.
    • Semaphore와는 다르게 Caller 스레드와 실제 메소드를 실행할 Thread가 분리 되어서 특정 시간이 지나게 되면 Caller 스레드(사용자에게 응답을 줘야하는 스레드)는 바로 Return 된다.

*Semaphore isolation의 경우 Timeout이 제 시간에 발생하지 못할 수 있다.

이유 : 다른 스레드를 임의로 중단시킬 방법은 없다. 유일한 방법은 Thread.Interrupt를 호출하는 것인데 그것도 실제 실행하는 로직에  Thread.Interrupt가 인식하도록 구현되어 있어야 한다.

따라서, Hystrix에서도 Timeout이 지났다고 복잡한 loop를 돌고 있는데 중단하라고 명령을 내려서 중단시킬 방법은 없다.

 

 

 

 

'Spring' 카테고리의 다른 글

Springboot ribbon 적용하기  (1) 2020.03.03
SpringBoot Hystrix 적용하기  (0) 2020.03.01

Pooling layer (sampling)

이미지가 있고 이미지에서 filter를 통해 conv layer를 만들었다. 거기에서 한 layer 씩 뽑아내 sampling을 하고 다시 모아 다른 형태로 만들어 넘기는 것을 pooling이라고 한다(size는 변하지만 depth는 변하지 않는다).

 

Max Pooling

Max Pooling 적용후 6, 8, 3, 4가 들어가게 된다.

  • Pooling 기법 중 하나로 여러 개의 값 중 가장 큰 값을 선택하는 방법이다.
  • 4 x 4로 이루어진 output에 2 x 2 filter, stride 2를 적용했으므로 결과는 2 x 2가 된다.
  • Sampling 이라고 부르는 이유는 전체의 값 중 하나를 뽑는 것이기 때문이다.

'Deep Learning' 카테고리의 다른 글

CNN - Convolutional Neural Network  (0) 2019.06.24
ReLu(Rectified Linear Unit)  (0) 2019.06.23

CNN은 고양이가 물체를 인식할 때 감각이 하나씩 깨어나면서 물체를 인식하는 걸 보고 아이디어를 얻었다고 한다.

즉, 이미지 전체를 하나의 입력으로 받는 것이 아니라, 이미지의 일부분 입력으로 받아 처리하는 것을 말한다(filter).

 

빨간색 네모 박스가 filter

  • Filter는 항상 하나의 값을 만들어낸다. 즉, X를 입력 받아 어떤 처리를 한 후 하나의 값을 뽑아낸다.
  • RGB에 해당하는 3은 항상 같아야한다. (만약 1로 지정되어 있다면 한가지 RGB에 해당하는 요소만 처리하겠다는 뜻)
  • W(weight)가 입력이 있을 때 어떤 숫자로 만들어내는 Filter의 값
  • Stride는 필터를 움직이는 단위를 말한다. (Stride가 1일 경우 필터를 1칸씩 움직인다는 뜻)

Filter를 거치면 Output이 나오는 공식

만약 7x7 이미지에 3x3 Filter를 1 Stride를 적용할 경우 5x5가 나오게 된다. (stride가 2라면 3x3 output이 생겨난다.)

이미지 사이즈를 7로 입력했는데 필터를 적용하게 되면 5x5, 3x3 이렇게 이미지가 작아지게 된다. 이미지가 작아질수록 어떤 정보를 잃어버리게 되기 때문에 보통 실제로 CNN을 사용할 때는 Padding을 사용한다.(이미지 손실을 없애기 위함)

 

Padding을 사용하게 되면 원래 이미지와 출력 이미지 사이즈를 같게 해줄 수 있다.

Padding 사용 이유

  • 그림이 급격하게 작아지는 것을 방지하기 위해
  • 이 부분이 모서리다 라는 것을 알려주기 위해
  • 이미지 손실을 최소화 하기 위해

* Padding을 하게 되면 이미지 손실을 막아주지만, 필요 없는 부분인 0이 붙은 만큼 noise가 발생하게 된다.

하지만 데이터 손실보다는 더 나으니 padding을 사용함

6개, 10개의 filter를 사용

필터를 적용해서 만들어진 새로운 출력 결과를 activation map이라고 한다. 각각의 filter의 Weight는 다르기 때문에 나오는 Output은 조금씩 다르다. 그리고 n개의 filter를 적용하게 되면 n개의filter를 사용했기 때문에 depth는 n이 된다.(depth = filter의 개수)

 

Stride가 1일 때 계산 값

(32-5) / 1 + 1 = 28 

(28-5) / 1 + 1 = 24 

 

Weight에 사용된 Variables 수

  • 5 x 5 x 3 x 6
  • 5 x 5 x 6 x 10

 

'Deep Learning' 카테고리의 다른 글

ConvNet Max pooling 과 Full Network  (1) 2019.06.24
ReLu(Rectified Linear Unit)  (0) 2019.06.23

Acitivation 함수란 ?

-  각 Layer의 말단에서 일정 값 기준으로 classification 해주는 것

 

Sigmoid function의 단점

   Backpropagation에서 Layer가 많아질수록 정확성이 매우 떨어진다. (Sigmoid를 통과하기 때문에 항상 1보다 작은 값이고 0에 가까운 값이 된다.) 결국은 입력이 크게 영향을 미치지 않고 0에 가까워져버리게된다. 

Vanishing gradient 문제가 발생하여 gradient가 아주 작아지는 앞쪽 Layer에서는 제대로 된 학습이 이루어 질 수 없다. local minimum에 정착되업린다.

 

ReLu : Rectified Linear Unit

  • 0보다 작을 경우 버리고 0보다 클 경우 값에 비례해서 커진다. 
  • 마지막 Layer의 출력은 반드시 Sigmoid를 사용해야한다. (마지막 Layer의 출력은 0~1 사이의 값이여야 하므로)

'Deep Learning' 카테고리의 다른 글

ConvNet Max pooling 과 Full Network  (1) 2019.06.24
CNN - Convolutional Neural Network  (0) 2019.06.24

안드로이드 웹 통신하기


안드로이드 진저브레드 이상에서는 Main Thread에서 네트워킹 관련 클래스 사용하면 에러가 발생합니다.

그래서 네트워킹 관련 클래스를 사용할때는 다른 Thread를 사용해야 합니다.



아래 예제는 String인 urlStr에 입력되어있는 웹페이지의 내용을 가져와서 TextView에 보여주는 예제입니다.

HttpUrlConnection은 네트워킹 관련 클래스 이므로 별도의 Thread를 생성해서 사용해야합니다.

HttpURlConnection을 사용 할 때 몇가지 속성을 더 추가해 줄 수 있습니다.

  • .setConnectTime(10000) - 해당 웹 페이지 연결시간을 10초로 설정
  • .setRequestMethod("POST") - Get/Post 방식 설정 
  • .setDoOutput(true) - outputStream으로 데이터를 넘겨주겠다고 설정
  • .setDoInput(true) - InputStream으로 데이터를 읽겠다고 설정  
  • .getResponseCode() - 연결 상태 확인
아래 코드에서 getResponseCode()를 통해 웹 페이지와 연결이 됐는지 확인하고 됐으면 웹 페이지의 글들을 가져와서 textView에 보여줍니다.
연결되지 않았다면 에러 발생이라는 토스트 메세지를 보여줍니다.
class mThread extends Thread{
@Override
public void run() {
try {
URL url = new URL(urlStr);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
if(urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK){
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
urlConnection.disconnect();
}else{
Toast.makeText(getApplicationContext(), "에러발생", Toast.LENGTH_SHORT).show();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}
}

public void readStream(InputStream in){
final String data = readData(in);
mHandler.post(new Runnable() {
@Override
public void run() {
tv.setText(data);
}
});
}
public String readData(InputStream is){
String data = "";
Scanner s = new Scanner(is);
while(s.hasNext()) data += s.nextLine() + "\n";
s.close();
return data;
}

Handler mHandler = new Handler();


아래 예제는 HttpURLConnection을 사용했습니다.

 


  


 


https://github.com/Ywook/AndroidPractice12


안드로이드 handler, AsyncThread 사용하기


Handler


안드로이드의 UI는 메인 스레드에서만 변경할 수 있습니다.

그래서 만약 서브 스레드에서 UI를 변경하고 싶으면 Handler를 사용해야합니다.

즉, 서브 스레드 -> Handler -> 메인 스레드 과정을 거쳐서 서브 스레드에서 UI를 변경할 수 있습니다.


아래 예제는 서브 스레드에서 TextView의 글자를 바꾸는 예제입니다.

Handler를 생성하는 스레드만 다른 스레드가 보내는 Message 객체나 Runnable 객체를 받을 수 있으므로 

메인 스레드에서 Handler를 생성해 줘야합니다.

public class MainActivity extends AppCompatActivity {
TextView tv;
int i;

Handler mhandler = new Handler(){
@Override
public void handleMessage(Message msg) {
tv.setText("숫자 : "+ i);

}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv = (TextView)findViewById(R.id.text1);
}

public void onClick(View v){
SubThread th = new SubThread();
th.start();
}

class SubThread extends Thread{
@Override
public void run() {
for(i = 1; i <= 10; i++){
try {
Thread.sleep(1000);
Message msg = mhandler.obtainMessage();
mhandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}


Handler를 이용해서 로딩화면을 구현해 줄 수 있습니다.

Handler의 postDelayed는 첫 번째 인자로 Runnable 객체, 두 번째 인자로 입력된 시간 후에 Runnable객체가 실행되게 합니다.

아래 예제에서는 3초후에 화면이 바뀌도록 설정한 예제입니다.

public class LoadingActivity extends AppCompatActivity {
Handler mhandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading);
mhandler = new Handler();
mhandler.postDelayed(new Runnable() {
@Override
public void run() {
Intent i = new Intent(LoadingActivity.this, MainActivity.class);
startActivity(i);
finish();
}
}, 3000);
}
}


AsyncTask


AsyncTask는 Handler를 사용하지 않고 UI 처리를 할 수 있고, Background 작업을 할 수  있습니다.

AsyncTask에는 5개의 Override 메소드가 있습니다.

  • onPreExecute() - AsyncTask가 실행될 때 처음 실행되는 메소드로 UI 변경을 할 수 있습니다.
  • doInBackground(Params...) - 백그라운드 스레드에서 처리되는 메소드 입니다. 진행 중 UI작업이 필요한 경우 publish Progress함수를 호출하면 UI 작업을 하는 onProgressUpdate가 호출됩니다.
  • onProgressUpdate - doInBackground에서 publishProgress를 호출하면 실행되는 메소드로 UI를 변경할 수 있습니다.
  • onPostExecute(Result) - doInBackGround 메소드 종료 후 호출되고 doInBackGround의 리턴 값을 받습니다.
  • onCancelled() - doInBackground 메소드에서 작업이 호출되면 onPostExecute 대신 호출됩니다.


생성법은 아래와 같습니다.

AsyncTask<String, Integer, Long>  

  • String - doInBackground 메소드의 전달 값 
  • Integer - onProgressUpdate 메소드의 전달 값
  • Void - doInBackground의 반환 값

class myTask extends AsyncTask<String,Integer,Void>{
@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected Long doInBackground(String... params) {
return null;
}

@Override
protected void onProgressUpdate(Integer... values) {
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}


@Override
protected void onCancelled() {
super.onCancelled();
}
}

생성한 AsyncTask를 실행하고 싶을 때는 .execute() 종료하고 싶을 때는 .cancel(true)를 생성합니다.

.execute에 넣어준 인자는 doInBackground에서 사용할 수 있습니다.

myTask task = new myTask();
task.execute(0);
task.cancel(true);


아래 예제는 Handler와 AsyncTask를 사용한 예제입니다.

코드는 깃 허브에서 볼 수 있습니다.

https://github.com/Ywook/AndroidPractice11


  


 


안드로이드 Canvas, Paint 사용하기




간단하게 말하면 Canvas는 도화지, Paint는 붓이라고 할 수 있습니다.

Paint에서 선의 굵기, 색상, 모양등을 선택해서 다양한 형태로 그릴 수 있습니다.

  • View를 상속받는 Custom클래스를 생성
  • Canvas에 그리고 싶은 내용을 onDraw에 코딩
Paint에서 사용되는 메소드
  • setColor(int color) - Paint의 색상 설정
  • setStrokeWidth(float width) - Paint의 굵기를 설정
  • setStyle(Paint.Style style) - Paint 스타일을 설정
  • setTextSize(float textSize) - Paint의 글자 크기를 설정
  • setAntiAlias(boolean aa) - Paint의 경계면을 부드럽게 처리할지 설정
등등..

Canvas에서 사용되는 메소드
  • void drawRect(left, top, right, bottom) - (left, top) 와 (right, bottom)를 대각선으로 가지는 사각형을 그림
  • void drawText(text, x, y, paint) - (x,y) 좌표에 문자를 출력
  • void drawLine(startX, startY, stopX, stopY, paint) - (x,y) 좌표부터 (x,y) 좌표에 선을 그림
  • void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) - 왼쪽 모서리 좌표를 (x,y)로 가지는  bitmap 을 그림
등등..

public class MyCanvas extends View {

public MyCanvas(Context context) {
super(context);
}

public MyCanvas(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawRect(10, 10, 100, 100, paint);

}

}


Canvas에 이미지 그리기

Bitmap img = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
canvas.drawBitmap(img, 300, 350, paint);


만약 이미지를 작게 또는 크게 그리고 싶을때는 createScaledBitmap을 사용합니다.

Bitmap smallimg = Bitmap.createScaledBitmap(img, img.getWidth()/2, img.getHeight()/2, false);
canvas.drawBitmap(smallimg, 400, 350, paint);

Bitmap bigimg = Bitmap.createScaledBitmap(img, img.getWidth()*2, img.getHeight()*2, false);
canvas.drawBitmap(bigimg, 100, 200, paint);


Touch event 추가하기

-Canvas에 touch event를 추가하려면 onTouchEvent메소드를 Override하고 

 return 값이 false일 경우 touch event를 받지 않겠다는 것이므로 return 값을 true로 해줘야합니다

화면에 터치된 x좌표와 y좌표는 .getX(), getY()를 통해 알 수 있습니다.

getX와 getY의 반환 값은 float이므로 int에 값을 저장하고 싶으면 형변환을 해줘야합니다.

@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
int y = (int)event.getY();
    invalidate();
return true;
}


Mask Filter 추가하기


1) View의 LayerType을 Software로 변경

public MyCanvas(Context context) {
super(context);
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

2) BlurMaskFilter를 생성해서 Paint 요소에 적용

- 첫번째 인자는 Blur의 정도 두번째 인자는 Blur를 줄 타입입니다.

BlurMaskFilter blur = new BlurMaskFilter(100,
BlurMaskFilter.Blur.INNER);
mPaint.setMaskFilter(blur);



아래 예제는 Canvas, Paint, 더블 버퍼링을 사용한 예제입니다.

코드는 깃허브에서 볼 수 있습니다.

https://github.com/Ywook/AndroidPractice10


      


    



안드로이드 파일 입출력 예제


파일 입출력을 할 때 내장 메모리 파일을 처리하는 경우와 외장 메모리(SD card) 파일을 처리하는 경우가 있습니다.



안드로이드에서 Byte 단위 처리를 할 때는 

  • FileInputStream/FileOutputStream
  • BufferedInputStream/BufferedoutputStream


문자 단위 처리 할 때는

  • FileReader/FileWiter
  • BufferedReader/BufferedWriter

클래스를 사용합니다.


내장 메모리 파일 처리


내장 메모리 파일 쓰기

- 아래 코드에서 FileWriter의 첫 번째 인자인 getFilesDir()는 파일이 저장되는 디렉토리를 나타내는 경로입니다.

  두 번째 인자가 true일 경우 기존 파일이 존재할 경우 이어쓰기를 하고 false일 경우 기존 파일이 존재할 경우 덮어쓰기를 합니다.

try{
BufferedWriter bw = new BufferedWriter(new FileWriter(getFilesDir() + "test.txt", true));
bw.write("안녕하세요 Hello");
bw.close();

Toast.makeText(this,"저장완료", Toast.LENGTH_SHORT).show();
}catch (Exception e){
e.printStackTrace();
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}


내장 메모리 파일 읽기

-  FileReader의 첫 번째 인자로 읽을 파일의 경로를 지정해줍니다.

readLine() 메소드는 줄 단위로 읽기 때문에 while문을 사용해서 끝까지 읽도록 해야합니다. 

try{
BufferedReader br = new BufferedReader(new FileReader(getFilesDir()+"test.txt"));
String readStr = "";
String str = null;
while(((str = br.readLine()) != null)){
readStr += str +"\n";
}
br.close();

Toast.makeText(this, readStr.substring(0, readStr.length()-1), Toast.LENGTH_SHORT).show();

}catch (FileNotFoundException e){
e.printStackTrace();
Toast.makeText(this, "File not Found", Toast.LENGTH_SHORT).show();
}catch (IOException e) {
e.printStackTrace();
}


외장 메모리 파일 처리(SD cared)


외장 메모리에 파일을 쓰기 위해서는 Permission이 필요합니다.
API 23 마시멜로 버전부터는 권한이 일반권한(Normal Permission)위험권한(Dangerous Permission)으로 나뉘게됐습니다.
위험 권한은 Permission 이외에도 별도의 권한을 요청하는 코드를 작성해야합니다.
외장 메모리 파일 쓰기는 위험 권한으로 지정되어 있습니다.

먼저 manifest에서 permission 코드를 작성합니다.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

아래 checkFunction()이 실행되면서 외부 메모리에 파일을 쓸 권한이 있는지 체크합니다.
이때 requestPermissions가 권한을 요청하는 팝업창을 띄우는 역할을 합니다.

onRequestPermissionsResult Override 메소드는 해당 권한의 사용자 수락 여부를 확인하는 메소드입니다.

public void checkFunction(){
int permissioninfo = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(permissioninfo == PackageManager.PERMISSION_GRANTED){
Toast.makeText(this,"SDCard 쓰기 권한 있음",Toast.LENGTH_SHORT).show();
}else{
if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)){
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},100);

}else{
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},100);
}
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
String str = null;
if(requestCode == 100){
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
str = "SD Card 쓰기권한 승인";
else str = "SD Card 쓰기권한 거부";
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
}


외장 메모리 경로 불러오기
-외장 메모리의 경로는 기기마다 다르게 인식할 수 있기 때문에  절대 경로로 지정해줘야합니다. 

getExternalPath()는 외장 메모리 경로를 반환해주는 메소드입니다.
Environment.MEDIA_MOUNTED는 외장 메모리가 존재하고 읽고 쓰기가 가능한 상태를 나타냅니다.
만약 저 상태가 아닐경우 내장 메모리의 경로를 반환합니다.

public String getExternalPath(){

    String sdPath ="";
String ext = Environment.getExternalStorageState();
if(ext.equals(Environment.MEDIA_MOUNTED)){
sdPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";
}else{
sdPath = getFilesDir() +"";

}
return sdPath;
}

외장 메모리 파일 처리

-외장 메모리 파일 처리는 내장 메모리 파일 처리하는 경우와 비슷합니다.


파일읽기

try{
String path = getExternalPath();
BufferedReader br = new BufferedReader(new FileReader(path+title));
String readStr = "";
String str = null;
while(((str = br.readLine()) != null)){
readStr += str +"\n";
}
br.close();


}catch (FileNotFoundException e){
e.printStackTrace();
Toast.makeText(this, "File not Found", Toast.LENGTH_SHORT).show();
}catch (IOException e) {
e.printStackTrace();
}

파일쓰기

try{
String path = getExternalPath();
String filename = title;

BufferedWriter bw = new BufferedWriter(new FileWriter(path + filename, false));
bw.write("Hello");
bw.close();
Toast.makeText(this,"저장완료", Toast.LENGTH_SHORT).show();
}catch (Exception e){
e.printStackTrace();
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}


아래 예제는 파일 입출력을 사용한 예제입니다.


  


  


  



위 예제의 모든 코드는 깃허브에서 볼 수 있습니다.

https://github.com/Ywook/AndroidPractice9


+ Recent posts