Проект — шпаргалка для начинающих Android разработчиков

от автора

В последнее время на Хабре стали все чаще появляться статьи связанные с разработкой для Android. Дабы не оставаться в стороне и внести свой небольшой вклад в помощь подрастающему поколению Android разработчиков, решил написать статью, в которой мы разработаем полноценное приложение-шпаргалку с использованием ряда наиболее востребованных компонентов Android SDK. Данное руководство рассчитано на разработчиков начального уровня имеющих общее представление касательно основных компонентов Android приложений таких как: Activity, Service, Intent, Broadcast Receiver.

В процессе разработки мы познакомимся с такими вещами как:

  • 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *