В процессе разработки мы познакомимся с такими вещами как:
- Activity
- Service
- Broadcast Receiver
- SQLite
- Asynk Tasks
- XML parsing
- ActionBar Sherlock
И так, в качестве задания примем такое: «Необходимо сделать приложение отображающее погоду в Москве, данные нужно брать с сервера Yahoo».
Для того чтоб охватить больше разнообразных компонентов Android SDK и сторонних библиотек, предлагаю пойти длинным и с токи зрения проектирования не самым верным путем.
В начале, нам необходимо продумать общую структуру приложения:
- Прежде всего, нам необходимо Activity, в котором мы будем отображать информацию.
- Для получения данных и их обработки нам необходим Service. Кроме того, что работать с сервисами очень удобно, это позволит инкапсулировать все запросы из сети в одном месте.
- Последним кирпичиком основных компонентов нам необходим Broadcast Receiver он будет обеспечивать получения уведомления о завершении работы сервиса.
- Последним крупным элементом является база данных, в которой будет храниться полученная информация.
Определив основные аспекты нашего приложения, приступим к разработке нашей Activity. Для этого создайте в папке /src своего проекта файл MainActivity.java.
Для использования Action Bar в более ранних версиях чем API 11 нам необходимо использовать библиотеку Action Bar Sherlock, в связи с этим в отличии от обычной Activity мы должны наследоваться от SherlockActivity таким вот образом:
public class MainActivity extends SherlockActivity { }
vЕсли вы еще не подключили библиотечный проект ActionBarSherlock – то самое время это сделать, найти его можно по адресу http://actionbarsherlock.com/. Библиотека поставляется с большим количеством примеров, потому дальнейшее ее исследование – выходящее за рамки этой статьи не должно вызвать у вас никаких трудностей.
Чаще всего, первым делом при создании Activity нам необходимо переопределить в ней метод
onCreate(Bundle savedInstanceState). За всю свою практику разработки под Android, я всего 1 раз сталкивался с ситуацией, когда это не нужно.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(R.style.Theme_Sherlock); setContentView(R.layout.main); }
Код очень простой, состоит из 3х строек: вызова конструктора суперкласса, установки темы для Activity – необходимо для работы Action Bar и последняя строка отвечает за установку layout для нашей Activity.
Теперь давайте настроим наш Action Bar и переопределим еще 2 метода. Для начала необходимо добавить задействованные переменные в нашу Activity:
public final static String NEW_WEATHER_EVENT = "com.andrewkravets.weather.NEW_WEATHER_ADDED"; private final static String REFRESH_MENU_ITEM = "Refresh"; private UpdateService mService; private boolean mBound; И непосредственно сами методы: @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(REFRESH_MENU_ITEM).setIcon(android.R.drawable.ic_dialog_info) .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); return true; } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { if (item.getTitle().equals(REFRESH_MENU_ITEM)) { if (mBound) { mService.loadWeatherData(); } return true; } return false; }
На этом этапе нам стоит оставить работу с Activity и перейти к созданию Service. Пусть вас не смущает то, что я вызываю на сервисе метод, или ввел кучу непонятных переменных, мы как раз сейчас к этому перейдем.
В нашем сервисе мы будем использовать несколько констант, поэтому предлагаю вынести их все в отдельный интерфейс.
public interface Consts { public static final String API_URL = "http://weather.yahooapis.com/forecastrss?w=2122265&u=c"; public static final String ROOT = "yweather:condition"; public static final String CONDITION_TEXT = "text"; public static final String CONDITION_TEMP = "temp"; public static final String CONDITION_DATE = "date"; }
Теперь перейдем конкретно к сервису, создадим класс UpdateService который будет унаследован от Service.
public class UpdateService extends Service implements Consts { }
Далее делаем все стандартно по примеру из статьи о сервисах и добавляем следующий код:
private final IBinder mBinder = new LocalBinder(); private boolean mIsLoading; @Override public IBinder onBind(Intent intent) { return mBinder; } public class LocalBinder extends Binder { public UpdateService getService() { return UpdateService.this; } }
Переменная mIsLoading нам необходима, для ограничения количества одновременно скачивающих потоков.
Следующим шагом нам необходимо написать AsynkTask для того, чтоб получать информацию, парсить ее и заносить в базу.
Для этого нам необходим класс модель, создадим простой POJO класс, WeatherObject, который содержит три текстовых поля: состояние, температуру и дату. При помощи IDE сгенерируем для него конструктор и get- и set- методы.
private class ManageWeatherData extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); mIsLoading = true; } @Override protected Void doInBackground(Void... params) { getWeather(); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); Intent intent = new Intent(MainActivity.NEW_WEATHER_EVENT); sendBroadcast(intent); mIsLoading = false; } } private void getWeather() { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document dom = db.parse(API_URL); parseDocument(dom); } catch (ParserConfigurationException pce) { pce.printStackTrace(); } catch (SAXException se) { se.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } } private void parseDocument(Document dom) { Element docEle = dom.getDocumentElement(); NodeList nl = docEle.getElementsByTagName(ROOT); if (nl != null && nl.getLength() > 0) { for (int i = 0; i < nl.getLength(); i++) { Element el = (Element) nl.item(i); WeatherObject weatherObject = parseWeather(el); writeToDB(weatherObject); } } } private WeatherObject parseWeather(Element el) { String condition = el.getAttribute(CONDITION_TEXT); String temp = el.getAttribute(CONDITION_TEMP); String date = el.getAttribute(CONDITION_DATE); return new WeatherObject(condition, temp, date); } private void writeToDB(WeatherObject weatherObject) { DatabaseHandler.getInstance(getApplicationContext()).addWeather(weatherObject); } Для завершения кода нашего Service осталось только добавить недостающий метод, при помощи которого ему отдается команда начать загрузку. public void loadWeatherData() { if (!mIsLoading) { new ManageWeatherData().execute(); } }
На этом наш Service полностью завершен и нам необходимо написать класс для работы с базой данных.
Ниже я приведу полный текст класса, с комментариями к методам, которые могут вызвать затруднения.
public class DatabaseHandler extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 1; private static final String DATABASE_NAME = "weather"; private static final String TABLE_CURRENT_WEATHER = "currentWeather"; private static final String KEY_ID = "id"; private static final String KEY_CONDITION = "condition"; private static final String KEY_TEMP = "temperature"; private static final String KEY_DATE = "date"; private static final String KEY_SIZE = "count(*)"; private static final String SIZE_QUERY = "SELECT "+ KEY_SIZE +" FROM " + TABLE_CURRENT_WEATHER; private static final String SELECT_QUERY = "SELECT * FROM " + TABLE_CURRENT_WEATHER; private static final String DROP_QUERY = "DROP TABLE IF EXISTS " + TABLE_CURRENT_WEATHER; private static final String CREATE_WEATHER_TABLE_QUERY = "CREATE TABLE " + TABLE_CURRENT_WEATHER + "(" + KEY_ID + " INTEGER PRIMARY KEY," + KEY_CONDITION + " TEXT," + KEY_TEMP + " TEXT," + KEY_DATE + " TEXT" + ")"; private static DatabaseHandler mInstance = null; //Я предпочитаю делать этот класс в виде Singleton хотя это не обязательно private DatabaseHandler(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public static DatabaseHandler getInstance(Context context) { if (mInstance == null) { synchronized (DatabaseHandler.class) { if (mInstance == null) { mInstance = new DatabaseHandler(context); } } } return mInstance; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_WEATHER_TABLE_QUERY); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP_QUERY); onCreate(db); } //Метод добавляющий объект в базу public void addWeather(WeatherObject object) { SQLiteDatabase db = mInstance.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(KEY_CONDITION, object.getCondition()); values.put(KEY_TEMP, object.getTemp()); values.put(KEY_DATE, object.getDate()); //Проверяем наличие записей в базе, если их нет - создаем первую, в противном случае перезаписываем ее. if (getDBSize() == 0) { db.insert(TABLE_CURRENT_WEATHER, null, values); } else { db.update(TABLE_CURRENT_WEATHER, values, KEY_ID + "=1", null); } db.close(); } //Очень простой метод для получения количества записей в таблице private int getDBSize() { SQLiteDatabase db = mInstance.getReadableDatabase(); Cursor cursor = db.rawQuery(SIZE_QUERY, null); if (cursor.moveToFirst()) { return Integer.parseInt(cursor.getString(cursor.getColumnIndex(KEY_SIZE))); } cursor.close(); return 0; } //Метод возвращающий объект из базы public WeatherObject getWeather() { WeatherObject weatherObject = null; SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery(SELECT_QUERY, null); if (cursor.moveToFirst()) { int condition = cursor.getColumnIndex(KEY_CONDITION); int temp = cursor.getColumnIndex(KEY_TEMP); int date = cursor.getColumnIndex(KEY_DATE); weatherObject = new WeatherObject(cursor.getString(condition), cursor.getString(temp), cursor.getString(date)); } cursor.close(); return weatherObject; } }
Как мы видим, в работе с базой данных в Android, нет ничего сложного, только необходимо не забывать, о том, что в младших версиях обязательно нужно закрывать класс Cursor по окончании работы с ним.
Итак, мы вышли на финишную прямую, осталось лишь описать внешний вид нашего приложения, добавить механизм отображения информации и немного поправить AndroidManifest.xml
Начнем с наброска мнималистичного интерфейса нашего приложения:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/main_date_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/date"/> <TextView android:id="@+id/main_date_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/main_date_label" android:layout_alignLeft="@+id/main_temperature_value"/> <TextView android:id="@+id/main_temperature_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/main_date_label" android:text="@string/temperature"/> <TextView android:id="@+id/main_temperature_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_below="@+id/main_date_label" android:layout_toRightOf="@+id/main_temperature_label"/> <TextView android:id="@+id/main_condition_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/main_temperature_label" android:text="@string/condition"/> <TextView android:id="@+id/main_condition_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/main_temperature_label" android:layout_toRightOf="@+id/main_condition_label" android:layout_alignLeft="@+id/main_temperature_value"/> </RelativeLayout>
В коде мы использовали несколько текстовых надписей при помощи ключа @string поэтому в файл strings.xml необходимо добавить несколько строчек:
<string name="temperature">Temperature:</string> <string name="date">Date:</string> <string name="condition">Condition:</string>
Вернемся к нашей Activity и напишем в ней Broadcast Receiver который будет получать сообщение об окончании загрузки и провоцировать обновление интерфейса.
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { new UpdateUI().execute(); } }; private class UpdateUI extends AsyncTask<Void, Void, WeatherObject> { @Override protected WeatherObject doInBackground(Void... params) { return DatabaseHandler.getInstance(MainActivity.this).getWeather(); } @Override protected void onPostExecute(WeatherObject object) { super.onPostExecute(object); if (object != null) { ((TextView) findViewById(R.id.main_date_value)).setText(object.getDate()); ((TextView) findViewById(R.id.main_temperature_value)).setText(object.getTemp()); ((TextView) findViewById(R.id.main_condition_value)).setText(object.getCondition()); } } }
В данном случае мы использовали еще один AsynkTask для получения данных из базы и внесения изменений в интерфейс.
Последним штрихом будет внести несколько корректив в наш Android Manifest, задекларируем работу сервиса:
<service android:enabled="true" android:name=".tools.UpdateService"/>
И добавим разрешение на работу с сетью интернет:
<uses-permission android:name="android.permission.INTERNET"/>
Вот и все наше приложение готово, кроме того, что вы потренировались, вы теперь всегда сможете заглянуть в него и найти кусочек кода, который может вам понадобиться в повседневной работе.
Версию которую я писал параллельно статье вы можете найти на Bitbucket
P.S.
Буду раз конструктивной критике, спасибо за внимание.
ссылка на оригинал статьи http://habrahabr.ru/post/166335/
Добавить комментарий