참고)
안드로이드의 HTTP 통신 라이브러리 고찰
[Android] OkHttp & Retrofit
OkHttp or Retrofit for Android?
프로젝트를 하면서 HTTP 통신을 할때 고민없이 Retrofit2, Okhttp3 의존성을 추가해 사용했었습니다.
Retrofit2 라이브러리 이전에 HTTP 통신은 어떤 방식으로 처리했고, Retrofit2를 사용하는 이유에 대한 이해가 필요하다고 생각했습니다.
그래서 Android 통신 라이브러리에 대한 역사를 공부하고, Retrofit2에 대해 알아보고자 합니다! 그 전에 비동기처리, HTTP에 대한 사전지식이 필요하여 이부분부터 공부하고 가겠습니다~ 갈길이 살짝 머니 안전벨트 꽉 잡으세요(?)ㅎㅎ
메인스레드(UI스레드)
: 1개만 존재백그라운드 스레드
: 여러개 존재 가능안드로이드 UI는 기본적으로 싱글 스레드 모델
로 작동합니다. 안드로이드 화면을 구성하는 뷰나 뷰그룹을 하나의 스레드에서만 담당하는 원칙을 싱글 스레드 라고 합니다
싱글 스레드 모델은 2가지 규칙이 있습니다
만약 UI스레드가 UI를 그리는 일 말고 네트워크 처리와 같은 무거운 작업을 함께 실행한다면 어떻게 될까요? (다른 스레드에서 UI를 그리는 것도 문제가 되지만 메인 스레드에서 UI 그리는 일 말고 다른 일을 한다면?)
일단, 안드로이드는 초당 60프레임을 지원하고 한 프레임은 16ms안에 그려져야 합니다 16ms 안에 화면(UI)을 그리는 작업이 완료되지 않으면 쟁크가 발생합니다
쟁크 발생과 더불어 UI이벤트 및 작업에 대해 5초 내에 응답안하면 안드로이드 시스템은 ANR(Android Not Responding=응용프로그램 응답 안함)팝업창을 표시합니다(안드로이드 시스템이 UI가 그려지는 속도가 늦는 걸 유저의 모바일 사용 환경을 방해한다고 판단하기 때문)
Looper와 Handler
: 두 개 이상의 스레드를 사용할 때의 동기화 이슈를 차단하기 위해서 Looper와 Handler를 사용 쓰레드는 수동으로 관리해야 하기 때문에 관리가 어렵고 코드를 읽기 어려워 질 수 있다는 단점이 있습니다HandlerThread
: 메인 스레드는 Looper가 기본적으로 생성돼 있지만, 새로 생성한 스레드는 기본적으로 Looper를 가지고 있지 않고, 단지 run 메서드만 실행한 후 종료하기 때문에 메시지를 받을 수 없어 불편합니다. HandlerThread는 생성할 때 Looper를 자동으로 보유한 클래스로 내부에 반복해서 루프를 도는 Looper를 가져 Looper와 Handler를 직접 사용할때보다 편리합니다AsyncTask
: Thread나 Message Loop 등의 작동 원리를 크게 고려하지 않고도 UI 스레드를 적절하고 쉽게 사용할 수 있습니다. 리스트에 보여주기 위한 데이터 다운로드 등 UI와 관련된 독립된 작업을 실행할 경우 AsyncTask로 간단하게 구현할 수 있습니다HTML
: 웹페이지를 만드는 언어URL, URI
: 원하는 웹페이지에 방문하도록 돕는 주소체계Web browser, Web server
: 웹페이지를 주고받는 소프트웨어HTTP
(HyperText Transfer Protocol): Web browser, Web server가 통신할때 사용하는 규칙① 요청 라인: HTTP Method, Web page, HTTP version
② 요청 헤더: Host, Accept, User-Agent, Cokie, Referer
③ 공백 라인: 요청 헤더와 요청 바디를 구분하는 라인
④ 요청 바디: 클라이언트가 서버에 실제 요청한 내용
① 상태 라인: HTTP Version, Status Code
② 응답 헤더: Date, Server, Content-Type, Last-Modified
③ 응답 바디: 실제 응답받은 메시지(데이터)
html, css, 이미지 같은 파일은 서로 주고받는 컨텐츠고 이걸 서로 주고받으려면 서버 클라이언트가 공통으로 알아들을 수 있는 교환방식=HTTP 메시지(request Message/ Response Message)가 필요
*
인터페이스(Interface)
: 사물과 인간 사이의 경계에서 상호 간의 소통을 위해 만들어진 물리적 매개체나 프로토콜을 말합니다. 예를 들어 TV 리모콘 전원 버튼은 인터페이스다. 사람이 리모콘 버튼을 눌러 TV가 켜지도록 연결하는 매개체이기 때문입니다.
API(Application Programming Interface)
: 애플리케이션(응용프로그램)에서 사용할 수 있도록, 운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스. 컴퓨터와 인간을 연결시키는 사용자 인터페이스(UI)와 반대로, API는 컴퓨터나 소프트웨어를 서로 연결하는 인터페이스
서버는 프로그램에게 자신이 제공하고자 하는 데이터나 기능을 제어할 수 있는 API로 만들면, 접근 권한이 있는 프로그래머나 프로그램이 API를 통해 서버에서 제공하는 데이터를 요청해서 사용할 수 있게 됩니다.
API는 컴퓨터, 응용프로그램 등을 연결하는 매개체/인터페이스. 서버가 제공하는 API를 사용한다면 서버는 API를 통해 시스템 동작 방식은 숨기고 필요한 부분만 노출시킬 수 있다(API사용하는 프로그래머는 신경쓸 사항이 적어지니 좋다!)
HTTP(request message, response message)통신규칙을 사용하여 프로그램끼리 소통하도록 만든 API(Application Programming Interface)
(참조: 짐코딩 API란 무엇인가? https://www.youtube.com/watch?v=Jg3FFBLyhK0)
IOT 응용프로그램은 HTTP프로토콜이 아닌 MQTT, CoAP프로콜을 사용한 API로 통신
REST(Representational State Transfer)
: 한마디로 네트워크 아키텍처 스타일입니다. (네트워크 자원을 정의하고 처리하는 방법 전반/HTTP를 사용하는 청사진,모범사례라고 생각할 수 있습니다)
2000년도에 로이 필딩 (Roy Fielding)의 박사학위 논문에서 최초로 소개되었습니다. 로이 필딩은 HTTP의 주요 저자 중 한 사람으로 그 당시 웹(HTTP) 설계의 우수성에 비해 제대로 사용되어지지 못하는 모습에 안타까워하며 HTTP의 장점을 최대한 활용할 수 있는 네트워크 아키텍쳐 스타일(네트워크 자원을 정의하고 처리하는 방법 전반)로써 REST를 발표했습니다
즉, REST는 HTTP를 잘 활용하기 위한 원칙이자 네트워크 아키텍쳐 스타일(청사진이자 모범사례)이라고 할 수 있다
Representational State Transfer
자원의 표현으로 상태를 전달하는 것
- URI로 자원을 표현하는 데에 집중하고, 자원의 상태(행위)에 대한 정의는 HTTP METHOD로 하는 것
GET /members/delete/1
-> DELETE /members/1
GET /members/show/1
->GET /members/1
1) 자원의 식별
2) 메세지를 통한 리소스 조작
3) 자기서술적 메세지
4) 애플리케이션의 상태에 대한 엔진으로서 하이퍼미디어(HATEOAS)
이러한 제약 조건들을 완벽하게 지키면서 개발하는 것을 RESTful API라고 하는데 실무에서는 이런 방법으로 개발하는 것은 현실적으로 어렵고 개발비용 대비 효과가 있는 것도 아니라고 하셨다. (4번째 원칙이 특히나 구현하기 어렵다고,,)
그런데 이미 많은 사람들이 이 조건들을 지키지 않아도 REST API라고 하기 때문에 HTTP API와 같은 의미로 사용하고 있다고 합니다. 하지만 위 제약 조건들을 모두 지켜야 REST API라고 말할 수 있습니다
REST API는 HTTP API(HTTP(request message, response message)통신규칙을 사용하여 프로그램끼리 소통하도록 만든 API(Application Programming Interface))와 거의 같은 개념으로 사용되지만 HTTP 프로토콜을 따르면서 4가지 원칙을 철저히 지킨 API
2007/11/05 : Android가 발표
2011/09/29 : HttpURLConnection을 권장하는 블로그가 나옴
2013/05/06 : OkHttp 1.0.0이 릴리즈 됨
2013/05/14 : Retrofit 1.0.0이 릴리즈 됨
2013/05/21 : Volley가 릴리즈 됨
2016 : Android6.0에서 HttpClient가 삭제 됨
2016/03/12 : Retrofit2가 릴리즈 됨
Http 통신을 용이하게 수행하기 위해 Apache에서 제작한 라이브러리입니다. 안드로이드 초기에 주로 사용되었으며 실제로는 HttpClient
를 래핑한 DefaultHttpClient
나, 안드로이드에 맞게 개수한 AndroidHttpClient
가 사용되었습니다.
HttpClient
는 안드로이드와 독립적으로 개발되는 라이브러리인지라 변경점을 안드로이드 SDK에 일괄적으로 즉시 반영할 수 없었습니다. 결국 버전이 뒤쳐지면서 버그가 계속 발생하게 되었고, HttpClient는 Android 5.1에서 Deprecated 되며 6.0에서는 아예 삭제되었습니다. 이 시기 클라이언트의 버그는 네이버 D2 블로그의 Android의 HTTP 클라이언트 라이브러리에 잘 정리되어 있으니 참고하시기 바랍니다.
HttpClient
를 삭제하면서 구글에서 제시한 대안이 HttpUrlConnection
인데요, 구글에서 HttpUrlConnection
을 사용하라고 권장한 블로그 포스팅도 있습니다. 기존의 URLConnection
에 HTTP를 다루는데 필요한 메서드를 추가한 클래스입니다.
현재 HttpURLConnection은 Deprecated되었습니다
URL.openConnection()으로 얻어진 URLConnection 객체를 HttpURLConnection으로 캐스팅하여 데이터 송수신을 행하고 disconnect로 접속을 종료하는 방식으로 사용합니다. 비동기 처리는 직접 해주어야 합니다
// WeatherRepositoryImplHttpURLConnection.java
public class WeatherRepositoryImplHttpURLConnection implements WeatherRepository {
public static final String TAG = WeatherRepositoryImplHttpURLConnection.class.getSimpleName();
@Override
public void getWeather(final RequestCallback callback) {
new AsyncTask<Void, Void, Weather>() {
// 처리 전에 호출되는 메소드
@Override
protected void onPreExecute() {
super.onPreExecute();
}
// 처리를 하는 메소드
@Override
protected Weather doInBackground(Void... params) {
final HttpURLConnection urlConnection;
try {
URL url = new URL(uri.toString());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
} catch (MalformedURLException e) {
return null;
} catch (IOException e) {
return null;
}
final String buffer;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
buffer = reader.readLine();
} catch (IOException e) {
return null;
} finally {
urlConnection.disconnect();
}
if (TextUtils.isEmpty(buffer)) {
return null;
}
return new Gson().fromJson(buffer, Weather.class);
}
// 처리가 모두 끝나면 불리는 메소드
@Override
protected void onPostExecute(Weather response) {
super.onPostExecute(response);
// 통신 실패로 처리
if (response == null) {
callback.error(new IOException("HttpURLConnection request error"));
} else {
Log.d(TAG, "result: " + response.toString());
// 통신 결과를 표시
callback.success(response);
}
}
}.execute();
}
}
HttpUrlConnection을 사용할 때는 Application Not Responding(ANR)을 피하기 위해 백그라운드 스레드도 만들어야하고, 버퍼를 통한 입출력도 준비해야 하고, 캐시나 예외처리도 한땀한땀 다 처리해 주어야 하는 불편함이 있었습니다.
그래서 구글에서는 HTTP 연결을 만들때마다 비동기 처리를 감싸주고 있기 때문에 AsyncTask 등을 사용하지 않아도 되는 라이브러리인 Volley를 2013년 Google I/O에서 발표했습니다.
사용법은 다음과 같습니다. HTTP 메소드와 url 정보를 가진 Request를 만들어서 RequestQueue에 넣어줍니다. 그러면 Volley가 알아서 스레드를 만들고 HttpUrlConnection으로 통신을 수행한 뒤 response를 반환해줍니다. 코드를 보시면 HttpUrlConnection을 직접 사용할 때보다 코드가 더 읽기 쉬워진 것을 알 수 있습니다.
하지만 Volley는 반환받은 JSON 객체를 데이터클래스로 바로 변환해주지 못하기 때문에, 별도의 과정을 통해 직접 변환해서 사용해야합니다
// WeatherRepositoryImplVolley.java
public class WeatherRepositoryImplVolley implements WeatherRepository {
public static final String TAG = WeatherRepositoryImplVolley.class.getSimpleName();
RequestQueue queue;
public WeatherRepositoryImplVolley(Context context) {
queue = Volley.newRequestQueue(context);
}
@Override
public void getWeather(final RequestCallback callback) {
final JsonObjectRequest request =
new JsonObjectRequest(uri.toString(), null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.d(TAG, "result: " + response.toString());
final Weather weather = new Gson().fromJson(response.toString(), Weather.class); //파싱해야함
callback.success(weather);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
callback.error(error);
}
});
queue.add(request);
}
}
그런 와중에 2013년 5월 6일엔 Square에서 OkHttp라는 HTTP 클라이언트 라이브러리를 발표합니다. 이 라이브러리는 Okio와 코틀린을 활용해 쓰여졌고 다음과 같은 특징이 있습니다. Connection pooling과 Redirection을 도입해 접속을 더 안정적이게 하면서도, 속도를 개선시킬 수 있는 여러가지 기술이 적용된 것으로 보입니다
OkHttp는 통신을 동기화로할지 비동기 처리로할지 선택할 수 있습니다. 그러나 스레드를 넘나들 수 없으므로 Handler를 사용합니다.
// WeatherRepositoryImplOkHttp3.java
public class WeatherRepositoryImplOkHttp3 implements WeatherRepository {
public static final String TAG = WeatherRepositoryImplOkHttp3.class.getSimpleName();
private Handler handler = new Handler();
@Override
public void getWeather(final RequestCallback callback) {
final Request request = new Request.Builder()
// URL 생성
.url(uri.toString())
.get()
.build();
// 클라이언트 개체를 만듬
final OkHttpClient client = new OkHttpClient();
// 새로운 요청을 한다
client.newCall(request).enqueue(new Callback() {
// 통신이 성공했을 때
@Override
public void onResponse(Call call, Response response) throws IOException {
// 통신 결과를 로그에 출력한다
final String responseBody = response.body().string();
Log.d(TAG, "result: " + responseBody);
final Weather weather = new Gson().fromJson(responseBody, Weather.class);
handler.post(new Runnable() {
@Override
public void run() {
callback.success(weather);
}
});
}
// 통신이 실패했을 때
@Override
public void onFailure(Call call, final IOException e) {
handler.post(new Runnable() {
@Override
public void run() {
callback.error(e);
}
});
}
});
}
}
Retrofit은 OkHttp를 개발한 Square에서 2013/05/14 에 발표한 라이브러리입니다. (OkHttp를 발표한지 8일만에) HttpURLConnection을 사용하기 편하도록 랩핑한게 Volley라면 Retrofit은 OkHttp를 랩핑한 것입니다.
Retrofit은 어노테이션을 사용하여 코드를 생성하기 때문에 이를 위한 인터페이스를 만듭니다
사용법은 다음과 같습니다. 우선 REST API 콜을 인터페이스 형식으로 준비합니다. 그리고 Retrofit 객체를 만들어서 인터페이스의 인스턴스를 생성합니다. 마지막으로 인터페이스를 동기 혹은 비동기적으로 구동시켜 response를 반환받게 되어 있습니다.
// WeatherRepositoryImplRetrofit2.java
public class WeatherRepositoryImplRetrofit2 implements WeatherRepository {
public static final String TAG = WeatherRepositoryImplRetrofit2.class.getSimpleName();
private final WeatherService service;
public WeatherRepositoryImplRetrofit2() {
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(new Uri.Builder().scheme(SCHEME).authority(AUTHORITY).build().toString())
.client(new OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(WeatherService.class);
}
@Override
public void getWeather(final RequestCallback callback) {
service.getWeather(130010).enqueue(new Callback<Weather>() {
@Override
public void onResponse(Call<Weather> call, Response<Weather> response) {
Log.d(TAG, "result: " + response.body().toString());
callback.success(response.body());
}
@Override
public void onFailure(Call<Weather> call, Throwable error) {
callback.error(error);
}
});
}
private interface WeatherService {
@GET(PATH)
Call<Weather> getWeather(@Query("city") int city);
}
}
Volley와 Retrofit은 지금도 꾸준히 갱신되고 있는 라이브러리입니다. 코드 가독성은 대부분 Retrofit이 더 좋다는 평가인 것 같습니다.
개인적으로는 Annotation으로 HTTP 메소드를 정의해서 사용하는 Retrofit이 전체 구조를 파악하기 더 좋은것 같아 이쪽을 선호합니다. 한가지 재밌는 것은 구글의 권장 앱 아키텍처였던것에서는 HTTP 통신에 Volley가 아닌 Retrofit을 추천하고 있다는 점입니다.
Retrofit은 Volley보다 빠르고, Annotation으로 HTTP 메소드를 정의하므로 가독성이 좋고, JSON, XML을 자동을 파싱해주는 Converter 연동을 지원하기 때문에 좋다!!
그런데 많은 프로젝트에서 OkHttp와 Retrofit을 함께 사용하는 모습을 보신적이 있을겁니다! 이 둘의 차이를 알아보고(Retrofit이 더 좋다는 이야기가 주된 내용이지만) 같이 사용하는 이유도 알아봅시다
둘 다 같은 회사(Square)에서 만든 HTTP 통신 라이브러리입니다. 앞서 언급한것처럼 OkHttp를 래핑하여 더 Type-safe하고, 더 직관적으로 사용할 수 있도록 인터페이스로 만들어진 게 Retrofit입니다. 따라서 완전히 다르진 않지만, 지원 기능과 용도 면에서 어느정도 차이가 있습니다.
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
interface wikiApiServe{
@GET(END_POINT)
fun hitCountCheckCall(
@Query(PARAM_ACTION) action: String,
@Query(PARAM_FORMAT) format: String,
@Query(PARAM_LIST) list: String,
@Query(PARAM_SRSEARCH) srsearch: String)
: Call<Model.Result>
}
wikiApiServe.hitCountCheckCall(
VALUE_QUERY, VALUE_JSON, VALUE_SEARCH, searchString)
val request = Request.Builder().url("$BASE_URL$BASE_PATH/?" +
"$PARAM_ACTION=$VALUE_QUERY&$PARAM_FORMAT=$VALUE_JSON&" +
"$PARAM_LIST=$VALUE_SEARCH&$PARAM_SRSEARCH=$searchString")
.build()
call?.enqueue(
object : Callback<Model.Result> {
override fun onFailure(call: Call<Model.Result>, t: Throwable) {
...
}
override fun onResponse(call: Call<Model.Result>,
response: Response<Model.Result>) {
response.body()?.let { //바로 사용 가능 }
}
client.newCall(request).enqueue(
object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
...
}
override fun onResponse(call: okhttp3.Call,
response: okhttp3.Response) {
response.body()?.let {
val result = Gson().fromJson(it.string(),
Model.Result::class.java)
useResult(result)
}
}
)
override fun onResponse(call: Call<Model.Result>,
response: Response<Model.Result>) {
Toast.makeText(...).show()
})
override fun onResponse(call: okhttp3.Call,
response: okhttp3.Response) {
runOnUiThread {
Toast.makeText(...).show()
}
}
Retrofit이 OkHttp보다 좋은점은,
1. 어노테이션(Annotation) 사용으로 코드의 가독성이 좋고, 직관적인 설계가능.
2. 통신 결과값을 JSON으로 변환해줄 필요가 없음
3. 결과값을 메인스레드에서 바로 사용할 수 있음
하지만 이 둘을 같이 쓰는 이유는, OkHttp가 제공하는 Intercepter를 통해 API가 통신되는 모든 활동을 모니터링 할 수 있으며 서버 통신 시간 조절이 가능하다는 장점!
결과적으로, 최고의 성능을 내기 위해서 둘 다 사용하는 게 보편적
통신라이브러리 차이에 대해 검색하다 좋은글 읽고가요. 저도 안드로이드 개발자인데 좋은글 잘 읽었습니다!