반응형

4개월 전, 안드로이드 공부를 시작한지 얼마 안되었을 때, OpenAPI를 사용하기 위해 현재 날씨 어플을 만들었다.

이전에 만들었던 어플의 내용은 아래 게시물에 있다.

 

[안드로이드 스튜디오 독학#28] 현재 날씨 어플2

현재 날씨 어플을 AsyncTask를 통해 구현한 후, 같은 기능을 Retrofit을 이용해 RESTful API를 구현해봤다. 어플의 기능은 아래 링크와 같다. [안드로이드 스튜디오 독학#27] 현재 날씨 어플 Thread와 Network

seminzzang.tistory.com

이번에는 진행하고 있는 '외출 도우미 앱' 프로젝트에 날씨 기능을 추가하기 위해 전에 만들었던 날씨 어플을 업그레이드해서 제작했다.

아래의 iOS 날씨 어플을 참고해서 만들었고,

최종적으로는 아래와 같은 화면을 만드는 것이였다.

API는 동일하게 기상청 API를 사용했다.

기상청에서 제공하는 날씨 API는 크게 4가지로 제공된다.

 

저번에 만들었던 [안드로이드 스튜디오 독학#28] 현재 날씨 어플2 에서는 현재 시점의 날씨 정보가 나오는 [초단기실황조회]를 사용했었다.

목표로 하는 화면을 만들기 위해 이번에는 [초단기예보조회][동네예보조회]를 사용했다.

여기서 문제가 생겼다.

1. 시행착오

(1) 기준 시간

[초단기실황조회]에서는 기준 시간을 자료가 없을 경우에 -1시간 해줘서 API를 호출할 수 있었으나, 이번에는 API 발표 시간이 달라서 이를 처리하는데에 애를 좀 먹었다.....

[초단기예보조회][초단기실황조회]와 비슷해서 비슷한 알고리즘으로 처리할 수 있었지만,

[동네예보조회]는 발표시간이 아래와 같았다.

시간이 02시부터 +3시간 단위로 발표가 되었고, API제공시간은 또 +10분 후였다.

이 문제를 해결하기 위해 현재 시간 별로 입력해야할 기준 시간을 

02시 - 02시

03시 - 02시 

04시 - 02시

05시 - 05시 

....

01시 - 23시

이런 식으로 정리했다.

그리고

02~04 -> 02시

05~07 -> 05시

....

23~01 -> 23시 

이런식으로 규칙을 찾은 후,

02~04 -> 02+(3*0)시

05~07 -> 02+(3*1)시

....

23~01 -> 02+(3*7)시 

이런식으로 정리했다.

이런식으로 정리하다 보니

02~04 -> 02+(02-02)시

05~07 -> 02+(05-02)시

....

23~01 -> 02+(23-02)시 

이런 규칙을 찾을 수 있었고 아래의 코드로 기준 시간을 맞출 수 있었다.

1
2
3
4
5
 
strTime2 = new SimpleDateFormat("HH").format(new Date(System.currentTimeMillis() - (1000 * 7200)));
            check = ((Integer.parseInt(strTime2) / 3* 3+ 2;
            strTime2 = Integer.toString(check) + "00";
 
cs

 

(2) 날씨 시간 겹침

(2)-1. 얕은복사 vs 깊은복사

[초단기예보조회] [동네예보조회]를 각각 불러올 때는 상관이 없었지만, 둘의 시간이 겹치는 문제가 발생했다.

목표 화면에서는 시간이 17시, 18시, 19시, 20시, 21시, .... , 0시, 3시, 6시 이런식으로 1시간 간격에서 3시간 간격으로 시간이 매끄럽게 연결되는데,

내가 만든 어플에서는 같은 시간이 중첩되서 보여지거나 시간의 간격이 지저분하게 표현되었다.

이를 해결하기 위해 [초단기예보조회]의 마지막 시간값을 [동네예보조회]에서 비교해주려 했는데, 값이 전달이 안되는 오류가 발생했다.

문제는 String에 있었다.

기존에 C++에 익숙했던 나는 java의 String을 C++의 string 처럼 사용했는데, String은 java의 java collection으로 값을 복사할 때 복사 대상의 주소를 복사하도록 되어있었다.

아래의 코드는 날씨어플 코드에서 API를 통해 json 값을 클래스로 변환하고, 날짜, 시간을 저장하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//....위 코드 생략....
 
strTime = new SimpleDateFormat("HHmm").format(new Date(System.currentTimeMillis() - (3600 * 1000)));
ultraSrtFcstInterface.getData(serviceKey, 10000"JSON", strDate, strTime, 59122).enqueue(new Callback<UltraSrtFcst>() {
    @Override
    public void onResponse(Call<UltraSrtFcst> call, Response<UltraSrtFcst> response) {//json을 저장할 response 클래스
        UltraSrtFcst ultraSrtFcst = response.body();
 
        size = ultraSrtFcst.getResponse().getBody().getItems().getItem().size() / 10;
        for (int i = 0; i < size; i++) {
 
         adapter_weather.addItem(ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstDate(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstTime(),
                                 ultra,
                                 "0",
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(RN1 * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(SKY * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(T1H * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(REH * size + i).getFcstValue());
         //날짜와 시간 저장!!
 
        }
        rv_weather.setAdapter(adapter_weather);
    }
 
    @Override
    public void onFailure(Call<UltraSrtFcst> call, Throwable t) {
 
    }
});

//함수 종료되면서 지역변수 사라짐 ㅜㅜ
 
//....아래 코드 생략....
cs

코드를 보면 json을 저장할 response클래스가

void onResponse(Call<UltraSrtFcst> call, Response<UltraSrtFcst> response)

에서 만들어지는 지역변수인 것을 알 수 있다.

String을 하나 만들어서 값을 복사하려 했는데, 지역변수(문자열)의 주소를 저장하면서 함수가 종료될 때 지역변수가 지워지면서 값이 저장이 안되는 것이였다.

이를 해결하기 위해 구글링을 해보니 얕은복사 vs 깊은복사에 대한 내용을 찾을 수 있었다.

 

[Java] 얕은 복사와 깊은 복사

배열의 복사와 관련해서는 얕은 복사와 깊은 복사의 차이점을 알아야 한다.

woovictory.github.io

내용에 대해서는 이해가 되었지만, 문자열 하나를 복사하기 위해 Cloneable을 상속받는 클래스를 하나 더 만들어서 문자열을 저장하는 것이 비효율적이라 생각되었다.

그래서 java collection을 사용하지 않는다면 얕은 복사를 피할 수 있지 않을까 생각했고, 아래와 같이 코드를 구현했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//....위 코드 생략....
 
strTime = new SimpleDateFormat("HHmm").format(new Date(System.currentTimeMillis() - (3600 * 1000)));
ultraSrtFcstInterface.getData(serviceKey, 10000"JSON", strDate, strTime, 59122).enqueue(new Callback<UltraSrtFcst>() {
    @Override
    public void onResponse(Call<UltraSrtFcst> call, Response<UltraSrtFcst> response) {//json을 저장할 response 클래스
        UltraSrtFcst ultraSrtFcst = response.body();
 
        size = ultraSrtFcst.getResponse().getBody().getItems().getItem().size() / 10;
        for (int i = 0; i < size; i++) {
 
         adapter_weather.addItem(ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstDate(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstTime(),
                                 ultra,
                                 "0",
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(RN1 * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(SKY * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(T1H * size + i).getFcstValue(),
                                 ultraSrtFcst.getResponse().getBody().getItems().getItem().get(REH * size + i).getFcstValue());
         //checkDate 저장
         checkDate = Integer.parseInt(ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstDate());                                        
         //checkTime 
         checkTime = "";
             for (int j = 0; j < 4; j++) {
                 checkTime += ultraSrtFcst.getResponse().getBody().getItems().getItem().get(PTY * size + i).getFcstTime().charAt(j);
             }
 
        }
        rv_weather.setAdapter(adapter_weather);
    }
 
    @Override
    public void onFailure(Call<UltraSrtFcst> call, Throwable t) {
 
    }
});
 
//....아래 코드 생략....
cs

먼저 년/월/일 8문자로 표현되는 날짜(ex) 20210622 )는 자릿수가 변경될 일이 없으므로 java collection이 아닌 int형 변수에 저장을 했다.

그리고 시간의 경우는 00~24시 형태로 표현되는데, 00시 00분의 경우 int형으로 변환하면 0000으로 저장되는것이 아니라 0으로 저장된다.

이를 해결하기 위해 시간은 무조건 문자열로 4자리 수 일 것이므로, 0~3까지 for문을 돌려 각 자리의 문자들을

내가 만든 새로운 문자열에 더해주는 식으로 구현했다.

'문자'는 java collection이 아니니까 얕은복사를 하지 않을 것이라 생각했고, 값을 대입하는 것이 아닌 더해주는 방식을 사용해서 주소를 최대한 참조하지 않도록 했다. 

(2)-2. int의 크기

시간을 문자열로 얻는 데에 성공했는데, 두번째 문제가 발생했다.

바로 int의 크기때문에 문자열을 숫자로 변환할 때 overflow가 발생하는 것이였다.

int의 크기는 다음과 같은데,

날짜-시간을 문자열로 표현하면 ( ex)202106221755 ) 14자리 수로, 12자리 수인 최대 4,294,967,295까지 표현되는 int형 변수에 담을 수 없었다.

이를 해결하기 위해 아래와 같이 문자열을 잘라서 자리수를 줄여서 해결할 수 있었다.

1
2
3
4
 
strCheckDate = Integer.toString(checkDate) + checkTime;
strCheckDate = strCheckDate.substring(210);
 
cs

(3) 위경도 격자 변환

이전 게시글에서도 언급했듯이, GPS로 받을 수 있는 위경도 값과 기상청에서 제공하는 기준 위치인 격자 좌표가 달라 이를 변환하는 과정이 필요했다.

위경도를 격자로 변환하는 과정은 아래의 링크를 참조했다.

 

기상청 격자 <-> 위경도 변환

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

2. 결과물

맨위의 두 줄은 시간을 비교하기 위한 두 시간 문자열이다.

날짜, 시간 뒤에 나오는 100, 200은 서로 다른 두 API를 의미한다.

API에 따라 저장되는 정보가 조금 달라 이를 처리하기 위해 임시로 코드를 집어넣었다.

 

3. 앞으로의 목표

 1) 리사이클러뷰로 위치의 정보를 받아 여러개의 위치 저장

반응형

'Legacy' 카테고리의 다른 글

[안드로이드 스튜디오 독학#38] WGout_메인화면(Main)  (0) 2021.07.05
[안드로이드 스튜디오 독학#37] WGout  (0) 2021.07.04
[C++#4-2] BFS  (0) 2021.06.14
[C++#4-1] DFS  (0) 2021.06.13
[C++#4] 탐색  (0) 2021.06.10

+ Recent posts