Поиск по этому блогу

среда, 30 марта 2011 г.

Pro Android 2 - Глава 2. Получение первого опыта (окончание)

Анализ приложения "Notepad"

Если Вы читаете всё по-порядку, то Вам уже известно как создать и запустить в эмуляторе Android-приложение.
Для того, чтобы поближе познакомиться с компонентами Android-приложений, здесь будет произведён анализ приложения "Notepad", которое поставляется вместе с Android SDK.
Приложение "Notepad" по сложности находится между приложением "Hello World!" и полномасштабным Android-приложением.

Установка и запуск приложения "Notepad"

В приложении "Notepad" пользователь может создать новую заметку, изменить существующую заметку, удалить заметку, просмотреть список созданных заметок и другое. Когда пользователь впервые запускает приложение, ему отображается пустой список заметок. При нажатии на кнопку "Menu", приложение предоставляет пользователю список действий, одно из которых позволяет создать новую заметку. После добавления новой заметки, её можно изменить или удалить, выбрав соответствующий пункт меню.
Для загрузки приложения "Notepad" в Eclipse IDE необходимо выполнить следующие действия:
  1. Запустить Eclipse IDE.
  2. Перейти File -> New -> Project.
  3. В диалоговом окне New Project выбрать Android -> Android Project.
  4. В диалоговом окне New Android Project отметить пункт Create project from existing sample и в выпадающем списке Samples выбрать NotePad. Следует отметить, что данное приложение находится в Android SDK в папке platforms\android-2.0\samples (версия android может отличаться).
  5. Нажать кнопку Finish.
После выполнения вышеприведённых действий, Вы должны увидеть проект приложения Notepad в Package Explorer. Если Eclipse выдал в консоли какие-то сообщения об ошибках, необходимо использовать опцию Clean (Project -> Clean) для своего проекта, после чего выбрать его в Package Explorer и нажать F5 (будет произведён Refresh проекта; также это можно сделать через контекстное меню проекта). Чтобы запустить приложение, можно создать новую конфигурацию (как это было сделано в приложении "Hello World!"), либо просто щёлкнуть правой кнопкой мышки на проекте, выбрать Run As -> Android Application. Запустится эмулятор, и на него будет установлено приложение "Notepad".


Состав приложения

Данное приложение содержит несколько java-файлов, png-изображения, 3 макета (в папке layout) активити, один xml-файл со строковыми константами и файл AndroidManifest.xml. Android определяет главное активити (активити верхнего уровня; оно создаётся при создании приложения) как точку входа в приложение.


В файле AndroidManifest.xml можно увидеть одного контент-провайдера (provider) и несколько активити. У активити есть фильтры намерений (intent-filter). Для главного активити обязательно определён интент-фильтр с android.intent.action.MAIN и  android.intent.category.LAUNCHER.
Когда приложение запускается, происходит считывание содержимого файла AndroidManifest.xml, и запускаются активити, которые содержат интент-фильтр с android.intent.action.MAIN и  android.intent.category.LAUNCHER.

<intent-filter>
     <action android:name="android.intent.action.MAIN" />
     <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

После того, как система нашла в AndroidManifest.xml стартовое активити, она определяет ассоциируемый с нею класс. Он состоит из названия корневого пакета и названия активити (в нашем случае, com.example.android.notepad.NotesList).

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.android.notepad"
>
     <application android:icon="@drawable/app_notes"
          android:label="@string/app_name"
     >
          <provider android:name="NotePadProvider" 
               android:authorities="com.google.provider.NotePad" 
          /> 
          <activity android:name="NotesList" android:label="@string/title_notes_list"> 
               <intent-filter> 
                    <action android:name="android.intent.action.MAIN" /> 
                    <category android:name="android.intent.category.LAUNCHER" /> 
               </intent-filter> 
               <intent-filter> 
                    <action android:name="android.intent.action.VIEW" /> 
                    <action android:name="android.intent.action.EDIT" /> 
                    <action android:name="android.intent.action.PICK" /> 
                    <category android:name="android.intent.category.DEFAULT" /> 
                    <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> 
               </intent-filter> 
               <intent-filter> 
                    <action android:name="android.intent.action.GET_CONTENT" /> 
                    <category android:name="android.intent.category.DEFAULT" /> 
                    <data android:mimeType="vnd.android.cursor.item/vnd.google.note" /> 
               </intent-filter> 
          </activity>
… 
</manfiest>

Название корневого пакета объявлено в элементе <manifest> файла AndroidManifest.xml как атрибут "name". Также каждое активити имеет атрибут "name".
После определения точки входа в приложение, система запускает стартовое активити и вызывает его метод onCreate(). Ниже представлен метод onCreate() данного приложения.

public class NotesList extends ListActivity {
     @Override 
     protected void onCreate(Bundle savedInstanceState) { 
          super.onCreate(savedInstanceState); 
           
          setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); 
          Intent intent = getIntent(); 
          if (intent.getData() == null) { 
               intent.setData(Notes.CONTENT_URI); 
          } 

          getListView().setOnCreateContextMenuListener(this); 
           
          Cursor cursor = managedQuery(getIntent().getData(),  
                                   PROJECTION, null, null, Notes.DEFAULT_SORT_ORDER); 

          SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,  
                                                            R.layout.noteslist_item, cursor, new String[] { Notes.TITLE },  
                                                            new int[] { android.R.id.text1 }); 
                                                            setListAdapter(adapter); 
     } 
}

Любая деятельность в Android, как правило, начинается с намерений, и одно активити может запустить другое. Метод onCreate() проверяет наличие данных в намерениях для текущего активити. Если данных нету, то устанавливается URI для их получения от намерений. Android предоставляет доступ к данным через контент-провайдеров, используя URI, который имеет достаточно информации для возвращения их из БД.
В Notepad.java объявлена статическая "финальная" константа Notes.CONTENT_URI:

public static final Uri CONTENT_URI =  Uri.parse("content://" + AUTHORITY + "/notes");

Примечание: класс Notes является внутренним классом класса Notepad.

С её помощью можно получить все записи из контент-провайдера. Чтобы получить запись с ID равным 11, необходимо инициализировать URI следующим образом:

public static final Uri CONTENT_URI =  Uri.parse("content://" + AUTHORITY + "/notes/11"); 

Класс NotesList расширяет класс ListActivity, который знает, как отобразить список данных. Элементы списка находятся во внутреннем ListView (компонент UI), который отображает записи в виде списка.
После установки URI, активити создаёт контекстное меню для заметок. Оно содержит различные варианты действий:
  1. Изменить заметку.
  2. Изменить название заметки.
  3. Добавить заметку.
Далее, активити выполняет запрос и получает "управляемый" указатель (cursor) на результат. "Управляемый" означает, что Android будет сам управлять этим курсором, и разработчику не стоит беспокоиться о нём при загрузке/перезагрузке приложения, его собственной загрузке/перезагрузке и о позиционировании курсора.
Параметры managedQuery() отображены ниже:
  1. Параметр - URI. Тип данных - Uri. Описание - URI контент-провайдера.
  2. Параметр - projection. Тип данных - String[]. Описание - возвращаемые столбцы (названия столбцов).
  3. Параметр - selection. Тип данных - String. Описание - дополнительное условие выборки.
  4. Параметр - selectionArgs. Тип данных - String[]. Описание - аргументы для выборки, если запрос содержит вопросительные знаки (?).
  5. Параметр - sortOrder. Тип данных - String. Описание - порядок сортировки, используемый для результирующей выборки.
Android возвращает данные в виде таблицы. Параметр "projection" позволяет определить столбцы, которые будут выбраны. Также присутствует возможность отсортировать результат, используя SQL-параметры в условии sortOrder (такие как asc или desc). Следует отметить, что запрос Android возвращает столбец с именем "_id", который необходим для получения отдельной записи. Кроме этого, разработчик должен знать тип данных каждого возвращаемого столбца.
После выполнения запроса, курсор передаётся в конструктор специального адаптера SimpleCursorAdapter. Он адаптирует записи в наборе данных для элементов пользовательского интерфейса (ListView).

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.noteslist_item,
                                                  cursor, new String[] { Notes.TITLE }, new int[] { android.R.id.text1 });

Android автоматически генерирует служебный класс R.java. Он предоставляет ссылки на ресурсы Вашего проекта из папки res. Например, Вы можете поместить все ваши строковые константы в папку values в файл strings.xml. AAPT автоматически сгенерирует идентификаторы для каждой строки и поместит их в файл R.java. Если какой-либо ресурс будет удалён, то система автоматически удалит его идентификатор.
Также следует рассмотреть метод onListItemClick() класса NotesList.

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
     Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);

     String action = getIntent().getAction();
     if (Intent.ACTION_PICK.equals(action) ||
          Intent.ACTION_GET_CONTENT.equals(action)) {
          setResult(RESULT_OK, new Intent().setData(uri));
     } else {
          startActivity(new Intent(Intent.ACTION_EDIT, uri));
     }
}

Метод onListItemClick() вызывается при выборе какой-либо заметки из списка. Он формирует новое URI из её ID и базового URI (в нашем случае - content://com.google.provider.NotePad/notes). Оно используется в методе startActivity(), который запускает новое активити. Существует и другой способ запуска нового активити - метод startActivityForResult(). В отличие от предыдущего метода, он регистрирует обратный вызов, который будет выполнен при завершении запущенного активити. Обратный вызов может вернуть какой-либо результат (RESULT_OK, RESULT_CANCELED, RESULT_FIRST_USER).
При создании заметок в приложении, они сохраняются на устройстве в SQLite базе данных. Метод managedQuery() получает данные из неё с помощью контент-провайдера, используя URI.

public static final Uri CONTENT_URI =  Uri.parse("content://" + AUTHORITY + "/notes");

URI всегда состоит из "content://", адреса целевого провайдера и дополнительного сегмента, который меняется в зависимости от того, какие данные необходимо получить. Адрес целевого провайдера (AUTHORITY) настраивается в файле AndroidManifest.xml в виде контент-провайдера:

<provider android:name="NotePadProvider"
                android:authorities="com.google.provider.NotePad"/>

Он описывается классом NotepadProvider.

public class NotePadProvider extends ContentProvider
{
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
                                    String[] selectionArgs,String sortOrder) {}
     @Override
     public Uri insert(Uri uri, ContentValues initialValues) {}

     @Override
     public int update(Uri uri, ContentValues values, String where,
                               String[] whereArgs) {}

     @Override
     public int delete(Uri uri, String where, String[] whereArgs) {}

     @Override
     public String getType(Uri uri) {}

     @Override
     public boolean onCreate() {}

     private static class DatabaseHelper extends SQLiteOpenHelper {

          @Override
          public void onCreate(SQLiteDatabase db) {}

          @Override
          public void onUpgrade(SQLiteDatabase db,
                                              int oldVersion, int newVersion) {
               //...
          }
     }
}

Класс NotePadProvider расширяет класс ContentProvider, который имеет 6 абстрактных методов. Четыре из них CRUID (Create - создать, Read - считать, Update - обновить, Delete - удалить), два других - onCreate() и getType(). Метод onCreate() вызывается, когда контент-провайдер создаётся в первый раз. С помощью метода getType() можно узнать MIME-тип для результирующего набора данных.
Класс NotePadProvider содержит внутренний класс DatabaseHelper, который расширяет класс SQLiteOpenHelper. Этот класс используется для инициализации БД приложения, её открытия и закрытия, а также других задач, связанных с БД.

private static class DatabaseHelper extends SQLiteOpenHelper {

     DatabaseHelper(Context context) {
          super(context, DATABASE_NAME, null, DATABASE_VERSION);
     }

     @Override
     public void onCreate(SQLiteDatabase db) {
          db.execSQL("CREATE TABLE " + NOTES_TABLE_NAME + " ("
                              + Notes._ID + " INTEGER PRIMARY KEY,"
                              + Notes.TITLE + " TEXT,"
                              + Notes.NOTE + " TEXT,"
                              + Notes.CREATED_DATE + " INTEGER,"
                              + Notes.MODIFIED_DATE + " INTEGER"
                              + ");");
     }
     //…
}

Конструктор класса DatabaseHelper вызывает конструктор базового класса, который создаёт базу данных заданной версии с заданным именем. Базовый класс будет вызывать метод onCreate() лишь в том случае, если в базе данных отсутствуют таблицы (обратите внимание на наличие колонки _ID, о которой упоминалось ранее).

Ниже приведён фрагмент кода одного из методов CRUD - метода insert():

//…
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(NOTES_TABLE_NAME, Notes.NOTE, values);
if (rowId > 0) {
     Uri noteUri = ContentUris.withAppendedId(
                                             NotePad.Notes.CONTENT_URI, rowId);
     getContext().getContentResolver().notifyChange(noteUri, null);
     return noteUri;
}

Вышеприведённый код получает экземпляр объекта БД и использует его внутренний метод insert() для вставки записи в неё. При вставке возвращается идентификатор вставленной записи.

Комментариев нет:

Отправить комментарий