REST API를 이용하여, 날씨를 조회하는 간단한 애플리케이션 만들기
조대협 (http://bcho.tistor
네트워크를 통한 REST API 호출 방법을 알아보기 위해서, 간단하게, 위도와 경도를 이용하여 온도를 조회해오는 간단한 애플리케이션을 만들어보자
이 애플리케이션은 경도와 위도를 EditText 뷰를 통해서 입력 받아서 GETWEATHER라는 버튼을 누르면 네트워크를 통하여 REST API를 호출 하여, 날씨 정보를 받아오고, 해당 위치의 온도(화씨) 출력해주는 애플리케이션이다.
날씨 API 는 http://www.openweathermap.org/ 에서 제공하는데, 사용법이 매우 간단하고, 별도의 API인증 절차가 필요하지 않기 때문에 쉽게 사용할 수 있다. 사용 방법은 다음과 같이 쿼리 스트링으로 위도와 경도를 넘겨주면 JSON 형태로 지정된 위치의 날씨 정보를 리턴해준다.
http://api.openweathermap.org/data/2.5/weather?lat=37&lon=127
아래는 리턴되는 JSON 샘플
이 리턴되는 값중에서 main.temp 에 해당하는 값을 얻어서 출력할 것이다.
날씨값을 저장하는 클래스 작성
package com.example.terry.simpleweather.client;
/*** Created by terry on 2015. 8. 27..
*/
public class Weather {
int lat;
int ion;
int temprature;
int cloudy;
String city;
public void setLat(int lat){ this.lat = lat;}
public void setIon(int ion){ this.ion = ion;}
public void setTemprature(int t){ this.temprature = t;}
public void setCloudy(int cloudy){ this.cloudy = cloudy;}
public void setCity(String city){ this.city = city;}
public int getLat(){ return lat;}
public int getIon() { return ion;}
public int getTemprature() { return temprature;}
public int getCloudy() { return cloudy; }
public String getCity() { return city; }
}
다음으로 REST API를 호출하는 OpenWeatherAPIClient.java 코드를 다음과 같이 작성한다.
package com.example.terry.simpleweather.client;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Created by terry on 2015. 8. 27..
* 목표
* 1. AsyncTask와 HTTPURLConnection을 이용한 간단한 HTTP 호출 만들기
* 2. 리턴된 JSON을 파싱하는 방법을 통하여, JSON 객체 다루는 법 습득하기
* 3. Phone Location (GPS) API 사용 방법 파악하기
*
* 참고 자료 : http://developer.android.com/training/basics/network-ops/connecting.html
* */
public class OpenWeatherAPIClient {
final static String openWeatherURL = "http://api.openweathermap.org/data/2.5/weather";
public Weather getWeather(int lat,int lon){
Weather w = new Weather();
String urlString = openWeatherURL + "?lat="+lat+"&lon="+lon;
try {
// call API by using HTTPURLConnection
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// urlConnection.setConnectTimeout(CONNECTION_TIMEOUT);
// urlConnection.setReadTimeout(DATARETRIEVAL_TIMEOUT);
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
JSONObject json = new JSONObject(getStringFromInputStream(in));
// parse JSON
w = parseJSON(json);
w.setIon(lon);
w.setLat(lat);
}catch(MalformedURLException e){
System.err.println("Malformed URL");
e.printStackTrace();
return null;
}catch(JSONException e) {
System.err.println("JSON parsing error");
e.printStackTrace();
return null;
}catch(IOException e){
System.err.println("URL Connection failed");
e.printStackTrace();
return null;
}
// set Weather Object
return w;
}
private Weather parseJSON(JSONObject json) throws JSONException {
Weather w = new Weather();
w.setTemprature(json.getJSONObject("main").getInt("temp"));
w.setCity(json.getString("name"));
//w.setCloudy();
return w;
}
private static String getStringFromInputStream(InputStream is) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try {
br = new BufferedReader(new InputStreamReader(is));
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
코드를 하나씩 뜯어보면
안드로이드에서 HTTP 호출을 하기 위해서는 HttpURLConnection이라는 클래스를 사용한다. URL이라는 클래스에 API를 호출하고자 하는 url주소를 지정하여 생성한후, url.openConnection()을 이용해서, HTTP Connection을 열고 호출한다.
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
다음으로, 리턴되어 오는 문자열을 읽어서 JSON형태로 파싱을 해야 하는데,
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
먼저 위와 같이 urlConnection으로 부터 getInputStream() 메서드를 통해서 InputStream을 리턴받고,
JSONObject json = new JSONObject(getStringFromInputStream(in));
getStringFromInput(InputStream xx)이라는 메서드를 이용하여 inputStream을 String으로 변환한후에 JSONObject로 변환한다. (여기서 getStringFromInput은 미리 정해진 메서드가 아니고 위의 소스코드 처럼 InputStream 을 String으로 변환하기 위해서 여기서 지정된 코드들이다.)
다음으로 해당 JSON을 파싱해서, main.temp 값을 읽은후 Weather class에 넣어서 리턴을 한다.
w = parseJSON(json);
w.setIon(lon);
w.setLat(lat);
에서 parseJSON이라는 메서드를 호출하는데, parseJSON은 다음과 같다.
private Weather parseJSON(JSONObject json) throws JSONException {
Weather w = new Weather();
w.setTemprature(json.getJSONObject("main").getInt("temp"));
w.setCity(json.getString("name"));
//w.setCloudy();
return w;
}
위의 메서드에서는 json에서 먼저 getJSONObject(“main”)을 이용하여 “main” json 문서를 얻고, 그 다음 그 아래 있는 “temp”의 값을 읽어서 Weather 객체에 세팅한다.
여기까지 구현을 하면 REST API를 http를 이용하여 호출하고, 리턴으로 온 JSON 문자열을 파싱하여 Weather 객체에 세팅을해서 리턴하는 부분까지 완료가 되었다.
그다음 그러면 이 클래스의 메서드를 어떻게 호출하는가? 네트워크 통신은 IO작업으로 시간이 다소 걸리기 때문에, 안드로이드에서는 일반적으로 메서드를 호출하는 방식으로는 불가능하고, 반드시 비동기식으로 호출해야 하는데, 이를 위해서는 AsyncTask라는 클래스를 상속받아서 비동기 클래스를 구현한후, 그 안에서 호출한다.
다음은 OpenWeatherAPITask.java 클래스이다.
public class OpenWeatherAPITask extends AsyncTask<Integer, Void, Weather> {
@Override
public Weather doInBackground(Integer... params) {
OpenWeatherAPIClient client = new OpenWeatherAPIClient();
int lat = params[0];
int lon = params[1];
// API 호출
Weather w = client.getWeather(lat,lon);
//System.out.println("Weather : "+w.getTemprature());
// 작업 후 리
return w;
}
}
위와 같이 AsyncTask를 상속받았으며, 호출은 doInBackground(..)메서드를 구현하여, 그 메서드 안에서 호출을 한다.
위의 코드에서 볼 수 있듯이, doInBackground에서 앞서 작성한 OpenWeatherAPIClient의 객체를 생성한후에, 인자로 넘어온 lat,lon값을 받아서, getWeahter를 통하여, 호출하였다.
이제 API를 호출할 준비가 모두 끝났다.
이제 UI를 만들어야 하는데, res/layout/activity_main.xml은 다음과 같다.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Temperature"
android:id="@+id/tem"
android:layout_below="@+id/tvLongtitude"
android:layout_alignParentStart="true"
android:layout_marginTop="46dp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="getWeather"
android:id="@+id/getWeatherBtn"
android:onClick="getWeather"
android:layout_alignBottom="@+id/tem"
android:layout_alignParentEnd="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="latitude"
android:id="@+id/tvLatitude"
android:layout_marginTop="27dp"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="longtitude"
android:id="@+id/tvLongtitude"
android:layout_marginRight="62dp"
android:layout_marginTop="30dp"
android:layout_below="@+id/lat"
android:layout_alignParentStart="true" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/lat"
android:width="100dp"
android:layout_marginRight="62dp"
android:layout_alignBottom="@+id/tvLatitude"
android:layout_toEndOf="@+id/tvLatitude"
android:layout_marginStart="36dp" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:width="100dp"
android:id="@+id/lon"
android:layout_marginRight="62dp"
android:layout_alignBottom="@+id/tvLongtitude"
android:layout_alignStart="@+id/lat" />
</RelativeLayout>
화면 디자인이 끝났으면, 이제 MainActivity에서 버튼을 누르면 API를 호출하도록 구현해보자.
package com.example.terry.simpleweather;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import com.example.terry.simpleweather.client.OpenWeatherAPITask;
import com.example.terry.simpleweather.client.Weather;
import java.util.concurrent.ExecutionException;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
// MapView 참고 http://seuny.tistory.com/14
public void getWeather(View view)
{
EditText tvLon = (EditText) findViewById(R.id.lon);
String strLon = tvLon.getText().toString();
int lon = Integer.parseInt(strLon);
EditText tvLat = (EditText) findViewById(R.id.lat);
String strLat = tvLat.getText().toString();
int lat = Integer.parseInt(strLat);
// 날씨를 읽어오는 API 호출
OpenWeatherAPITask t= new OpenWeatherAPITask();
try {
Weather w = t.execute(lon,lat).get();
System.out.println("Temp :"+w.getTemprature());
TextView tem = (TextView)findViewById(R.id.tem);
String temperature = String.valueOf(w.getTemprature());
tem.setText(temperature);
//w.getTemprature());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
위에서 getWeather(View )부분에서 호출을하게 되는데, 먼저 앞에서 AsyncTask를 이용해서 만들었던 OpenWeatherAPITask 객체 t를 생성한 후에, t.execute로 호출을하면 된다.
실행 결과는 t.execute()에 대해서 .get()을 호출함으로써 얻을 수 있다.
이때 주의할점이 안드로이드는 네트워크 호출과 같은 리소스에 대한 접근을 통제하고 있는데, 이러한 통제를 풀려면, AnrdoidManifest.xml에 다음과 같이 INTERNET 과 NETWORK 접근을 허용하는 내용을 추가해줘야 한다.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
다음은 위의 내용을 추가한 AndroidManifest.xml이다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.terry.simpleweather" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
참고 : http://developer.android.com/training/basics/network-ops/connecting.html
9월 15일 추가 내용
- t.execute().get 보다는 onPostExecute 를 통해서 리턴 값을 받는 것이 더 일반적인 패턴
- AsyncTask를 사용하기 보다는 근래에는 Retrofit이라는 프레임웍을 사용하는게 더 효율적이며, 성능도 2배이상 빠름.
관련 튜토리얼 : http://gun0912.tistory.com/30
아래는 성능 비교 자료
'프로그래밍 > 안드로이드' 카테고리의 다른 글
SharedPreference를 이용한 데이타 저장 및 애플리케이션간 데이타 공유 (0) | 2015.09.23 |
---|---|
안드로이드 웹뷰(Webview)의 이해와 성능 최적화 방안 (3) | 2015.09.16 |
안드로이드 주요 레이아웃 (1) | 2015.08.27 |
안드로이드 기초 - 컴파일,설치,실행 (커맨드라인) (0) | 2015.08.18 |
안드로이드 리소스파일 (Resource) (0) | 2015.08.09 |