인터넷은 우리 삶의 중요한 한 부분으로서 우리 중 대부분은 새로운 정보에 대한 탐욕적인 욕구를 발달시켜왔습니다. 우리의 주의 지속 시간도 그 어느 때보다도 짧아서 콘텐츠가 정적인 안드로이드 애플리케이션을 개발하는 것은 좋지 않은 생각일 수 있습니다. 대신 사용자가 열 때마다 새로운 내용을 표시할 수 있는 애플리케이션을 만드는 것을 고려해야 합니다.
그렇게 하기가 어려울 수도 있겠지만 REST API를 통해 리소스를 노출하는 웹 사이트가 점점 더 많아지면서 실제로는 그리 어렵지 않게 할 수 있게 됐습니다. (초보자를 위한 HTTP와 REST 안내서를 참고하세요.)
이번 튜토리얼에서는 안드로이드 SDK에서 이용할 수있는 클래스와 메서드를 사용해 원격 웹 서버에 연결하고 REST API를 사용해 상호작용하는 방법을 보여드리겠습니다.
1. 인터넷 접속 활성화
REST API를 사용하려면 당연히 인터넷을 이용할 수 있어야 합니다. 하지만 안드로이드 애플리케이션은 android.permission.INTERNET
권한이 있는 경우에만 인터넷에 접속할 수 있습니다. 따라서 네트워킹 코드 작성을 시작하기 전에 프로젝트의 매니페스트 파일에 다음과 같은 uses-permission
태그가 있는지 확인해야 합니다.
1 | < uses-permission android:name = "android.permission.INTERNET" /> |
android.permission.INTERNET
은 위험한 권한으로 간주되지 않으므로 런타임 시 API 레벨 23 이상을 실행하는 기기를 대상으로 요청할 필요가 없습니다.
2. 백그라운드 스레드 생성
안드로이드 플랫폼에서는 애플리케이션의 메인 스레드에서 네트워크 작업을 수행하는 것을 허용하지 않습니다. 따라서 모든 네트워킹 코드는 백그라운드 스레드에 속해야 합니다. 그러한 스레드를 만드는 가장 쉬운 방법은 AsyncTask
클래스의 execute()
메서드를 사용하는 것입니다. execute()
메서드는 유일한 인자로 Runnable
객체를 받습니다.
1 2 3 4 5 6 7 | AsyncTask.execute( new Runnable() { @Override public void run() { // All your networking logic // should be here } }); |
백그라운드 스레드에서 작업을 실행하는 것에 대해 자세히 알고 싶다면 '안드로이드 처음부터 배우기(Android From Scratch)' 시리즈의 백그라운드 작업에 대한 튜토리얼을 읽어보길 바랍니다.
3. HTTP 연결 생성
URL
클래스의 openConnection()
메서드를 이용하면 어떤 REST 엔드포인트에 대한 연결도 빠르게 설정할 수 있습니다. 엔드포인트에 HTTP 또는 HTTPS를 통해 접속하는지 여부에 따라 openConnection()
의 반환값을 HttpURLConnection
이나 HttpsURLConnection
인스턴스로 형변환해야 합니다. HttpURLConnection
과 HttpsURLConnection
모두 요청 헤더를 추가하거나 응답을 읽는 것과 같은 작업을 허용합니다.
다음 코드는 GitHub API의 루트 엔드포인트와의 연결을 설정하는 방법을 보여줍니다.
1 2 3 4 5 6 | // Create URL // Create connection HttpsURLConnection myConnection = (HttpsURLConnection) githubEndpoint.openConnection(); |
참고로 HttpsURLConnection
은 HttpURLConnection
클래스의 하위 클래스입니다.
4. 요청 헤더 추가
REST API를 제공하는 대부분의 웹 사이트에서는 앱을 고유하게 식별할 수 있기를 원합니다. 그러한 웹 사이트를 돕는 가장 쉬운 방법은 모든 요청에 고유한 User-Agent
헤더를 포함하는 것입니다.
요청에 User-Agent
헤더를 추가하려면 HttpURLConnection
객체의 setRequestProperty()
메서드를 사용해야 합니다. 예를 들어, User-Agent
헤더를 my-rest-app-v0.1로 설정하는 방법은 다음과 같습니다.
1 | myConnection.setRequestProperty( "User-Agent" , "my-rest-app-v0.1" ); |
setRequestProperty()
메서드를 여러 번 호출해서 요청에 헤더를 여러 개 추가할 수 있습니다. 예를 들어, 다음 코드는 Accept
헤더와 사용자 정의 Contact-Me
헤더를 추가합니다.
1 2 3 4 | myConnection.setRequestProperty( "Accept" , "application/vnd.github.v3+json" ); myConnection.setRequestProperty( "Contact-Me" , "hathibelagal@example.com" ); |
5. 응답 읽기
모든 요청 헤더를 전달하고 나면 HttpURLConnection
객체의 getResponseCode()
메서드를 이용해 유효한 응답이 있는지 확인할 수 있습니다.
1 2 3 4 5 6 | if (myConnection.getResponseCode() == 200 ) { // Success // Further processing here } else { // Error handling code goes here } |
HttpURLConnection
클래스가 301과 같은 리디렉션 응답 코드를 받으면 이를 자동으로 처리하고 리디렉션을 따릅니다. 따라서 일반적으로 리디렉션을 확인하는 추가 코드를 작성할 필요는 없을 것입니다.
오류가 없는 경우 이제 getInputStream()
메서드를 호출해서 연결의 입력 스트림에 대한 참조를 가져올 수 있습니다.
1 | InputStream responseBody = myConnection.getInputStream(); |
요즘 대부분의 REST API는 유효한 JSON 문서 형식의 데이터를 반환합니다. 따라서 InputStream
객체에서 직접 읽는 대신 InputStreamReader
를 만들기를 제안합니다.
1 2 | InputStreamReader responseBodyReader = new InputStreamReader(responseBody, "UTF-8" ); |
6. JSON 응답 파싱
안드로이드 SDK에는 JsonReader라는 클래스가 있어서 이 클래스로 JSON 문서를 손쉽게 파싱할 수 있습니다. JsonReader
클래스의 생성자에 InputStreamReader
객체를 전달해서 새 인스턴스를 만들 수 있습니다.
1 | JsonReader jsonReader = new JsonReader(responseBodyReader); |
JSON 문서에서 특정 정보를 추출하는 방법은 구조에 따라 다릅니다. 예를 들어, GitHub REST API의 루트 엔드포인트에서 반환된 JSON 문서는 다음과 같습니다.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | { "current_user_authorizations_html_url" : "https://github.com/settings/connections/applications{/client_id}" , "organization_repositories_url" : "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}" , "repository_search_url" : "https://api.github.com/search/repositories?q=1{&page,per_page,sort,order}" , } |
보다시피 응답은 여러 키가 포함된 하나의 커다란 JSON 객체에 불과합니다. 여기서 organization_url이라는 키의 값을 추출하려면 다음과 같은 코드를 작성해야 합니다.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | jsonReader.beginObject(); // Start processing the JSON object while (jsonReader.hasNext()) { // Loop through all keys String key = jsonReader.nextName(); // Fetch the next key if (key.equals( "organization_url" )) { // Check if desired key // Fetch the value as a String String value = jsonReader.nextString(); // Do something with the value // ... break ; // Break out of the loop } else { jsonReader.skipValue(); // Skip values of other keys } } |
위 코드에서는 JSON 응답을 토큰 스트림으로 처리합니다. 따라서 메모리를 거의 소비하지 않습니다. 하지만 모든 토큰을 하나씩 처리해야 하기 때문에 큰 응답을 처리하는 동안 속도가 느려질 수 있습니다.
필요한 모든 정보를 추출하고 나면 JsonReader
객체의 close()
메서드를 호출해서 보유한 모든 리소스를 해제해야 합니다.
1 | jsonReader.close(); |
또한 HttpURLConnection
객체의 disconnect()
메서드를 호출해서 연결을 닫아야 합니다.
1 | myConnection.disconnect(); |
7. 다양한 HTTP 메서드 사용하기
HTTP 기반 REST 인터페이스는 HTTP 메서드를 사용해 자원을 대상으로 수행해야 하는 연산의 유형을 결정합니다. 이전 단계에서는 HTTP GET
메서드를 사용해 읽기 연산을 수행했습니다. HttpURLConnection
클래스는 기본적으로 GET
메서드를 사용하기 때문에 명시적으로 지정하지 않아도 됩니다.
HttpURLConnection
객체의 HTTP 메서드를 변경하려면 setRequestMethod()
메서드를 사용해야 합니다. 예를 들어, 다음 코드는 httpbin.org에 속한 엔드포인트에 대한 연결을 열고 HTTP 메서드를 POST
로 설정합니다.
1 2 3 4 5 | HttpsURLConnection myConnection = (HttpsURLConnection) httpbinEndpoint.openConnection(); myConnection.setRequestMethod( "POST" ); |
이미 알고 계실 수도 있겠지만 POST
요청은 서버에 데이터를 보내는 데 사용됩니다. 연결의 출력 스트림에 쓰는 식으로 POST
요청의 본문에 어떤 데이터도 손쉽게 추가할 수 있습니다. 그러나 그렇게 하기 전에 HttpURLConnection
객체의 setDoOutput()
메서드를 호출하고 true
를 전달해야 합니다.
다음 코드는 간단한 키-값 쌍을 서버에 보내는 방법을 보여줍니다.
1 2 3 4 5 6 7 8 | // Create the data String myData = "message=Hello" ; // Enable writing myConnection.setDoOutput( true ); // Write the data myConnection.getOutputStream().write(myData.getBytes()); |
8. 응답 캐싱하기
HTTP 응답을 캐싱하는 것은 언제나 좋은 생각입니다. 그렇게 함으로써 앱의 대역폭 소비를 줄일 수 있을뿐더러 응답 속도도 향상시킬 수 있습니다. API 레벨 13부터 안드로이드 SDK에서는 HttpResponseCache
라는 클래스를 제공하므로 네트워킹 로직을 변경하지 않고도 손쉽게 캐싱을 구현할 수 있습니다.
애플리케이션에 캐시를 설치하려면 HttpResponseCache
클래스의 install()
메서드를 호출해야 합니다. 이 메서드는 캐시가 설치될 위치를 나타내는 절대 경로와 캐시의 크기를 나타내는 숫자를 인자로 받습니다. 수동으로 절대 경로를 지정하고 싶지 않다면 getCacheDir()
메서드를 이용하면 됩니다.
다음 코드는 크기가 100,000바이트인 캐시를 설치합니다.
1 2 | HttpResponseCache myCache = HttpResponseCache.install( getCacheDir(), 100000L); |
캐시가 설치되면 HttpURLConnection
클래스가 캐시를 자동으로 사용하기 시작합니다. 캐시가 작동하는지 확인하려면 캐시에서 제공된 HTTP 응답의 개수를 반환하는 getHitCount()
메서드를 사용하면 됩니다.
1 2 3 | if (myCache.getHitCount() > 0 ) { // The cache is working } |
결론
안드로이드 앱에서 자유롭게 사용할 수 있는 REST API는 수천 개에 달합니다. 이러한 REST API를 이용하면 앱을 더욱 유익하고, 흥미롭고, 기능이 풍부하게 만들 수 있습니다. 이번 튜토리얼에서는 HttpURLConnection
클래스를 이용해 이러한 REST API를 사용하는 법을 배웠습니다. 또한 앱의 대역폭 사용량을 낮게 유지하는 HTTP 응답 캐시를 만드는 방법도 배웠습니다.
HttpURLConnection
을 사용하는 것이 어렵다면 Volley와 같은 서드파티 라이브러리를 사용해 보십시오. 이 같은 라이브러리는 내부적으로 HttpURLConnection
클래스를 사용하지만 코드를 좀 더 간결하고 읽기 쉽게 만들 수 있는 편리한 메서드를 많이 제공합니다.
안드로이드 플랫폼의 네트워킹에 대한 자세한 내용은 안드로이드 네트워크 작업 가이드를 참고합니다.