날씨 얻어 오기
우리는 실제 데이터를 얻어 올 것입니다 openweathermap 싸이트에 API를 이용합니다
http://openweathermap.org/
전세계 날씨 정보를 얻어 올 수 있는 곳입니다 싸이트에 접속해서 회원가입 후 API키를 받습니다
http://openweathermap.org/api 많은 데이터를 얻을 수 있습니다 현재 날씨 / 앞으로 5일간 3시간 간격 / 16일 동안 매일매일 등등 정말 많은 데이터를 얻어 올 수 있습니다
http://openweathermap.org/weather-conditions 에 접속하면 날씨 정보에 대한 아이콘을 볼 수 있습니다
차후에 이 아이콘을 사용할 예정 입니다
http://openweathermap.org/city/1835848 서울에 대한 날씨에 대한 페이지입니다
앞으로 Application에서 보여줄 데이터를 얻어와야 합니다 openweathermap을 통해서요 복잡한 주소로 이루어져 있지만 사실 규칙이 있고 규칙에만 맞으면 데이터를 가져올 수 있습니다
연습 : 서울의 1주일 날씨를 JSON 데이터 형태로 온도는 metric하게 필요하다
정리 : 1 Week, city (seoul), JSON, metric(˚C ⟷ ˚F)
http://api.openweathermap.org/data/2.5/forecast/daily?q=seoul,kr&mode=json&units=metric&cnt=7&appid={API_KEY}
1 2 3 4 5 6 http://api.openweathermap.org/data/2.5/forecast/daily? q=seoul,kr &mode=json &units=metric &cnt=7 &appid={API_KEY}
처음에는 어렵습니다
각 웹싸이트들은 API를 제공하고 있습니다 개발자들을 위해서 편하게 제공하는 것입니다
많이 사용하면 적응이 되고 쉽게 사용할 수 있습니다.
서울 말고도 여러 도시를 연습해 보세요
HTTP REQUEST FOR WEATHER
Make HTTP Request
Read response from input stream
Clean up and log any errors
Android에서 HTTP를 요청하는 방법은 2가지가 있습니다
HttpURLConnection
HttpClient
두가지 방법 모두 Https, 스트리밍 업로드, 다운로드, 시간제한, IPv6, 폴링 지원합니다 용어를 잘 모르겠다면 한번 검색하세요 HTTP 통신에 대한 기본적인 내용입니다 Android에서 주로 추천하는 HttpURLConnection 입니다 오픈 소스를 사용하면 okhttp 도 있습니다
Android HTTP에 대해서 더 알고 싶다면http://android-developers.blogspot.kr/2011/09/androids-http-clients.html http://developer.android.com/training/basics/network-ops/connecting.html 여기를 참고하세요
okhttp에 대해서 알고 싶으면http://square.github.io/okhttp/ 여기를 참고하세요
Log
Android 작업을 하다 보면 우리는 예상 못 한 많은 결과를 보게 됩니다 이를 해결할 방법이 Log입니다
VS로 작업을 할 때는 Debug를 모드를 이용해서 많이 진행하였지만 Android에서 같은 방법으로 진행하면 상당히 힘듭니다
Log는 Android Studio에 있는 Logcat을 이용해서 볼 수 있습니다
여기에서 Android Monitor 클릭하면 됩니다
Log는 총 5가지의 종류가 있습니다
ERROR : 에러 상황을 표시합니다
WARN : 경고 상황을 표시합니다
INFO : INFO 현재 각 단계에 맞는 상황을 표시합니다
DEBUG : 개발 할 때는 보이지만 배포 때는 제외됩니다
VERBOSE : 개발 할 때는 포함되지만 배포 할때는 컴파일이 되어서는 안 됩니다
1 Log.e(String tag, String msg)
Log를 작성하고 볼 때 중요한 것이 있습니다 LogSpam을 조심해야 합니다
너무 많은 Log가 존재하면 중요한 Log가 숨겨지거나 표시가 안 될 수 있습니다 이런 사항을 조심해야 합니다 Log 버퍼에 용량은 한정되어 있기 때문입니다
우리는 앞으로 많은 코드를 실제로 작성도 하기는 하지만 잘 작성하신 선배님들의 코드를 Copy & Paste를 합니다 그럴 때 설정해야 하는 것이 있습니다 Import를 자동으로 처리하게 해주는 것 입니다
이제 실제로 코드를 작성해 봅시다
생각보다 많은 양이라서 미리 준비했습니다
다음 링크에서 코드를 복사해서 붙여넣어 봅시다
MainActivityFragment.java 파일에서 작업하면 됩니다
1 2 listView.setAdapter(mForecastAdapter);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 HttpURLConnection urlConnection = null ; BufferedReader reader = null ; String forecastJsonStr = null ; try { URL url = new URL("http://api.openweathermap.org/data/2.5/forecast/daily?q=seoul,kr&mode=json&units=metric&cnt=7&APPID={API_KEY}" ); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET" ); urlConnection.connect(); InputStream inputStream = urlConnection.getInputStream(); StringBuffer buffer = new StringBuffer(); if (inputStream == null ) { forecastJsonStr = null ; } reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null ) { buffer.append(line + "\n" ); } if (buffer.length() == 0 ) { forecastJsonStr = null ; } forecastJsonStr = buffer.toString(); } catch (IOException e) { Log.e("MainActivityFragment" , "Error " , e); forecastJsonStr = null ; } finally { if (urlConnection != null ) { urlConnection.disconnect(); } if (reader != null ) { try { reader.close(); } catch (final IOException e) { Log.e("MainActivityFragment" , "Error closing stream" , e); } } }
이제 openweathermap에서 정보를 가져올 수 있습니다
코드를 실행을 시켜서 데이터를 가져오는지 확인하기 위해 실행을 합니다
Error가 발생하면서 안됩니다, Why? Log를 확인해서 무슨 ERROR인지 확인해 봅시다
NetworkOnMainThreadException
Threads
Android에서는 크게 2가지의 Thread로 구분합니다
Main Thread (aka UI Thread) - All user input + output Main Thread 에서는 긴 작업은 피해야 합니다
Background Thread - for long-running work Background Thread에서는 네트워크 호출, DB 읽기 쓰기, 비트맵 압축 등 긴 작업을 주로 처리합니다
지금 Http Request 같은 작업을 진행할 때는 오래 걸리기 때문에 Main Thread에서 제거 해야 합니다
이것을 Background Thread에서 동작하기 위해서는 몇 가지 선택이 있습니다 그중에서 우리는 대표적인 것을 가지고 작업을 하겠습니다
processes-and-threads Document
AsyncTask
Android는 Java를 기반으로 하므로 Runnable interface나 Thread Class를 이용해도 됩니다 그러나 생각보다 손이 많이 갑니다
Android에서 제공하는 걸 사용합시다
버튼을 누르면 웹에서 이미지를 다운받고 다운받은 이미지를 보여주는 코드 1 2 3 4 5 6 7 8 9 10 11 12 13 public void onClick (View v) { new Thread(new Runnable() { public void run () { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png" ); mImageView.post(new Runnable() { public void run () { mImageView.setImageBitmap(bitmap); } }); } }).start(); }
어렵다…..
Android에서 제공하는 AsyncTask로 표현한 코드 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void onClick (View v) { new DownloadImageTask().execute("http://example.com/image.png" ); } private class DownloadImageTask extends AsyncTask <String , Void , Bitmap > { protected Bitmap doInBackground (String... urls) { return loadImageFromNetwork(urls[0 ]); } protected void onPostExecute (Bitmap result) { mImageView.setImageBitmap(result); } }
아직 내용은 잘 모르겠지만, 더 짧고 쉬워 보인다
AsyncTask는 사실 간단합니다
Main Thread에서 할 일과 Background Thread에서 할 일만 정하면 됩니다
Android Developer 싸이트에서 AsyncTask를 찾고 어떤 작업이 MainThread인지 Background Thread인지 찾아봅시다
onPreExecute() / M
doInBackground() / B
onProgressUpdate() / M
onPostExecute() / M
doInBackground를 진행하면서 publishProgress()를 이용하면 현재 작업이 얼마나 진행되었는지 알 수 있습니다 쉽게 생각하면 우리가 다운로드 몇% 되었는지 보는 거랑 같아요
FetchWeatherTask class를 MainActivityFragment class 안에 생성
FetchWeatherTask class는 AsyncTask 상속
FetchWeatherTask class 완성
FetchWeatherTask class안에 doInBackground method 안에 완성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class FetchWeatherTask extends AsyncTask <Void , Void , Void > { private final String LOG_TAG = FetchWeatherTask.class.getSimpleName(); @Override protected Void doInBackground (Void... params) { HttpURLConnection urlConnection = null ; BufferedReader reader = null ; String forecastJsonStr = null ; try { URL url = new URL("http://api.openweathermap.org/data/2.5/forecast/daily?q=seoul,kr&mode=json&units=metric&cnt=7&appid={API_KEY}" ); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET" ); urlConnection.connect(); InputStream inputStream = urlConnection.getInputStream(); StringBuffer buffer = new StringBuffer(); if (inputStream == null ) { forecastJsonStr = null ; } reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null ) { buffer.append(line + "\n" ); } if (buffer.length() == 0 ) { forecastJsonStr = null ; } forecastJsonStr = buffer.toString(); } catch (IOException e) { Log.e(LOG_TAG, "Error " , e); forecastJsonStr = null ; } finally { if (urlConnection != null ) { urlConnection.disconnect(); } if (reader != null ) { try { reader.close(); } catch (final IOException e) { Log.e(LOG_TAG, "Error closing stream" , e); } } } return null ; } }
코드를 잘 넣었으면 동작하는데 아까처럼 Error가 발생하지는 않습니다
우리는 앞으로 개발을 위해서 Refresh Button를 추가합니다 하지만 Refresh Button은 실제 Application에서는 제외 해야 합니다
사용자가 Refresh를 원하기 전에 자동으로 해주는 것이 좋은 것 입니다 Save Button도 필요 없습니다
다 구시대적 UI/UX입니다
우리 최신에 맞게 합시다
우리가 열심히 AsyncTask 로 변화도 시켰고 했지만 사실 여기에는 약점이 있습니다
안드로이드 자체에 있는 LifeCycle 에 치명적입니다
그래서 우리는 다른 방법을 찾아야 합니다
일단 우리는 Background Thread에 집중하겠습니다
너무 많은 걸 한꺼번에 하면 어려워요
천천히 단계를 밟아가면서 진행을 하도록 합시다
Menu에 대해서 알아봅시다
메뉴 문자열
app - resources - menu - menu_main.xml
1 2 3 4 5 6 7 8 9 10 <menu xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" tools:context =".MainActivity" > <item android:id ="@+id/action_settings" android:orderInCategory ="100" android:title ="@string/action_settings" app:showAsAction ="never" /> </menu >
우리가 만들 메뉴는 임시로 처리하는 거닌깐 파일을 하나 만들어서 처리해 봅시다
forecast_fragment_menu.xml 파일을 하나 만들어 봅시다
우리가 만들 메뉴의 ID는 action_refresh이고 메뉴에 label은 Refresh 입니다 그리고 이것을 실제로 메뉴로 나타나게 해야 합니다
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" > <item android:id ="@+id/action_refresh" android:title ="@string/action_refresh" app:showAsAction ="never" /> </menu >
우리가 Refresh라고 쓴 글자는 app - resources - value - string.xml 에 저장이 됩니다
string.xml에 저장된 글자들은 번역을 하게 됩니다 value-ko 폴더를 만들고 string.xml 만들어서 각 내용을 번역한다면 한국어도 지원이 됩니다 value-es 스페인어 value-fr 불어 등등 이렇게 처리하면 됩니다 만약 번역이 필요 없는 경우가 있습니다
그럴 때는
1 <string name ="action_refresh" translatable ="false" > Refresh</string >
적으면 됩니다
메뉴를 사용할 건지 선언을 하기 위해서는 LifeCycle을 이해할 필요가 있습니다
Override를 할 때 쉽게 하는 방법이 있습니다
위에 메뉴에서 Code - Override Method를 클릭하고 내가 필요한 Override Method를 선택하면 됩니다 단축키 : Ctrl + O(Win), ⌘ + O(Mac)
1 2 3 4 5 6 7 8 9 10 @Override public void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setHasOptionsMenu(true ); } @Override public void onCreateOptionsMenu (Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.forecast_fragment_menu, menu); }
일단 메뉴를 사용한다는 사실을 알려줍니다 그리고 사용할 메뉴에 대해서 알려줍니다
Refresh Button 을 눌렀을 때 동작도 추가해야 합니다
1 2 3 4 5 6 7 8 @Override public boolean onOptionsItemSelected (MenuItem item) { int id = item.getItemId(); if (id == R.id.action_refresh) { return true ; } return super .onOptionsItemSelected(item); }
Refresh Button을 클릭했을 때 다시 정보를 얻어와야 합니다 다음과 같이 수정합니다
1 2 3 4 5 6 7 8 9 10 @Override public boolean onOptionsItemSelected (MenuItem item) { int id = item.getItemId(); if (id == R.id.action_refresh) { FetchWeatherTask fetchWeatherTask = new FetchWeatherTask(); fetchWeatherTask.execute(); return true ; } return super .onOptionsItemSelected(item); }
이제 실제로 빌드를 하고 Refresh 버튼을 클릭해 봅시다 또 안됩니다 Logcat을 이용해서 Error를 찾아봅시다
OutOfMemoryException
SecurityException
NetworkOnMainThreadException
IOException
SecurityException
Permissions
안드로이드 앱을 만들다 보면 Permissions이 필요하게 됩니다 잘 모르겠다고요?
좀 더 자세하게 설명하겠습니다
Android is middleware 리눅스 기반에 Kernel에서 돌아갑니다 리눅스에서 각각에 Android Application 은 고유 ID를 부여받습니다 서로 서로 침범을 못 하도록 SandBox에 보관하게 됩니다
민감한 데이터나 인터넷 접속같이 기기의 중요사항들을 차단합니다 이것을 해결하기 위해서 Permissions이 필요합니다
http://developer.android.com/guide/topics/security/permissions.html
Android Permissions에 대해서 정리되어있는 개발문서입니다
단순한 생각으로 ‘여기 있는 모든 Permissions을 추가하면 되겠다’는 생각을 가지면 안됩니다 정말 나쁜 생각입니다 그러면 안 됩니다!!!! 우리가 정말 필요한 최소한의 Permissions만 요청하도록 합시다
Application에서 직접 해결하는 방법보다는 기존 것을 사용하는 방법을 더 추천 드립니다
사진을 찍기 위해서 카메라 권한을 요청 하는 것보다 카메라 앱을 이용해서 사진을 찍는 것
Android 6.0 (Marshmallow)부터 Permissions에 대한 내용이 상당히 많이 변경되었습니다
Permissions에 대해서 모든 권한을 한 번에 승인을 하는 방식이 아닌 필요할 때 요청하는 방식으로 변경되었습니다
Marshmallow 기반으로 Application을 제작할 때는 Permissions에 대한 생각을 많이 하셔야 합니다
추천하는 라이브러리는 TedPermission 입니다.
manifest.xml 파일에 Permission을 추가합시다
어떤 Permission이 필요할까요?
android.permission.INTERNET 실제 코드를 적어 봅시다
1 2 3 4 5 6 <uses-permission android:name ="android.permission.INTERNET" /> <application <!-- 생략-- > >
실제로 데이터가 들어오는지 확인도 해야 합니다
MainActivityFragment.java - FetchWeatherTask - doInBackground()에서 작성합니다
1 2 3 4 5 6 7 forecastJsonStr = buffer.toString(); Log.v(LOG_TAG, "Forecast JSON String : " + forecastJsonStr); } catch (IOException e) { }
이러고 실행을 합시다
Logcat을 이용해서 보면 데이터가 잘 들어온 것을 확인할 수 있습니다
1 02-05 20:08:05.002 11691-12680/com.study.sunshine V/FetchWeatherTask: Forecast JSON String : "내용"
생각해보니깐 지금 도시가 Uri에서 고정되어 있습니다 이것을 입력을 통해서 도시를 수정하도록 합시다
추가로 소스가 너무 더러운 부분이 많아요 고쳐봐요 (이걸 Refactoring이라고 부릅니다)
URL을 고정하게 하지 말고 Class를 사용해서 만들 수 있습니다
1 2 3 4 5 6 7 8 9 10 @Override public boolean onOptionsItemSelected (MenuItem item) { int id = item.getItemId(); if (id == R.id.action_refresh) { FetchWeatherTask fetchWeatherTask = new FetchWeatherTask(); fetchWeatherTask.execute('사용자입력' ); return true ; } return super .onOptionsItemSelected(item); }
이렇게 수정합니다
하나하나씩 수정해 봅시다
1 2 3 public class FetchWeatherTask extends AsyncTask <Void , Void , Void > { }
AsyncTask< Params, Progress, Result> 의 구조입니다 우리는 실행을 할 때 도시 이름을 넣어서 전달하기 때문에 Params을 수정해야합니다
1 2 3 4 5 6 7 8 9 public class FetchWeatherTask extends AsyncTask <String , Void , Void > { private final String LOG_TAG = FetchWeatherTask.class.getSimpleName(); @Override protected Void doInBackground (String... params) { } }
다음으로 URL을 만들어야 합니다 Uri를 만들고 그것을 URL로 변경하면 됩니다
Uri 클래스를 찾아보고 어떻게 진행을 해야 하는지 생각해 보세요
Uri Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 String forecastJsonStr = null ; String format = "JSON" ; String units = "metric" ; int numDays = 7 ;try { final String FORECAST_BASE_URL = "http://api.openweathermap.org/data/2.5/forecast/daily?" ; final String QUERY_PARAM = "q" ; final String FORMAT_PARAM = "mode" ; final String UNITS_PARAM = "units" ; final String DAYS_PARAM = "cnt" ; final String APPID_PARAM = "APPID" ; Uri uri = Uri.parse(FORECAST_BASE_URL).buildUpon() .appendQueryParameter(QUERY_PARAM, params[0 ]) .appendQueryParameter(FORMAT_PARAM, format) .appendQueryParameter(UNITS_PARAM, units) .appendQueryParameter(DAYS_PARAM, Integer.toString(numDays)) .appendQueryParameter(APPID_PARAM, BuildConfig.OPEN_WEATHER_MAP_API_KEY) .build(); URL url = new URL(uri.toString()); Log.v(LOG_TAG, "URI : " + uri.toString()); }
BuildConfig.OPEN_WEATHER_MAP_API_KEY 값은 build.gradle(Module: app) 파일에
1 2 3 buildTypes.each { it.buildConfigField 'String' , 'OPEN_WEATHER_MAP_API_KEY' , '"API_KEY"' }
추가하면 됩니다
주의 : ‘ ‘(작은따옴표)를 사용해야 합니다
JSON Parser 이제 데이터도 잘 가지고 오고 내가 원하는 곳으로 이동도 잘 됩니다 그러면 가지고 온 데이터를 이용하겠습니다https://jsonformatter.curiousconcept.com/ 위 싸이트를 접속해서 우리가 받아온 데이터를 한번 넣어 봅시다
알 수 없던 정체불명의 데이터를 이쁘게 볼 수 있네요 한번 연습을 해야 합니다 어려울 수 있으나 JSON에 대해서 분석하고 파악해야 합니다
1 2 3 4 5 public static double getMaxTemperatureForDay (String weatherJsonStr, int dayIndex) throws JSONException { return -1 ; }
그날의 최고기온을 뽑아 봅시다
dayIndex는 우리가 받아 온 데이터에서 +며칠인지를 의미합니다 String weatherJsonStr 은 아마 JSON 데이터를 그냥 넘겨 받은 거 같으니 JSON 데이터로 변환하여서 처리합시다
JSON을 처리하기 위해서는 2가지만 알면 됩니다
JSONObject, JSONArray
직접 찾아서 한번 만들어 보세요
1 2 3 4 5 6 7 8 9 public static double getMaxTemperatureForDay (String weatherJsonStr, int dayIndex) throws JSONException { JSONObject weather = new JSONObject(weatherJsonStr); JSONArray days = weather.getJSONArray("list" ); JSONObject dayInfo = days.getJSONObject(dayIndex); JSONObject temperatureInfo = dayInfo.getJSONObject("temp" ); return temperatureInfo.getDouble("max" ); }
http://developer.android.com/reference/org/json/JSONObject.html
우리 Application에서 실제로 적용합시다
다음과 같이 FetchWeatherTask에서 결과 값이 나오도록 해야 합니다
조금 전 우리는 JSON 데이터 타입을 분석해서 가져오는 법을 했습니다 그것을 JSON 데이터를 분석하고 우리가 표시하고 싶은 형태로 바꾸도록 합시다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class FetchWeatherTask extends AsyncTask <String , Void , Void > { private final String LOG_TAG = FetchWeatherTask.class.getSimpleName(); @NonNull private String getReadableDateString (long time) { SimpleDateFormat shortenedDateFormat = new SimpleDateFormat("EEE MMM dd" ); return shortenedDateFormat.format(time); } private String formatHighLows (double high, double low) { long roundedHigh = Math.round(high); long roundedLow = Math.round(low); String highLowStr = roundedHigh + "/" + roundedLow; return highLowStr; } private String[] getWeatherDataFromJson(String forecastJsonStr, int numDays) throws JSONException { final String OWM_LIST = "list" ; final String OWM_WEATHER = "weather" ; final String OWM_TEMPERATURE = "temp" ; final String OWM_MAX = "max" ; final String OWM_MIN = "min" ; final String OWM_DESCRIPTION = "main" ; JSONObject forecastJson = new JSONObject(forecastJsonStr); JSONArray weatherArray = forecastJson.getJSONArray(OWM_LIST); Time dayTime = new Time(); dayTime.setToNow(); int julianStartDay = Time.getJulianDay(System.currentTimeMillis(), dayTime.gmtoff); dayTime = new Time(); String[] resultStrs = new String[numDays]; for (int i = 0 ; i < weatherArray.length(); i++) { String day; String description; String highAndLow; JSONObject dayForecast = weatherArray.getJSONObject(i); long dateTime; dateTime = dayTime.setJulianDay(julianStartDay+i); day = getReadableDateString(dateTime); JSONObject weatherObject = dayForecast.getJSONArray(OWM_WEATHER).getJSONObject(0 ); description = weatherObject.getString(OWM_DESCRIPTION); JSONObject temperatureObject = dayForecast.getJSONObject(OWM_TEMPERATURE); double high = temperatureObject.getDouble(OWM_MAX); double low = temperatureObject.getDouble(OWM_MIN); highAndLow = formatHighLows(high, low); resultStrs[i] = day + " - " + description + " - " + highAndLow; } for (String s : resultStrs) { Log.v(LOG_TAG, "Forecast entry: " + s); } return resultStrs; } }
doInBackground의 맨 밑으로 이동합시다
1 2 3 4 5 6 7 try { return getWeatherDataFromJson(forecastJsonStr, numDays); } catch (JSONException e) { Log.e(LOG_TAG, e.getMessage(), e); e.printStackTrace(); } return null ;
실행하고 Logcat을 확인하면 Log에 날씨 정보들이 다 표시되는걸 볼 수 있습니다 이제 이 데이터를 UI Thread(Main Thread)에 전달하는지 AsyncTask를 봅시다 AsyncTask 에서 모든 작업이 완료된 후에 우리는 UI Thread에 전달하면 됩니다 바로 onPostExecute을 이용하면 됩니다 일단 mForecastAdapter를 전역 변수화 시킵시다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ForecastFragment extends Fragment { private ArrayAdapter<String> mForecastAdapter; @Override public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mForecastAdapter = new ArrayAdapter<String>( getActivity(), R.layout.list_item_forecast, R.id.list_item_forecast_textview, weekForecast); return rootView; } }
1 2 3 4 5 6 7 8 9 @Override protected void onPostExecute (String[] strings) { if (strings != null ) { mForecastAdapter.clear(); for (String daysForecastStr : strings) { mForecastAdapter.add(daysForecastStr); } } }
이러면 끝입니다
빌드를 하고 실행을 하면 우리가 설정한 도시의 날씨 예보를 볼 수 있습니다
제대로 되었다면 Log.v로 선언한 verbose Log를 다 지웁시다
마지막으로 하나만 더 하면 됩니다
ForecastFragment로 바꿔 주세요
자동으로 데이터가 수정되는데 어떻게 동작하는 걸까요?
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/ArrayAdapter.java#192
정답은 여기 있습니다
Add를 할 때마다 자동으로 호출이 됩니다 우리가 앞으로 진행될 때마다 궁금한 사항이 있으면 직접 확인해 보세요!! Android는 Open Source입니다!
복습은 필수 !!
HttpURLConnection
Logcat
MainThread vs. Background Thread
AsyncTask
Adding Menu Buttons
values/strings.xml
Permissions
JSON Parsing