본문 바로가기
Android

[깡쌤의 안드로이드 프로그래밍 with 자바 - 2022 - 쌤즈] 정리 24 - Retrofit2 구조

by 들풀민들레 2022. 5. 9.

책의 모든 내용을 저자 직강으로 진행한 강의는 ssamz.com 에서 들으실 수 있습니다.

 

 

본 글은 [깡쌤의 안드로이드 프로그래밍 with 자바 - 2022 - 쌤즈] 의 내용을 발췌한 것입니다.
좀더 자세한 내용은 책 혹은 인강(www.ssamz.com)을 통해 확인해 주세요.

 

14.2.2. Retrofit2 구조


안드로이드 앱에서 서버와 HTTP 통신을 도와주는 유명한 라이브러리 중 하나가 Retrofit입니다. HTTP
통신 프로그램을 작성할 때 개발자 관점에서 중요한 점은 "얼마나 성능이 나올까? 얼마나 쉽게 작성할
수 있을까?"인 것 같습니다. 이 두 가지 측면에서 보면 현재 안드로이드에서 HTTP 통신 프로그램 중에
Retrofit이 가장 많은 선택을 받을 수밖에 없는 라이브러리가 아닌가 싶습니다. 인터넷상에 Retrofit과
Volley, AsyncTask의 성능을 비교한 표에서 Retrofit이 가장 빠른 성능을 자랑하고 있습니다. 또한,
Retrofit의 코드 작성이 가장 편하다는 평을 받고 있습니다(물론 필자의 생각은 이 Retrofit의 프로그램
작성 방법에 익숙하다는 전제가 있어야 합니다).

 

Retrofit을 이용해 서버 연동을 하려면 기본 Call 객체가 필요합니다. Call 객체는 실제 서버 연동을
실행하는 객체로 생각하면 됩니다. 그런데 이 Call 객체를 개발자가 직접 만들지 않고 Retrofit이
자동으로 만들어 준다는 개념입니다.

 

위의 그림은 Retrofit의 동작 흐름입니다. 여기서 개발자가 네트워크를 위해 직접 작성해야 하는 것은
인터페이스뿐입니다. 인터페이스에 네트워킹 시 호출해야 할 함수들을 등록합니다. 당연히 인터페이스
내에는 실제 네트워킹을 위한 코드가 작성되어 있지 않습니다. 그냥 추상 함수만 선언되어 있습니다.

 

이렇게 작성한 인터페이스를 Retrofit에 전달하면, Retrofit에서 인터페이스의 함수를 구현해 Call
객체를 반환하는 Service 객체를 생성합니다. 그리고 Call 객체가 실제 네트워킹을 진행합니다.
여기에서의 Service는 Retrofit에서 사용하는 용어로, 안드로이드 백그라운드 업무를 담당하기 위한
Service가 아닙니다. 즉, Service 객체는 개발자가 만든 클래스의 객체가 아니며 Service 내의 함수는
개발자가 등록한 인터페이스와 같은 이름으로 구현됩니다. 이렇게 Service 객체를 획득하여 실제
네트워킹이 필요한 순간 Service의 함수를 호출하여 Call 객체를 받고 Call 객체에게 일만 시키면
됩니다. 결국, 인터페이스를 이용하여 Service 객체를 생성해놓으면 네트워킹이 필요할 때 Call 객체만
획득해 이용하면 되는 구조입니다.
Retrofit을 이용하려면 build.gradle 파일에 의존성 설정이 필요합니다. Retrofit만을 설정해서
이용할 수도 있지만, 서버 연동을 할 때 주고받는 데이터가 대부분 JSON 파일과 XML 파일이어서
이를 파싱하는 것이 여간 불편한 게 아닙니다. 그래서 대부분은 자동으로 파싱해 개발자가 만든
VO 객체로 변환해주는 기능을 요구합니다. 이 기능을 컨버터(Convertor)라고 부릅니다. 하지만
불행히도 Retrofit에는 이 컨버터가 내장되어 있지 않습니다. 하지만 걱정할 필요 없습니다. Retrofit을
이용하면서 JSON, XML 데이터를 VO 객체로 변환해주는 외부 라이브러리 연동을 지원하므로 이를
이용하면 됩니다.
다음은 Retrofit에서 함께 이용할 수 있는 컨버터들입니다. 이 중 하나를 이용하면 됩니다. 책에서는 이
중 Gson을 이용한 실습 코드를 담겠습니다.

 

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml


먼저 build.gradle 파일의 설정은 다음과 같습니다

 

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

Model 정의


Model은 서버 연동을 위한 데이터 추상화 클래스입니다. JSON이나 XML 데이터를 개발자가 직접
파싱하지 않고 컨버터가 자동으로 생성해 객체 생성 후 변수에 파싱된 데이터를 담아 줍니다.

 

public class ItemModel {
	public long id;
	public String author;
	public String title;
	public String description;
	public String urlToImage;
	public String publishedAt;
}

Model은 데이터를 담기 위한 목적이므로 위처럼 public으로 선언한 변수들이 나열된 클래스입니다.
물론 이 클래스에 개발자가 선언한 변수나 함수를 추가해도 되지만, 컨버터가 데이터를 저장하는 변수는
public으로 선언해야 합니다.


위처럼 변수를 선언하면 컨버터는 변수명과 파싱된 데이터의 키값을 매핑해서 데이터를 저장합니다

 

{
    "author":"kkang",
    "description":"…",
    "id":1,
    "publishedAt":"2019-01-30T18:30:00Z",
    "title":"....","urlToImage":"..."
}

만약 서버에서 전송된 데이터가 위와 같다면 키값에 해당하는 변수에 각각의 데이터가 저장됩니다.
author 키값인 kkang이라는 데이터가 public String author 변수에 저장됩니다. 그런데 때로는
JSON의 키값과 변수명이 다를 때도 있습니다. 이때는 @SerializedName이라는 어노테이션을 추가해
다음처럼 변수에 할당할 데이터의 키값을 명시해주면 됩니다.

 

public class ItemModel {
    …
    @SerializedName("publishedAt")
    public String published_at;
}

또한, Model은 구조적으로 작성할 수도 있습니다. 예를 들어 아래처럼 특정 페이지 정보와 그 페이지에
포함되는 여러 항목 데이터가 넘어올 수도 있습니다.

 

{
    "articles":[{
        "author":"......",
        "description":"......",
        "id":0,
        "publishedAt":"........",
        "title":".......",
        "urlToImage":"......."
    },{
        "author":".....","description":"......",
        "id":0,
        "publishedAt":".........",
        "title":"........",
        "urlToImage":"......."
    }],
    "id":0,
    "totalResults":24718
}

만약 데이터가 위와 같다면 id, totalResults, articles를 저장하기 위한 Model 클래스와 articles
안의 몸체인 { } 부분을 저장하기 위한 Model 클래스 이렇게 두 개가 필요합니다. 예를 들어 articles
안의 몸체를 저장하기 위한 클래스가 ItemModel이라면 id, totalResults, articles를 저장하기 위한
PageListMode 클래스를 아래처럼 만들면 됩니다.

 

public class PageListModel {
    public long id;
    public long totalResults;
    public List<ItemModel> articles;
}

Retrofit 객체 생성


Model이 준비되었다면 이제 Retrofit을 이용하면 됩니다. Retrofit은 Builder에 의해 만들어지며
Builder의 세터 함수로 각종 정보를 설정합니다.

 

public class RetrofitHelper {
    static Retrofit getRetrofit() {
        return new Retrofit.Builder()
        .baseUrl("https://~~~~~~~")
        .addConverterFactory(GsonConverterFactory.create())
        .build();
    }
}

baseUrl () 함수로 전달한 인자는 Retrofit을 이용할 때 기본으로 적용되는 서버 URL입니다. 이곳에서
URL을 지정해 놓으면 실제 네트워킹 때는 이 URL 뒤에 이어지는 경로나 질의 문자열 등만 지정하면
됩니다. 물론 이렇게 baseUrl이 지정되었더라도 실제 네트워킹 때 다른 URL을 사용할 수도 있습니다.


addConverterFactory () 함수로 서버와 통신할 데이터 타입에 맞는 컨버터를 지정합니다. 위에서는
GsonConverterFactory를 지정하였으므로 JSON 데이터를 Gson 라이브러리로 파싱하고 그
데이터를 Model에 자동으로 담아줍니다.


Service 인터페이스
Retrofit의 핵심은 서버 네트워킹을 위한 함수를 인터페이스의 추상 함수로 만들고 그 함수에
어노테이션으로 GET/POST 등의 HTTP method와 서버 전송 질의를 지정하면, 그 정보에 맞게
서버를 연동하는 Call 객체를 자동으로 만들어 주는 구조라는 것입니다.

 

public interface RetrofitService {
    @GET("/v2/everything")
    Call<PageListModel> getList(@Query("q") String q,
    @Query("apiKey") String apiKey,
    @Query("page") long page,
    @Query("pageSize") int pageSize);
}

어노테이션 선언이 중요한데 위의 코드에 담긴 어노테이션의 의미를 설명해 보겠습니다.

 

@GET("/v2/everything")

Get 방식으로 연동이 이루어져야 하며 baseUrl 뒤에 Path로 "/v2/everything"가 지정되어야 한다는
설정입니다.

 

@Query("q") String q

서버 연동 시 넘어가야 하는 질의 문자열을 지정하는 부분입니다. 만약 String에 1이 대입되어 있다면
서버 URL은 https://~~~~~/v2/everything?q=1이 됩니다. 위에서는 @Query를 여러 매개변수에
지정하였습니다. 연동되는 URL은 다음과 같습니다.


https://~~~~~/v2/everything?q=1&apiKey=~~&page=1&pageSize=10

 

Call 객체 획득
이제 Call 객체를 만들어줄 Retrofit Service 객체를 획득하면 됩니다.

 

networkService = RetrofitHelper.getRetrofit().create(RetrofitService.class);

Retrofit 객체의 create () 함수로 네트워킹을 위한 Call 객체를 가지는 Service 객체가 자동으로
만들어집니다. 이렇게 얻은 Service 객체를 이용해 인터페이스에서 정의한 함수를 호출하면 Call
객체가 반환됩니다.

 

Call<PageListModel> call = networkService.getList(QUERY, API_KEY, 1, 2)

Call 객체의 제네릭 정보는 획득하고자 하는 데이터 타입입니다. 위에서 만들어놓은 Model
클래스입니다.


네트워킹 시도
Call 객체까지 획득했다면 이제 실제 네트워킹이 이루어지게 일만 시키면 됩니다. 이때 enqueue()
함수를 이용합니다.

 

call.enqueue(new Callback<PageListModel>() {
    @Override
    public void onResponse(Call<PageListModel> call, Response<PageListModel> response) {
        if(response.isSuccessful()) {
        	//…
        }
    }
    @Override
    public void onFailure(Call<PageListModel> call, Throwable t) {
    }
});

Call 객체의 enqueue ( ) 함수를 호출하면서 Callback 클래스의 객체를 매개변수로 지정하면
네트워킹을 시도하고 서버에서 정상적으로 결과를 받으면 자동으로 onResponse() 함수가 호출되어
이 함수의 매개변수로 결과 데이터가 전달됩니다. 서버 연동에 실패하면 자동으로 onFailure () 함수가
호출됩니다.

 

 

책의 모든 내용을 저자 직강으로 진행한 강의는 ssamz.com 에서 들으실 수 있습니다.