Платформа Android предлагает широкий спектр вариантов хранения для использования в ваших приложениях. В этой серии руководств мы собираемся исследовать некоторые средства хранения данных, предоставляемые Android SDK, создав простой проект: художественный редактор ASCII.
Эта серия учебных пособий по созданию простого редактора ASCII Art представлена в четырех частях:
- Создание пользовательского интерфейса
- Экспорт изображения и настройка пользователя
- Создание базы данных и запросы
- Сохранение и удаление изображений ASCII
Шаг 1: Создайте класс помощника базы данных
Для управления базой данных SQLite в приложениях Android мы расширяем класс SQLiteOpenHelper. Этот класс будет обрабатывать создание базы данных, поэтому мы определим структуру данных в нем. Создайте новый класс в своем проекте Android и назовите его «ImageDataHelper» или любое другое имя по вашему выбору. Расширьте строку открытия объявления класса следующим образом:
1
|
public class ImageDataHelper extends SQLiteOpenHelper {
|
Добавьте следующий импорт вверху класса:
1
2
3
4
|
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
|
Шаг 2: Определите свойства базы данных
Внутри вашего вспомогательного класса базы данных создайте следующие переменные, чтобы определить свойства базы данных. Во-первых, версия базы данных:
1
|
private static final int DATABASE_VERSION = 1;
|
Затем дайте базе данных имя:
1
|
private static final String DATABASE_NAME = «asciipics.db»;
|
Чтобы создать надежную модель базы данных, нам нужен столбец ID, поэтому добавьте один, используя константу BaseColumns :
1
|
public static final String ID_COL = BaseColumns._ID;
|
Это автоматически дает нам столбец первичного ключа, который будет автоматически увеличиваться. База данных будет содержать одну таблицу, поэтому назовите ее следующим:
1
|
public static final String TABLE_NAME = «pics»;
|
Таблица будет содержать два столбца: один для содержимого обложки ASCII, которая будет текстовой строкой, и один для имени, которое появится в списке, когда пользователь попытается загрузить сохраненные обложки. Определите эти столбцы сейчас:
1
2
|
public static final String ASCII_COL = «ascii_text»;
public static final String CREATED_COL = «pic_creation»;
|
Теперь мы можем определить строку создания базы данных:
1
2
|
private static final String DATABASE_CREATE = «CREATE TABLE » + TABLE_NAME + » (» + ID_COL + » INTEGER » +
«PRIMARY KEY AUTOINCREMENT, » + ASCII_COL + » TEXT, » + CREATED_COL + » TEXT);»;
|
Как видите, в этом случае синтаксис включает стандартный SQL.
Шаг 3: Реализация создания базы данных
Мы будем использовать шаблон проектирования Singleton для вспомогательного класса базы данных, который вы, возможно, не встретили в зависимости от вашего опыта работы с Java. Чтобы мы могли использовать помощник по базе данных в нескольких действиях, сохраняя эффективность, мы хотим ограничить приложение, чтобы оно могло создавать только один экземпляр класса. Добавьте еще пару переменных экземпляра:
1
2
|
private static ImageDataHelper dbInstance;
private Context dbContext;
|
Вместо непосредственного использования метода конструктора наша деятельность будет вызывать фабричный метод, который мы определили для возврата экземпляра класса. Здесь мы будем использовать первую переменную для хранения экземпляра, чтобы он создавался только один раз. Переменная контекста поможет нам избежать утечек памяти, так как мы собираемся использовать контекст приложения, а не контекст для действия, создающего вспомогательный объект базы данных.
После констант добавьте метод конструктора к вспомогательному классу базы данных:
1
2
3
|
private ImageDatabase(Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
|
Обратите внимание, что конструктор является закрытым, поэтому внешний код не сможет вызывать его напрямую. Теперь добавьте метод фабрики, чтобы ваши Activity могли создавать и получать доступ к одному экземпляру этого класса:
1
2
3
4
5
|
public static ImageDataHelper getInstance(Context context) {
if (dbInstance == null)
dbInstance = new ImageDataHelper(context.getApplicationContext());
return dbInstance;
}
|
Это статический метод, поэтому мы сможем получить к нему доступ, ссылаясь на сам класс, а не через его экземпляр. Мы проверяем, был ли уже создан вспомогательный экземпляр базы данных, только вызывая конструктор, если это не так. Мы используем контекст приложения для эффективного использования памяти и возвращаем экземпляр класса. Скоро вы увидите, как мы создаем этот класс из Activity.
Мы выполним создание таблицы базы данных в методе onCreate , поэтому добавим его:
1
2
3
|
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
|
Здесь мы передаем строку создания таблицы базы данных. Нам также необходимо предоставить метод для выполнения при обновлении базы данных:
1
2
3
4
5
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(«DROP TABLE IF EXISTS pics»);
db.execSQL(«VACUUM»);
onCreate(db);
}
|
Мы уничтожаем существующую таблицу и создаем ее снова. Если вы хотите изменить структуру вашей базы данных в любой момент, измените строку создания базы данных и увеличьте номер версии — метод onUpgrade будет выполнен.
На этом класс нашей базы данных завершен. Теперь мы можем создать экземпляр помощника по базе данных из основного Activity, чтобы использовать его при запуске приложения. В вашем основном классе Activity добавьте новую переменную экземпляра вверху:
1
|
private ImageDataHelper imgData;
|
Внутри метода onCreate создайте экземпляр нового класса:
1
|
imgData = ImageDataHelper.getInstance(this);
|
Обратите внимание, что мы используем имя класса и метод фабрики для возврата экземпляра вспомогательного класса базы данных, поэтому мы знаем, что приложение в целом имеет не более одного экземпляра класса.
Шаг 4: Обработка кликов на кнопке загрузки
Помните, что мы включили кнопку «Загрузить», чтобы пользователи могли загружать ранее сохраненные изображения В вашем основном методе Activity onCreate слушайте клики по кнопке:
1
2
|
Button loadBtn = (Button)findViewById(R.id.load_btn);
loadBtn.setOnClickListener(this);
|
Теперь добавьте новый раздел в условный оператор в вашем методе onClick :
1
2
3
|
else if(v.getId()==R.id.load_btn) {
}
|
Мы добавим обработку к этому условному блоку позже.
Шаг 5: Создать класс загрузки
Когда пользователь нажмет кнопку «Загрузить», мы запустим всплывающее действие в стиле, которое появится в верхней части основного действия. В этом новом действии будет представлен список сохраненных произведений искусства в базе данных, что позволит пользователю выбрать один из них для загрузки. Создайте новый класс в своем проекте и назовите его «PicChooser» или альтернативное имя, если вы предпочитаете. Поскольку содержимое этого действия будет представлять собой список произведений искусства, мы будем использовать ListActivity , поэтому расширьте строку вступительной декларации:
1
|
public class PicChooser extends ListActivity {
|
Добавьте следующий класс в класс:
1
2
3
4
5
6
7
8
|
import android.app.ListActivity;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
|
Мы будем использовать базу данных для вывода списка сохраненных изображений, поэтому добавим переменные экземпляра для базы данных, помощника и курсор для запроса данных:
1
2
3
|
private ImageDataHelper picDataHelp;
private SQLiteDatabase savedPictures;
private Cursor picCursor;
|
Добавьте метод onCreate :
1
2
3
4
5
|
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.load);
}
|
Шаг 6: Разработка макета загрузки активности
Давайте добавим макет, который мы только что упомянули — добавьте новый файл в папку «res / layout» и назовите его «load.xml», чтобы он соответствовал приведенному выше коду. Добавьте линейный макет в новый файл:
1
2
3
4
5
6
7
|
<LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android»
android:layout_width=»fill_parent»
android:layout_height=»wrap_content»
android:orientation=»vertical»
android:padding=»10dp» >
</LinearLayout>
|
Внутри макета добавьте информативное текстовое представление и представление списка, чтобы загрузить имена сохраненных изображений в:
01
02
03
04
05
06
07
08
09
10
|
<TextView
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:text=»@string/load_pics»
android:textStyle=»italic» />
<ListView
android:id=»@android:id/list»
android:layout_width=»fill_parent»
android:layout_height=»fill_parent» />
|
Идентификатор представления списка позволит нам загружать данные в него на Java. Добавьте указанную здесь отображаемую строку в ваш XML-файл «res / values / strings»:
1
|
<string name=»load_pics»>Choose from these saved pictures:</string>
|
Шаг 7: Дизайн элементов списка
Нам нужно определить макет для каждого элемента, который будет отображаться в представлении списка. Добавьте новый файл макета в свое приложение, назвав его «pic_item.xml» и включив линейный макет:
01
02
03
04
05
06
07
08
09
10
|
<LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android»
android:layout_width=»fill_parent»
android:layout_height=»wrap_content»
android:background=»#333333″
android:clickable=»true»
android:onClick=»picChosen»
android:orientation=»horizontal»
android:padding=»5dp» >
</LinearLayout>
|
Обратите внимание, что мы включили атрибут onClick , указывающий имя метода, который мы хотим выполнить, когда пользователи щелкают по нужному элементу списка. Внутри линейного макета добавьте текстовые представления для идентификатора и строку создания для представленной картинки:
01
02
03
04
05
06
07
08
09
10
11
12
|
<TextView
android:id=»@+id/picID»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:paddingRight=»5dp»
android:textStyle=»italic» />
<TextView
android:id=»@+id/picName»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:textStyle=»bold» />
|
Идентификаторы позволят нам отобразить данные из базы данных в эти представления.
Шаг 8: запрос сохраненных изображений
Вернитесь в новый метод выбора изображений Activity onCreate , после строки, в которой вы устанавливаете новый макет, создайте экземпляры базы данных и помощника:
1
2
|
picDataHelp=ImageDataHelper.getInstance(this);
savedPictures=picDataHelp.getReadableDatabase();
|
Мы снова используем метод factory, чтобы вернуть экземпляр помощника базы данных. Мы будем использовать курсор для прохождения записей в таблице базы данных, поэтому создайте его сейчас, запрашивая все в таблице «фото»:
1
|
picCursor = savedPictures.query(«pics», null, null, null, null, null, null);
|
Подсказка. В этом руководстве мы используем очень простую реализацию базы данных, чтобы представить основы хранения данных на Android. Тем не менее, для более сложных приложений вы должны обратить внимание на использование контент-провайдеров для вашей базы данных. Смотрите этот пост об использовании контент-провайдеров и этот пост о загрузке данных с помощью курсорных загрузчиков и фрагментов. Это позволит вам повысить эффективность за счет переноса операций загрузки данных из основного потока пользовательского интерфейса приложения, но уровень сложности значительно повышается при базовом использовании, которое мы здесь исследуем, и поэтому немного выходит за рамки этой серии.
Для каждого изображения в базе данных мы собираемся перечислить идентификатор и строку создания. Мы будем использовать простой адаптер курсора, чтобы сопоставить их с элементами в представлении списка, представляя их для выбора пользователями. Нам нужно определить столбцы таблицы базы данных, которые мы хотим отобразить, и представления, которым мы хотим сопоставить их в представлении списка:
1
2
|
String[] columns = {ImageDataHelper.ID_COL, ImageDataHelper.CREATED_COL};
int[] views = {R.id.picID, R.id.picName};
|
Мы можем ссылаться на имена столбцов, используя открытые константы, которые мы создали в вспомогательном классе базы данных. Элементы макета — это два текстовых представления, которые мы включили в макет элемента списка. Теперь мы можем создать Simple Cursor Adapter для сопоставления данных с видимыми элементами пользовательского интерфейса:
1
2
|
SimpleCursorAdapter picAdapter = new SimpleCursorAdapter(this, R.layout.pic_item, picCursor, columns,
views, SimpleCursorAdapter.FLAG_AUTO_REQUERY);
|
Мы передаем макет, который мы создали для каждого элемента списка, курсор, который мы создали для обхода изображений базы данных, столбцов и представлений, которые мы хотим отобразить. Теперь мы можем установить это как Адаптер для Активности Списка:
1
|
setListAdapter(picAdapter);
|
Это приведет к тому, что имена и идентификаторы всех сохраненных изображений будут перечислены в представлении — далее мы осуществим выбор одного из списка для загрузки в текстовое поле.
Шаг 9: реализовать выбор сохраненного изображения
Помните, что когда мы создали макет «pic_item», мы указали атрибут onClick для каждого элемента в списке. Когда пользователи щелкают элемент списка, запускается указанный метод — метод должен быть включен в действие, в котором размещен макет, и в качестве параметра будет получен щелчок по представлению. Добавьте метод в ваш класс действий «PicChooser»:
1
2
3
|
public void picChosen(View view){
}
|
Параметр View — это макет для элемента списка, который содержит два текстовых представления, одно для идентификатора изображения и одно для имени. Мы хотим получить идентификатор выбранной картинки, поэтому в методе нажмите на текстовое представление идентификатора из представления, а затем на его текстовое содержимое:
1
2
|
TextView pickedView = (TextView)view.findViewById(R.id.picID);
String chosenID = (String)pickedView.getText();
|
Идентификатор позволит нам получить содержимое изображения из базы данных. Теперь мы собираемся завершить действие выбора изображения и вернуть идентификатор выбранного изображения в основное действие. Сначала закройте соединения с базой данных:
1
2
3
|
picDataHelp.close();
savedPictures.close();
picCursor.close();
|
Мы собираемся запустить это действие из основного класса Activity, указав, что он должен возвращать результат. Когда это действие заканчивается, метод onActivityResult будет, следовательно, выполняться в основном классе, поэтому мы можем передать ему идентификатор изображения, выбранного пользователем. Создайте намерение и передайте данные:
1
2
|
Intent backIntent = new Intent();
backIntent.putExtra(«pickedImg», chosenID);
|
Установите результат:
1
|
setResult(RESULT_OK, backIntent);
|
Теперь мы можем закончить это занятие:
1
|
finish();
|
Прежде чем мы закончим с классом «PicChooser», нам нужно немного поработать. Если пользователь выбирает изображение из списка, мы убедились, что соединения с базой данных закрыты до завершения действия. Однако пользователь может нажать кнопку «Назад», чтобы вернуться к основному действию вместо выбора изображения. В этом случае мы можем закрыть соединения в onDestroy , просто добавив его в класс:
1
2
3
4
5
6
7
|
@Override
public void onDestroy() {
picCursor.close();
picDataHelp.close();
savedPictures.close();
super.onDestroy();
}
|
Шаг 10: Загрузите выбранное изображение
Теперь у нас есть возможность выбирать между изображениями, хранящимися в базе данных, нам просто нужно загрузить выбранное изображение в текстовое поле. Вернувшись к основной деятельности, добавьте следующие операторы импорта:
1
2
|
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
|
В другом случае, если вы создали для кликов кнопку загрузки метода прослушивания кликов, запустите действие «PicChooser» для результата:
1
2
|
Intent loadIntent = new Intent(this, PicChooser.class);
this.startActivityForResult(loadIntent, LOAD_REQUEST);
|
Обратите внимание, что это похоже на код, который мы использовали при запуске цветовой конфигурации Activity, с константой, представляющей идентификатор для метода onActivityResult — добавьте переменную константы вверху класса:
1
|
private final int LOAD_REQUEST=2;
|
Теперь, когда пользователь выбрал изображение из отображаемого списка, его идентификатор выбранного изображения будет возвращен onActivityResult, поэтому давайте поработаем над этим методом. После оператора if, в котором вы обрабатывали пользователей, возвращающихся из Activity выбора цвета, добавьте else if с похожим контуром:
1
2
3
4
5
|
else if(requestCode == LOAD_REQUEST) {
if(resultCode == RESULT_OK){
}
}
|
Внутри этого блока получите данные, возвращенные из действия «PicChooser»:
1
|
String pickedID = data.getStringExtra(«pickedImg»);
|
Всякий раз, когда изображение, отображаемое в текстовом поле, сохраняется в базе данных, мы сохраняем запись идентификатора сохраненного изображения. В верхней части класса добавьте переменную, чтобы сделать это:
1
|
private int currentPic=-1;
|
Инициализация его к отрицательному позволит нам проверить, является ли текущее изображение из базы данных или нет. Вернувшись в onActivityResult после получения данных из «PicChooser», обновите эту переменную:
1
|
currentPic=Integer.parseInt(pickedID);
|
Мы будем использовать это, когда пользователь удалит или отредактирует сохраненную картинку. Получить экземпляр базы данных от помощника:
1
|
SQLiteDatabase savedPicsDB = imgData.getWritableDatabase();
|
Запросите базу данных для картинки с выбранным идентификатором:
1
2
3
4
5
|
Cursor chosenCursor = savedPicsDB.query(«pics»,
new String[]{ImageDataHelper.ASCII_COL},
ImageDataHelper.ID_COL+»=?»,
new String[]{«»+currentPic},
null, null, null);
|
Найдите минутку, чтобы посмотреть на это. Первый параметр — это таблица, второй — массив String, представляющий нужные нам столбцы, в данном случае просто текст, который составляет изображение ASCII. Третий и четвертый параметры — это выбор, в SQL это, как правило, запрос where , с идентификатором выбранного пользователем изображения, который должен быть сопоставлен в столбце ID, чтобы мы могли получить это конкретное изображение.
Должна быть только одна запись с указанным идентификатором, поэтому переместите курсор на первую найденную запись:
1
|
chosenCursor.moveToFirst();
|
Получить текст для картинки:
1
|
String savedChars = chosenCursor.getString(0);
|
Отобразить картинку в текстовом поле:
1
|
textArea.setText(savedChars);
|
Закройте курсор, базу данных и помощник:
1
2
3
|
chosenCursor.close();
savedPicsDB.close();
imgData.close();
|
Вывод
Это наша база данных, созданная для хранения и загрузки изображений. Проверьте загрузку исходного кода на предмет каких-либо сомнений. В тот момент, когда вы запустите приложение, вы не увидите ни одной сохраненной картинки на выбор. Это потому, что мы еще не реализовали сохранение изображений. Мы сделаем это в следующий раз, в заключительной части серии уроков. Мы также будем обрабатывать удаление изображений, создание новых изображений и редактирование существующих изображений. Тогда наш арт-редактор ASCII будет полностью функциональным.