본문 바로가기
Rx

안드로이드의 RxJava 활용 - 11( REST API를 활용한 네트워크,Retrofit2 + OkHttp)

by 봄석 2018. 12. 30.

본 내용은 필자가 학습한 내용을 정리하는 내용입니다.

대부분 의 내용이 아래 책의 내용이므로 원서를 구매해서 직접보시는걸 추천드립니다!

RxJava 프로그래밍 리액티브 프로그래밍 기초부터 RxAndroid까지 한 번에

유동환 , 박정준 지음 | 한빛미디어 | 2017년 09월 04일 출간

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788968488658&orderClick=LAV&Kc=


저자님의 블로그

https://brunch.co.kr/@yudong#info



REST API를 활용한 네트워크 프로그래밍

REST는 네트워크 아키텍처 원리의 모음으로 ' 네트워크 아키텍처 원리' 라는 자원을 정의하고 

자원에 대한 주소를 지정하는 방법 전반을 말합니다. 웹상의 자료를 HTTP위에서 SOAP나 쿠키를 통한 세션 트랙킹 같은 별도의 전송 계층 없이 전송하는 아주 간단한 인터페이스입니다.


간단하게 설명하면 아래와 같습니다.

1) HTTP를 사용한 웹서비스 입니다.

2) 모든 자원은 고유 주소 (URL) 로 식별합니다.

3) HTTP 메소드(GET, POST, PUT, DELETE)를 사용합니다.

4) JSON, XML등을 사용합니다.



Retrofit 2+ OkHttp 활용하기

OkHttp와 Retrofit은 RxAndroid를 배포하는 Square Open Source 의 자바 및 안드로이드용 네트워킹 라이브러리입니다.


OkHttp는 안드로이드에서 사용할 수 있는 대표 클라이언트 중 하나이며 페이스북에서 사용하는 것으로 유명합니다. SPDY/GZIP 지원 등 네트워킹 스택을 효율적으로 관리할 수 있고, 빠른 응답속도를 보일 수 있다는 장점이 있습니다. 


Retrofit은 서버 연동과 응답 전체를 관리하는 라이브러리 입니다. OkHttp가 서버와 연동 관련 기능만 제공한다면 응답까지 관리해 주는 면에서 편리합니다. Retrofit 1.x 버전에서는 OkHttp, HttpClient 등 사용자가 원하는 클라이언트를 선택해서 사용할 수 있었지만 2.x 버전에서는 HttpClient는 더 이상 사용할 수 없고 OkHttp에 의존하도록 변경되었습니다.


이제 Retrofit과 OkHttp 라이브러리를 RxAndroid 에서 어떻게 사용하는지 알아보겠습니다.


Retrofit 장점중 하나는 애너테이션을 지원하는 것입니다. 스프링처럼 애너테이션으로 API를 설계할 수 있습니다. 예를들어 @Header를 이용하여 헤더(header) 를 설정하고, @GET이나 @POST 등으로 사용할 HTTP 메소드를 선언할 수 있습니다.

public interface GitHubServiceApi {
    @GET("repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> getCallContributors(@Path("owner")String owner,@Path("repo"String repo);
 
    @GET("repos/{owner}/{repo}/contributors")
    Observable<List<Contributor>> getObContributors(@Path("owner"String owner,@Path("repo")String repo);
    
    @Headers({"Accept:application/vnd.github.v3.full+json"})
    @GET("repos/{owner}/{repo}/contributors")
    Future<List<Contributor>> getFutureContributors(@Path("owner"String owner,@Path("repo")String repo);
 
 

}



getCallContributors() 메소드를 호출하면 Retrofit은 기본 URI와 결합하여 URL을 생성합니다.

즉, owner와 repo에 'android'와 'RxJava를 전달하면 Retrofit은 

http://api.github.com/repos/android/RxJava/contributors로 해석하여 사용하게 됩니다 .


Retrofit은 RxJava를 정식으로 지원하므로 Observable을 API 리턴값으로 사용할 수 있습니다. 

그 외에 Call과 Future 인터페이스도 지원합니다. 그럼 아래코드에서 정의한 API를 사용할 수 있는 사용자화 Adapter 클래스를 만들어 보겠습니다.

public class RestfulAdapter {
    private static final String BASE_URI = "https://api.github.com/";
 
    public RestfulAdapter() {
    }
    private static class Singleton{
        private static final RestfulAdapter instance= new RestfulAdapter();
    }
    
    public static RestfulAdapter getInstance(){
        return Singleton.instance;
    }
    public GitHubServiceApi getServiceApi() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
 
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(loggingInterceptor).build();
 
        Retrofit retrofit = new Retrofit.Builder()
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .baseUrl(BASE_URI)
                .build();
        return retrofit.create(GitHubServiceApi.class);
    }
        public GitHubServiceApi getSimpleApi(){
            Retrofit retrofit=new Retrofit.Builder()
                    .baseUrl(BASE_URI)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
            return retrofit.create(GitHubServiceApi.class);
        }
    }

}



 Retrofit.Builder 객체를 생성하고 baseUrl() 메소드와 addConverterFactory() 메소드로 JSON 변환기를 설정하면 간단하게 생성할 수 있습니다. 마지막에 retrofit.create() 를 호출할 떄 해당 

API 인터페이스의 클래스를 넣어주면 Builder에 설정한 정보를 바탕으로 단일 인터페이스 프락시를 생성합니다.


getSimpleApi()와 getServiceAPi() 메소드의 차이점은 REST API 스택의 디버깅이 가능한 지 여부입니다. getSimpleApi() 메소드의 경우에는 Retrofit에 포함된 OkHttpClient 클래스를 사용하게 되고 getServiceApi() 메소드는 따로 OkHttpClient.Builder() 객체를 구성하여 로그를 위한 인터셉터를 설정합니다. 인터셉터를 설정하면 네트워크를 통해 이동하는 데이터나 에러 메시지를 실시간을 확인할 수 있습니다.


Contributor 클래스는 JSON 응답에서 필요한 정보 데이터를 추출합니다. 

public class Contributor {
    String login;
    String url;
    int id;
 
    @Override
    public String toString() {
        return "login : " + login + "id : " + id + "url : " + url;
    }

}

모든 필드를 정의할 필요는 없으며 원하는 정보만 JSON(이름 ,데이터타입, 구조)에 맞게 정의하면 GSON에서 디코딩하여 원하는 값을 Contributor 클래스의 필드에 설정합니다. 

즉, JSON에서 응답해야할 login, url, id만 추출합니다.



구현된 인터페이스 사용 예

public class OkHttpFragment extends Fragment {
 
    private static final String sName = "jungjoonpark-pandora";
    private static final String sRepo = "rxAndroid";
 
    @BindView(R.id.ohf_lv_log)
    ListView mLogView;
 
    private Unbinder mUnbinder;
    private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
 
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View layout = inflater.inflate(R.layout.fragment_okhttp, container, false);
 
        mUnbinder = ButterKnife.bind(this, layout);
        return layout;
    }
 
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setupLogger();
    }
 
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mUnbinder != null) {
            mUnbinder.unbind();
        }
        mCompositeDisposable.clear();
    }
 
    @OnClick(R.id.ohf_btn_retrofit)
    void getRetrofit() {
        startRetrofit();
    }
 
    @OnClick(R.id.ohf_btn_get_retrofit_okhttp)
    void getOkHttp() {
        startOkHttp();
    }
 
    @OnClick(R.id.ohf_btn_get_retrofit_okhttp_rx)
    void getRx() {
        startRx();
    }
 
 
    /**
     * retrofit + okHttp( Call의 내부 )
     */
    private void startRetrofit() {
        GitHubServiceApi service = RestfulAdapter.getInstance().getSimpleApi();
        Call<List<Contributor>> call = service.getCallContributors(sName, sRepo);
        call.enqueue(new Callback<List<Contributor>>() {
            @Override
            public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
                if (response.isSuccessful()) {
                    List<Contributor> contributors = response.body();
                    for (Contributor c : contributors) {
                        log(c.toString());
                    }
                } else {
                    log("not successful");
                }
            }
 
            @Override
            public void onFailure(Call<List<Contributor>> call, Throwable t) {
                log(t.getMessage());
            }
        });
    }
 
 
    /**
     * retrofit + okHttp
     */
    private void startOkHttp() {
        GitHubServiceApi service = RestfulAdapter.getInstance().getServiceApi();
        Call<List<Contributor>> call = service.getCallContributors(sName, sRepo);
 
        call.enqueue(new Callback<List<Contributor>>() {
            @Override
            public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
                if (response.isSuccessful()) {
                    List<Contributor> contributors = response.body();
                    for (Contributor c : contributors) {
                        log(c.toString());
                    }
                } else {
                    log("not successful");
                }
            }
 
            @Override
            public void onFailure(Call<List<Contributor>> call, Throwable t) {
                log(t.getMessage());
            }
        });
    }
 
 
    /**
     * retrofit + okHttp + rxJava
     */
    private void startRx() {
        GitHubServiceApi service = RestfulAdapter.getInstance().getServiceApi();
        Observable<List<Contributor>> observable = service.getObContributors(sName, sRepo);
 
        mCompositeDisposable.add(observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(new DisposableObserver<List<Contributor>>() {
                    @Override
                    public void onNext(List<Contributor> contributors) {
                        for (Contributor c : contributors) {
                            log(c.toString());
                        }
                    }
 
                    @Override
                    public void onError(Throwable e) {
                        log(e.getMessage());
                    }
 
                    @Override
                    public void onComplete() {
                        log("complete");
                    }
                })
 
 
        );
    }
 
 
    // Log
    private LogAdapter mLogAdapter;
    private List<String> mLogs;
 
    private void log(String log) {
        mLogs.add(log);
        mLogAdapter.clear();
        mLogAdapter.addAll(mLogs);
    }
 
    private void setupLogger() {
        mLogs = new ArrayList<>();
        mLogAdapter = new LogAdapter(getActivity(), new ArrayList<>());
        mLogView.setAdapter(mLogAdapter);
    }
}



startRx() 메소드는 RestfulAdapter 클래스의 getServiceApi() 메소드 안 retrofit 변수를 이용해 생성된 API 프락시를 가져옵니다. owner와 repo의 값을 전달하면 observable변수에 저장된 Observable을 리턴합니다. 생성된 Observable에 구독자를 설정하면 getServiceAPi() 메소드를 호출하여 github.com 에서 정보를 얻어옵니다. 결과는 구독자가 수신하게 되고 GSON에서 COntributor 클래스의 구조에 맞게 디코딩 한 다음 UI스레드를 이용해 화면에 업데이트 합니다.


startRetrofit() 메소드도 동일합니다. 하지만 getXXX() 메소드의 실행을 위해서는 retrofit 에서 제공하는 Call인터페이스를 제공합니다. Call 인터페이스의 enqueue() 메소드에 콜백을 등록하면 GSON에서 디코딩한 결과를 화면에 업데이트 할 수 있습니다.



Observable vs CompletableFuture의 비동기 요청

Observable에서 여러 개의 요청을 비동기로 처리하는 가장 간단한 방법은 요청 명령 수 만큼 REST Observable을 생성하고 flatMap() 함수를 사용하여 동시에 요청하고 결과를 구독자가 발행하는 것입니다. 


CompletableFuture에서는 각각의 명령을 Future 객체로 생성한 다음 allOf() 메소드를 이용하여 모든 명령을 비동기로 요청합니다. 결과(모든 명령에 대한 처리가 끝나면 결과를 리턴)는 Stream.of()로 묶어서 처리할 수 있습니다.


댓글