블로그 이미지
미국에서 모바일 애플리케이션을 개발하고 있습니다. 요즘 Corona로 앱을 하나 개발하고 있는데 나도 공부 하면서 여러 분들에게 소개도 하고 싶어서 블로그를 만들었습니다.
솔웅

최근에 받은 트랙백

글 보관함

calendar

  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      


요즘 회사일 하느라고 진도를 못 나갔습니다.
흐름이 끊기지 않기 위해 일단 2과 완성 소스를 다운 받아서 실행해 봤습니다.

소스코드는 여기서 받으시면 됩니다.

https://github.com/udacity/Sunshine-Version-2/tree/2.09_display_data

혹은 이 github 로 가셔서 아래처럼 브랜치를 선택하면 됩니다.



2.09 display data 가 2과의 완성된 소스코드 입니다.

이 소스코드를 받은 다음 OpenWeather 앱 아이디를 제 것으로 수정한 다음에 실행했습니다.
요거 하는데도 좀 헤맸는데요. 어쨌든 됐습니다.

이 소스코드를 실행하면 처음에는 지금까지 봐 왔던 하드 코딩된 날짜들이 표시됩니다.

그런데 잘 보면 우상단에 메뉴 버튼이 있는데요.
이 버튼을 누르고 Refresh 를 선택하면 그 날씨 웹 사이트에서 현재 날씨를 받아서 표시해 줍니다.

저는 제가 사는 곳의 우편번호를 넣어서 제가 사는 곳이 표시되도록 했습니다.





Click on Refresh


그러면 날씨 웹 싸이트로 부터 아래와 같은 정보를 받습니다.


{"cod":"200","message":0.0243,"city":{"id":0,"name":"Eden Prairie","country":"US","coord":{"lat":44.8342,"lon":-93.4389}},"cnt":7,"list":[{"dt":1471888800,"temp":{"day":19.77,"min":19.77,"max":19.77,"night":19.77,"eve":19.77,"morn":19.77},"pressure":993.75,"humidity":57,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"02n"}],"speed":6.47,"deg":205,"clouds":8},{"dt":1471975200,"temp":{"day":26.23,"min":18.85,"max":28.32,"night":24.13,"eve":27.26,"morn":18.85},"pressure":993.82,"humidity":65,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":6.66,"deg":192,"clouds":48},{"dt":1472061600,"temp":{"day":21.63,"min":17.19,"max":23.82,"night":17.19,"eve":22.65,"morn":20.18},"pressure":990.44,"humidity":97,"weather":[{"id":501,"main":"Rain","description":"moderate rain","icon":"10d"}],"speed":3.48,"deg":313,"clouds":92,"rain":3.3},{"dt":1472148000,"temp":{"day":19.95,"min":13.88,"max":20.64,"night":13.88,"eve":20.64,"morn":14.4},"pressure":995.57,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":7.56,"deg":291,"clouds":2},{"dt":1472234400,"temp":{"day":20.98,"min":10.21,"max":20.98,"night":15.51,"eve":18.56,"morn":10.21},"pressure":998.44,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":1.87,"deg":124,"clouds":86,"rain":1.08},{"dt":1472320800,"temp":{"day":18.07,"min":14.4,"max":18.07,"night":17.53,"eve":17.6,"morn":14.4},"pressure":993.44,"humidity":0,"weather":[{"id":502,"main":"Rain","description":"heavy intensity rain","icon":"10d"}],"speed":4.91,"deg":122,"clouds":98,"rain":13.41},{"dt":1472407200,"temp":{"day":20.11,"min":13.46,"max":20.86,"night":13.46,"eve":20.86,"morn":16.38},"pressure":992.58,"humidity":0,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"speed":3.43,"deg":343,"clouds":67,"rain":0.95}]}


이 정보를 필요한 것만 추려서 아래와 같이 표시합니다.




Weather in Eden Prairie



날씨가 좀 시원한 편이네요.
온도는 섭씨로 표시됩니다.

이렇게 표시되도록 하려면 지난번 소스에다 메뉴를 추가 하고 refresh button 을 만들어서 이 버튼을 선택했을 경우 이를 처리할 수 있도록 해야 합니다.

버튼 선택시에 지난번에 만들었던 AsyncTask 가 실행되도록 해야겠죠.
지난번에 살펴 봤듯이 이곳에서는 날씨 웹사이트에 정보를 요청한 후 이를 받아서 처리해야 합니다.

그런데 여기서 정보를 받는것은 JSON 포맷으로 받을 것이기 떄문에 이를 파싱하는 로직도 넣어야 합니다.

그리고 날씨 웹사이트에 정보를 요청하려면 Android Permission 들 중에 Internet Permission 을 Manifesto xml 파일에 세팅해야 합니다.

이 퍼미션 세팅에 대해서도 배울 겁니다.

대개 이런 기능들을 추가하면 위와 같이 작동되도록 할 수 있겠네요.

시간이 허락하는 대로 다시 이 강좌 공부를 계속 이어 나가야겠습니다.

저작자 표시 비영리 동일 조건 변경 허락
신고

Udacity 강좌 - Lesson 2 실습 03

2016.08.16 18:38 | Posted by 솔웅


오늘은 어제에 이어 Refactoring 한 소스코드를 분석해 보겠습니다.


이 소스코드는 아래 GitHub 에 가시면 받을 수 있습니다.


https://github.com/udacity/Sunshine-Version-2/tree/2.02_refactor_forecast_fragment


Branch 를 2.02 로 한 후 소스코드를 받아 보세요.


먼저 MainActivity 를 살펴 보겠습니다.


public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new ForecastFragment())
                    .commit();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.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);
    }
}


ForecastFragment 클래스가 별도의 파일로 옮겨지는 바람에 소스가 아주 간단해 졌습니다.


먼저 onCreate() 메소드를 보면 이전에 다 설명 됐던 부분인데요.

activity_main 레이아웃을 View로 세팅하고 첫번째 실행일 경우 ForecastFragment 클래스를 container라는 id를 가진 곳에 할당해 줍니다.


그러면 이제 container라는 id를 가진 곳에 ForecastFragment 클래스에서 구현한 내용이 표시 될 겁니다. 이 클래스는 조금 있다가 보구요.


보니까 onCreateOptionsMenu() 메소드에서는 menu 라는 파라미터를 받아서 이를 메뉴로 표시할 수 있도록 구현돼 있구요.


onOptionsItemSelected() 메소드에서는 메뉴 아이템이 선택 됐을 경우 어떻게 할지를 구현하는 곳입니다.



이제 ForecastFragment 클래스를 보겠습니다.


/**
 * Encapsulates fetching the forecast and displaying it as a {@link ListView} layout.
 */
public class ForecastFragment extends Fragment {

    private ArrayAdapter<String> mForecastAdapter;

    public ForecastFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // Create some dummy data for the ListView.  Here's a sample weekly forecast
        String[] data = {
                "Mon 6/23 - Sunny - 31/17",
                "Tue 6/24 - Foggy - 21/8",
                "Wed 6/25 - Cloudy - 22/17",
                "Thurs 6/26 - Rainy - 18/11",
                "Fri 6/27 - Foggy - 21/10",
                "Sat 6/28 - TRAPPED IN WEATHERSTATION - 23/18",
                "Sun 6/29 - Sunny - 20/7"
        };
        List<String> weekForecast = new ArrayList<String>(Arrays.asList(data));

        // Now that we have some dummy forecast data, create an ArrayAdapter.
        // The ArrayAdapter will take data from a source (like our dummy forecast) and
        // use it to populate the ListView it's attached to.
        mForecastAdapter =
                new ArrayAdapter<String>(
                        getActivity(), // The current context (this activity)
                        R.layout.list_item_forecast, // The name of the layout ID.
                        R.id.list_item_forecast_textview, // The ID of the textview to populate.
                        weekForecast);

        View rootView = inflater.inflate(R.layout.fragment_main, container, false);

        // Get a reference to the ListView, and attach this adapter to it.
        ListView listView = (ListView) rootView.findViewById(R.id.listview_forecast);
        listView.setAdapter(mForecastAdapter);

        return rootView;
    }

    public class FetchWeatherTask extends AsyncTask<Void, Void, Void> {

        private final String LOG_TAG = FetchWeatherTask.class.getSimpleName();

        @Override
        protected Void doInBackground(Void... params) {
            // These two need to be declared outside the try/catch
            // so that they can be closed in the finally block.
            HttpURLConnection urlConnection = null;
            BufferedReader reader = null;

            // Will contain the raw JSON response as a string.
            String forecastJsonStr = null;

            try {
                // Construct the URL for the OpenWeatherMap query
                // Possible parameters are avaiable at OWM's forecast API page, at
                // http://openweathermap.org/API#forecast
                String baseUrl = "http://api.openweathermap.org/data/2.5/forecast/daily?q=94043&mode=json&units=metric&cnt=7";
                String apiKey = "&APPID=" + BuildConfig.OPEN_WEATHER_MAP_API_KEY;
                URL url = new URL(baseUrl.concat(apiKey));

                // Create the request to OpenWeatherMap, and open the connection
                urlConnection = (HttpURLConnection) url.openConnection();
                urlConnection.setRequestMethod("GET");
                urlConnection.connect();

                // Read the input stream into a String
                InputStream inputStream = urlConnection.getInputStream();
                StringBuffer buffer = new StringBuffer();
                if (inputStream == null) {
                    // Nothing to do.
                    return null;
                }
                reader = new BufferedReader(new InputStreamReader(inputStream));

                String line;
                while ((line = reader.readLine()) != null) {
                    // Since it's JSON, adding a newline isn't necessary (it won't affect parsing)
                    // But it does make debugging a *lot* easier if you print out the completed
                    // buffer for debugging.
                    buffer.append(line + "\n");
                }

                if (buffer.length() == 0) {
                    // Stream was empty.  No point in parsing.
                    return null;
                }
                forecastJsonStr = buffer.toString();
            } catch (IOException e) {
                Log.e(LOG_TAG, "Error ", e);
                // If the code didn't successfully get the weather data, there's no point in attemping
                // to parse it.
                return 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;
        }
    }
}


좀 긴데요.


우선 MainActivity 에서 이 앱을 처음 실행할 때 ForecastFragment 클래스를 실행하도록 onCreate() 메소드에서 구현해 놨습니다.


이렇게 되면 이 클래스의 생성자가 불려와서 실행 될  텐데... 아직 생성자는 Empty 상태 입니다.


그러면 다음에 onCreateView() 메소드가 참조 될 겁니다.


이 코드도 지난번에 다 분석했던 겁니다.


일단 배열에 요일별 날씨를 하드코딩해 놓았구요.

이것을 리스트에 담은 다음 ArrayAdapter 에서 처리를 했습니다.

그리고 View를 정의해 놓은 다음 이 뷰를 리스트 뷰에 할당하고 이 리스트뷰에 ArrayAdapter를 세팅합니다.


그리고 이 rootView를 return 합니다.


이렇게 하면 이전에 실행했던 대로 변하지 않고 앱이 표시될 겁니다.


그 다음에 AsyncTask 를 구현한 클래스가 있는데요. 아직 이 클래스를 호출하는 곳은 없습니다.

즉 이 클래스가 아직은 실행이 되지는 않을 건데요.


다음 단원에서 이 분을 코딩할 것 같습니다.


일단 이 클래스 (FetchWeatherTask) 를 분석해 보겠습니다.


일단 AsyncTask 를 extends 했습니다. 이 부분을 구현하겠다는 얘기이지요.


안에를 보면 getSimpleName() 메소드를 사용해 이 클래스의 이름을 구해서 LOG_TAG string 변수에 할당합니다.


그리고 AsyncTask 의 메소드인 doInBackground() 메소드가 있습니다.


바로 여기에 네트워킹 작업을 구현해서 Main Thread 가 아닌 Background Thread에서 이 일을 처리하도록 할 겁니다.


HttpURLConnection 와 BufferedReader 를 try 구문 밖에서 선언했는데요. 이것은 나중에 finally 블럭에서 이 두개를 close 시킬 수 있게 하기 위해서 이렇게 했습니다.


그리고 JSON 정보를 받을 forecastJsonStr 이라는 이름의 스트링 변수를 선언했습니다.


이제 try 구문이 시작되는데요.


OpenWeatherMap 에 정보를 요청할 쿼리부터 만듭니다.


baseUrl 에 기본 정보를 담고 apiKey 에 AppID를 넣습니다.

이 AppID는 본인의 것을 넣어야 합니다.

여기서는 BuildConfig.OPEN_WEATHER_MAP_API_KEY 라고 해서 XML에서 불러오도록 했는데요.

저는 아직 이 부분을 구현해 놓지 않아서 이부분에 그냥 제 AppID를 하드코딩 해 넣었습니다.


그런다음 이 전체 정보 (URL+AppID) 를 URL에 담습니다.


그 다음엔 connection을 오픈하고 request method는 GET 형식으로 정하고 마지막에 connect() 합니다.


이제 OpenWeatherMap 에 요청 정보를 보냈고 이곳에서 보내오는 응답을 처리할 차례입니다.


정보를 InputStream에 담은 후 BufferReader에 이 inputStream을 담습니다.


그리고 난 다음에 이 BufferReader에 있는 내용을 while문을 돌려서 하나하나 읽으면서 line 이라는 String에 담습니다.


그리고 이 String을 아까 선언했든 forecastJsonStr 이라는 String에 담습니다.


여기까지 Try 구문이구요.


catch 구문에서는 예외사항이 발생했을 때 이 에러 내용을 로그에 표시하도록 합니다.

LOG_TAG에는 이 클래스 이름을 담았었는데요. 이 이름을 로그 앞에 표시하도록 했습니다.


그리고 finally 구문에서는 connection 들을 담습니다.



여기 까지 인데요. 이렇게 한 다음에 이 앱을 실행하면 지난번에 나왔던 하드코딩한 날씨정보가 그대로 나옵니다.




아직 FetchWeatherTask 를 실행하도록 하지 않았기 때문입니다.


여기서는 단지 NetworkOnMainThreadException 이 나오지 않도록 Refactoring 한 겁니다.


이제 다음 단원에서 실제로 이 AsyncTask 로 처리해서 Network 가 Background로 돌아가게 하고 또 실제 날씨 정보를 OpenWeatherMap 에서 받는 작업을 할 겁니다.



저작자 표시 비영리 동일 조건 변경 허락
신고

Udacity 강좌 - Lesson 2 실습 02

2016.08.15 18:51 | Posted by 솔웅


오늘은 지난번에 분석했던 네트워크 연결 관련 소스코드를 실제로 프로젝트에 집어 넣겠습니다.


우선 https://gist.github.com/anonymous/1c04bf2423579e9d2dcd 에 있는 소스코드를 MainActivity 의 PlaceholderFragment class에 있는 onCreateView Method 에 삽입합니다.


이 때 몇가지 import 해야 하는데요. 이건 Auto Import를 활성화 해 놓으면 이클립스가 알아서 처리합니다.


Mac instructions: To enable auto-import, go to Android Studio > Preferences > Editor > Auto Import. Check all the boxes and insert all imports on paste.

Windows instructions: To enable auto-import, go to File > Settings > Editor > Auto Import > Java. Check all the boxes and insert all imports on paste.


이렇게 한 다음에 실행 하면 NetworkOnMainThreadException 이 일어납니다.

Android Device Monitor 에서 프로세스 아이디 (여기서는 7762)로 LOGCAT을 검색해 보면 위 그림과 같이 NetworkOnMainThreadException 이 일어났다는 메세지가 나오고 어느 클래스의 몇번째 줄에 있는 코드에서 예외가 발생했다는 로그들을 찾아 볼 수 있습니다.



저 로그를 보면 에러는 115번째 줄에서 난 것으로 나옵니다.


바로  urlConnection.connect(); 부분 입니다.

그 이유는 이 연결은 Main Thread 에서는 구현하면 안되는 것이기 때문입니다.

그럼 이제 이 소스코드를 올바르게 Refactoring 해야 하는데요. 일단 그 전에 프로세스와 쓰레드에 대해 간단히 공부하고 넘어가겠습니다.

메인 쓰레드에서는 대개 사용자와 상호 작용하는 UI 관련 작업들을 담당하게 됩니다.

백그라운드 쓰레드에서는 long-running work 을 주로 담당하게 됩니다.

Background Thread 를 생성하려면 AsyncTask 를 사용하면 간단하게 처리할 수 있습니다.

이 클래스의 핵심 메소드는 doInBackground() 입니다.

이 글 쓰기 전에 프로세스와 쓰레드에 대해 두어개의 글을 올렸는데 그것도 도움이 될 겁니다.

이제 네트워크 관련 소스코드들을 AsyncTask 로 옮겨 보겠습니다.

옮기는 순서는 

1. PlaceholderFragment class 를 ForecastFragment class 로 이름을 바꾼다

2. ForecastFragment 라는 새로운 자바 파일을 만들어서 이 클래스를 옮긴다.

3. FetchWeatherTask 라는 이름의 AsyncTask 클래스를 만든다.

이렇게 하면 되는데요.

이 과정은 다음 글에서 코드를 분석한 후에 실습을 이어 나가겠습니다.


저작자 표시 비영리 동일 조건 변경 허락
신고
이전 1 2 3 4 5 ... 291 다음

티스토리 툴바