본문 바로가기

Android 하나하나 집어보기

Retrofit으로 날씨 api를 이용한 통신 예제!!

Retrofit에 대해서 알아보자!!!!


Retrofit이 뭔가요??

Retrofit 이란 Square 사에서 만든 http 통신 라이브러리 이다. 사실 통신관련해서 왜 라이브러리를 써야하지? AsyncTask를 통해서 구현하면 되는것 아닌가? 뭐가 다른가? 생각이 갈 수 도 있다.  말이 필요없다 코드를 보자


AsyncTask 코드

public class HttpUtil extends AsyncTask<String, Void, Void> {
@Override
public Void doInBackground(String... params) {
try {
String url = "http://apis.skplanetx.com/weather/current/minutely";
URL obj = new URL(url);
HttpURLConnection conn = (HttpURLConnection) obj.openConnection();

conn.setReadTimeout(10000);
conn.setConnectTimeout(15000);
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json");

byte[] outputInBytes = params[0].getBytes("UTF-8");
OutputStream os = conn.getOutputStream();
os.write(outputInBytes);
os.close();

int retCode = conn.getResponseCode();

InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while ((line = br.readLine()) != null) {
response.append(line);
response.append('\r');
}
br.close();

String res = response.toString();

} catch (Exception e) {
e.printStackTrace();
}

return null;

}
}


Retrofit 코드

private void getApi(){
Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://apis.skplanetx.com/weather/current/minutely")
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<JsonObject> jsonObjectCall = apiService.getWeather();
jsonObjectCall.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
String result = response.body().getAsString();
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {

}
});
}

느껴....지십니까??? 이 엄청난 코드의 길이차이!!!!!! 

더 짧게도 가능하지만 최소한의 디버깅을 위해서 이정도로 구현을 했습니다.

과연 길이만 줄어든것이 통신의 안정성, 속도 ,편의성을 제공해 줄 수 있을까요? 정답 부터 말씀드리자면 네 그렇습니다. 

아래 표를 한번 봐보겠습니다.

(사진 출저 : http://instructure.github.io/blog/2013/12/09/volley-vs-retrofit/ )

위에는 AsyncTask를 통한 통신, 구글에서 제공중인 Volley라는 라이브러리 그리고 마지막으로 우리가 배울 Retrofit의 성능 비교 입니다. 정말 엄청난속도 차이를 자랑하지요.

코드도 적고 속도도 훨신빠르고 https 통신가능 MultiPart 업로드 스트리밍등 정말 많은 기능들이 가능합니다.



http통신이 혹여나 모르시는 분이나 개념이 잘안잡혀져서 우리가 뭘 작업하려는지 모르시는분은 아래 그림을 한번 보시고 그아래 생활코딩 의 http의 영상을 보시고나서 다음 글을 읽으시면 더욱더 이해가 잘되십니다. 갓 이고잉님!!! 

(사진 출저 : 직접제작)

 아래 영상을 보시면서 웹브라우저를 안드로이드라고 생각하시고 들으시면 됩니다.

(출처 : 생활코딩 https://opentutorials.org/)



-그래서 어떻게 쓰는건데!!! 항상 서두가 너무길어!!!!


이제 본격적으로 이Retrofit을 쓰는 방법을 알려드리도록 하겠습니다.

지금 서부터 레트로핏을 이용한 현재 자신의 위치에서의 실시간 날씨정보를 받아오는 예제를 해보도록 하겠습니다. 많이 내용이 길고 힘듭니다. 잘 따라오세요.


먼저 안드로이드에서 라이브러리를 사용할수 있도록 gradle에 아래와 같이 설정해 주도록 하겠습니다.

compile 'com.squareup.retrofit2:retrofit:2.3.0'

compile 'com.google.code.gson:gson:2.2.4'

compile 'com.squareup.retrofit2:converter-gson:2.3.0'

그래들에 dependencies 안에 넣어주시면 되겠습니다. 그리고 바로 우측 상단에 sync를 해줍니다. 그리고 나서 Androidmanifest.xml 에 인터넷 좀 쓰겟습니다 라고  어플리케이션 태그 위에 아래와 같이 작성해줍니다.


 <uses-permission android:name="android.permission.INTERNET"/>

라이브러리가 추가가 되었으면 Interface를 정의해줘야합니다. 하지만 그전에 인터페이스 정의에 필요한 몇가지 준비사항들이 있씁니다.

인터페이스 정의에 필요한것은

Url= 우리가 데이터를 가져올 서버의 주소(날씨 API 주소)

AppKey = SK플래닛 웨더 api 로부터 발급받은 키값 (발급받는 법 아래 있음)

우리가 가져올 URL는 

http://apis.skplanetx.com/weather/current/hourly?version={version}&lat={lat}&lon={lon}&city={city}&county={county}&village={village} 이고,

ProtocolREST (모르는 분은 그냥 넘어가도됨)

HttpMethodGET 입니다.


이 url 에서 BaseUrl 을 http://apis.skplanetx.com/ 세팅합니다. 주의할점은 BaseUrl의 끝은 항상 / 슬래쉬로 끝나야합니다.

레트로핏은 URL을 BaseUrl 과 그 뒤의 Url로 따로 구분하여 세팅합니다. 


쿼리스트링은 url에서 ? 다음으로 오는 것들을 이야기합니다. 쿼리스트링은 변수 = 값 으로 되어있습니다. 우리가 필요한 데이터는 version, lat(위도), lon(경도) 만 있으면 됩니다.

여기까지 baseUrl과 쿼리스트링 변수들을 파악해봤습니다. 마지막으로 APPKEY 발급 받는 방법을 말씀드리겠습니다.


https://developers.skplanetx.com/develop/app/ <이 링크를 통해 SK플래닛 에 접속하여 가입을 먼저 해줍니다.

가입을 하신뒤 방금 링크에들어가면 앱등록이라는 버튼이 나옵니다. 버튼을 클릭해주세요

앱이름은 원하는대로 아무렇게나 작성해주시고 다음으로 넘어가겠습니다.

다음으로 넘어가면 서비스 선택부분이 있는데 우리가 사용할 Weather Planet 만 선택해 줍니다.

그리고나서 다음으로 넘어오면 하단에 AppKey 라고 있습니다. 저 값을 우리가 사용할 것 입니다.


자이제 모든 준비가 마쳤습니다. 주소, 주소의 쿼리 분석,AppKey 를 모두 준비했습니다. 이제 제대로 Retrofit을 사용해보도록 하겠습니다.

먼저 인터페이스를 아래와 같이 작성해줍니다. 

public interface ApiService {
//베이스 Url
static final String BASEURL = "http://apis.skplanetx.com/";
static final String APPKEY ="사이트에서 받아온 키값";
//get 메소드를 통한 http rest api 통신
@GET("weather/current/hourly")
Call<JsonObject> getHourly (@Header("appKey")String appKey ,@Query("version") int version,
@Query("lat") double lat, @Query("lon") double lon);

}

간단하게 살펴보면 retrofit은 자바 interface를 통해서 Http Api 를 작성 할 수 있습니다. 구조를 보면

@GET("베이스 url 다음으로 나올 주소들")

Call<받아올 데이터의 형태> 메소드명 (@Header("정의하고 싶은 속성 이름") 자료형 변수명,

@Query("변수 이름") 자료형 변수명);


위와 같이 되어있습니다. 우리가 준비해논 자료들을 마구마구 넣어줍니다. 인터페이스 작성을 마쳤으면 자바코드 작성에 앞서 간단한 레이아웃부터 작성해보도록 하겠습니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.edge.weather.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="위도"
android:textColor="@android:color/black"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:textSize="17dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="23151615"
android:id="@+id/latitude"
android:textColor="@android:color/black"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:textSize="17dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="경도"
android:textColor="@android:color/black"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:textSize="17dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="3213132"
android:id="@+id/longitude"
android:textColor="@android:color/black"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:textSize="17dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="날씨 받아오기"
android:id="@+id/button"
android:layout_marginTop="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="결과"
android:textColor="@android:color/black"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:textSize="17dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:id="@+id/weather"
android:textSize="17dp"/>
</LinearLayout>

간단하게 위와 같은 레이아웃을 작성해 봤습니다. 


다시 자바코드로 넘어와서 레이아웃 세팅과 레트로핏 작성을 바로 해보도록 하겠습니다.

private void initView(){
//뷰세팅
latText = (TextView) findViewById(R.id.latitude);
lonText = (TextView) findViewById(R.id.longitude);
weather = (TextView) findViewById(R.id.weather);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
}

액티비티 안에서 뷰세팅 메소드를 만들어주시구요 버튼 클릭 리스너는 implements 를 해줍니다.(모르시는 분은 this 대신 new OnClickListener를 바로 넣어주시면되요)


이제 Http 통신 코드를 아래와 같이 작성해 줍니다.

private void getWeather(double latitude, double longitude) {
Retrofit retrofit =new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
.baseUrl(ApiService.BASEURL)
.build();
ApiService apiService = retrofit.create(ApiService.class);
Call<JsonObject> call = apiService.getHourly(ApiService.APIKEY,1,latitude,longitude);
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(@NonNull Call<JsonObject> call, @NonNull Response<JsonObject> response) {
if (response.isSuccessful()){
//날씨데이터를 받아옴
JsonObject object = response.body();
if (object != null) {
//데이터가 null 이 아니라면 날씨 데이터를 텍스트뷰로 보여주기
weather.setText(object.toString());
}

}
}
@Override
public void onFailure(@NonNull Call<JsonObject> call, @NonNull Throwable t) {
}
});
}

코드를 살펴보면 

  Retrofit retrofit =new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())

                .baseUrl(ApiService.BASEURL)

                .build();

레트로핏 객체를 선언해주는데 addConverterFactory 이 메소드는 받아오는 데이터를 Gson 이라는( JSON을 파싱하기위한 구글에서 제공하는 라이브러리) 것을 통해 바꿔주기위한 메소드이고, baseUrl() < 우리가 준비해둔 baseUrl을 여기에다가 세팅해 주고 Build()로 마무리를 해줍니다.


ApiService apiService = retrofit.create(ApiService.class);

 Call<JsonObject> call = apiService.getHourly(ApiService.APPKEY,1,latitude,longitude);

우리가 만든 인터페이스를 통해 레트로핏을 사용하겠다고 초기화를 해주고 apiService의 우리가만들어준 메소드 getHourly() Call<JsonObject> 로 받아줍니다. 여기서 getHourly의 파라미터 APPKEY, 버전, 위도, 경도를 넘겨줍니다.


여기서 JSONObject 가 아닙니다. Gson의 JsonObject 입니다. 이점 유의해서 진행해주세요!

Gson은 안드로이드 Json파싱해보기!  < 이글을 참조해주세요.


그리고 바로 call을 실행하는 코드를 작성합니다.

  call.enqueue(new Callback<JsonObject>() {

            @Override

            public void onResponse(@NonNull Call<JsonObject> call, @NonNull Response<JsonObject> response) {

                    if (response.isSuccessful()){

                        //날씨데이터를 받아옴

                        JsonObject object = response.body();

                        if (object != null) {

                            //데이터가 null 이 아니라면 날씨 데이터를 텍스트뷰로 보여주기

                            weather.setText(object.toString());

                        }

                    }

            }

            @Override

            public void onFailure(@NonNull Call<JsonObject> call, @NonNull Throwable t) {

            }

        });

마지막으로 call.enqueue 를 통해서 비동기 통신 메소드를 실행합니다. 통신결과는 onResponse에서 위와 같이 받아볼수 있으며 통신 실패의 경우 onFailure 가 실행이 됩니다.


통신은 모두 끝났습니다. 엄청 길지요. 하지만 그만큼 많이 중요하고 많이 사용하기 때문에 세세히 설명드렸습니다. 정리를 해보자면 interface에서 통신을 어떻게 할지 정보들을 세팅을 하고 액티비티 클래스 에서 통신을 실행한다 였습니다.


이제 현재위치의 위도경도를 받아와서 통신하는 것을 짜보도록 하겠습니다.

 (여기서 지치신 분들은 통신까지만 작성하시고 edittext를 만드셔서 직접 위도 경도를 숫자로 넣어주는 방식으로 진행하시면 되겠습니다) 


위도경도를 얻기위해선 사용자에게 위치정보좀 사용해도 되겠습니까 하는 권한을 요구해야합니다.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

이 두가지 권한을 위에 인터넷 권한설정한것과 같이 AndroidManifest.xml 에 작성해줍니다.


그리고 메인 액티비티로 돌아와서

public class MainActivity extends AppCompatActivity implements LocationListener, View.OnClickListener {
LocationManager locationManager;
double latitude;
double longitude;
TextView latText, lonText, weather;
Button button;

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

initView();

locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

}

private void initView() {
//뷰세팅
latText = (TextView) findViewById(R.id.latitude);
lonText = (TextView) findViewById(R.id.longitude);
weather = (TextView) findViewById(R.id.weather);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
}


@Override
public void onLocationChanged(Location location) {
/*현재 위치에서 위도경도 값을 받아온뒤 우리는 지속해서 위도 경도를 읽어올것이 아니니
날씨 api에 위도경도 값을 넘겨주고 위치 정보 모니터링을 제거한다.*/
latitude = location.getLatitude();
longitude = location.getLongitude();
//위도 경도 텍스트뷰에 보여주기
latText.setText(String.valueOf(latitude));
lonText.setText(String.valueOf(longitude));
//날씨 가져오기 통신
getWeather(latitude, longitude);
//위치정보 모니터링 제거
locationManager.removeUpdates(MainActivity.this);
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {

}

@Override
public void onProviderEnabled(String provider) {

}

@Override
public void onProviderDisabled(String provider) {

}
@Override
public void onClick(View v) {
switch (v.getId()) {
//버튼 클릭시 현재위치의 날씨를 가져온다
case R.id.button:
if (locationManager != null) {
requestLocation();
}

break;
}
}

위와 같이 작성해 줍니다. 코드는 길지만 내용은 별거 없습니다.


안드로이드는 LocationManager 라는것을 통해서 위치정보를 가져올 수 있습니다. onCreate에서 LocationManager를 초기화를 해주고 LocationListener를 액티비티에 implement 해줍니다.  그럼 오버라이드 메소드들이 마구 생겨날 것입니다. 그 중에서 우린 onLocationChanged를 사용합니다. 영어그대로 위치가 변경될때 실행되는 메소드 입니다. 이 메소드는 Location을 파라미터로 받는데 Location 클래스 안에는 위도,경도,신뢰도 등의 정보들이 있습니다. 우리는 여기서 위도 경도 정보를 가져와서 아까 작성한 통신 메소드로 넘겨주면 끝입니다.


벌써 끝인가 싶지만 아직 하나가 더 남아잇습니다 허허허허

보시면 LocationManager 초기화도 했고 LocationListener 도 만들었습니다. 남은 하나는 LocationManager에서 위치정보를 가져오도록 LocationListener 를 세팅해줘야합니다.

private void requestLocation() {
//사용자로 부터 위치정보 권한체크
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 0);
} else {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 500, 1, this);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 500, 1, this);

}


갑자기 무지막지한 알수 없는 코드들이 나왔네요. 하지만 내용은 간단합니다 조건문 코드를 보면 위치정보는 사용자의 권한이 필요하다고 했습니다. 

아까 해준것 아닌가요? 하고 생각해볼 수 있는데 아까해준것은 이 앱은 사용자 위치정보를 필요로 합니다라고 단말기에다가 알려준것이고 실제로 사용자에게 요청한것은 아닙니다.

 그래서 우리는 사용자에게 요청 하는 코드를 위에처럼 작성해야 하구요 우리가 마지막으로 세팅해주는 코드는 else 문을 보시면 됩니다.

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 500, 1, this);

locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 500, 1, this);

로케이션매니저 에서 리퀘스트! 요청한다! 로케이션! 위치를! 업데이트! 업데이트해줘라!!

업데이트 하기위해선 gps와 network를 이용하고 500은 0.5초당 한번 위치 갱신!,1은 1m터 이동할때마다 위치 갱신! 그리고 this 는 아까 implement한 LocationListener를 넣어줘라!!!!! 


이렇게 하면 LocationManager에서 위치정보를 받아올 수 있도록 요청을 합니다. 주의사항은 로케이션 매니저 세팅할때보면 0.5초당 1m당 이런 말들이 있었죠. 맞습니다. 로케이션 매니저는 우리가 직접 해제 해주지 않는이상 계속해서 위치정보를 받아옵니다. 하지만 우리는 현재위치에서 날씨정보 단한번만 불러올 것이기 때문에 locationManger에서 너이제 그만일해 라고 세팅해줘야합니다.


더해야될게 잇냐구요? 아니요 우린 이미 코드를 작성해놨습니다. onLocationChanged를 보면 

locataionManager.removeUpdates(MainActivity.this);

이 코드가 작성되있습니다. 로케이션매니저 !! 너 업데이트제거해!! 끝!!!!


이제 우리는 버튼하나만 누르면 현재 내위치정보를 받아와서 현시간 날씨를 구할 수 있게 되었습니다. 진짜 고생많으셨습니다. 양도 많고 할 이야기도 많아서 두서도 없었지만 차근 차근 따라 해보시면 하는 방법을 이해하시리라 믿습니다. 

https://gist.github.com/EdgeJH/96bc1ace3f522c42d050b4e777db04c1

위의 사이트 가시면 풀 코드가 나와있습니다. 



ps.


비전공자 안드로이드 질문방을 운영중입니다. 

톡방링크 (링크) 를 통해 들어오시면 못다 설명드린내용들 자세히 설명드릴게요!!! 

이깟 블로그보다 직접만나서 배워보고 싶으시면 말리지 않습니다. 어서오세요 (링크)

마지막으로....제가 만든 앱 (링크) 입니다. 리뷰... 하나가 생명을 살립니다. 감사합니다.