오늘은 어제에 이어 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 에서 받는 작업을 할 겁니다.
'WEB_APP > Android' 카테고리의 다른 글
Udacity 강좌 - Lesson 3 실습 01 - 다른 Activity로 화면 전환하기 - (0) | 2016.09.21 |
---|---|
Android Toast 정리 (0) | 2016.09.07 |
Udacity 강좌 - Lesson 2 소스 실행 순서 따라가기 (0) | 2016.09.06 |
Udacity 강좌 - Lesson 2 실습 05 (2) | 2016.08.29 |
Udacity 강좌 - Lesson 2 실습 04 - 2과 완성 코드 실행 - (0) | 2016.08.23 |
Udacity 강좌 - Lesson 2 실습 02 (0) | 2016.08.16 |
Android Network Connection (0) | 2016.08.11 |
Udacity 강좌 - Lesson 2 실습 01 (0) | 2016.08.10 |
Udacity 강좌 - Developing Android Apps Lesson 2 Summary (0) | 2016.08.09 |
Udacity 강좌 - Lesson 1 실습 (0) | 2016.08.08 |