Статьи

Android с нуля: как хранить данные приложения локально

Когда дело доходит до локального сохранения данных приложения, разработчики Android определенно избалованы выбором. Помимо прямого доступа к внутренним и внешним областям хранения устройства Android, платформа Android предлагает базы данных SQLite для хранения реляционных данных и специальные файлы для хранения пар ключ-значение. Более того, приложения Android также могут использовать сторонние базы данных, которые поддерживают NoSQL.

В этом уроке я покажу вам, как использовать все эти опции хранения в ваших приложениях для Android. Я также помогу вам понять, как выбрать наиболее подходящий вариант хранения ваших данных.

Тебе легче учиться с видео? Почему бы не проверить наш курс:

Если вы ищете быстрый способ сохранить несколько строк или чисел, вам следует рассмотреть возможность использования файла настроек . Действия и службы Android могут использовать метод getDefaultSharedPreferences() класса PreferenceManager чтобы получить ссылку на объект SharedPreferences который можно использовать как для чтения, так и для записи в файл настроек по умолчанию.

1
2
SharedPreferences myPreferences
   = PreferenceManager.getDefaultSharedPreferences(MyActivity.this);

Чтобы начать запись в файл настроек, необходимо вызвать метод edit() объекта SharedPreferences , который возвращает объект SharedPreferences.Editor .

1
SharedPreferences.Editor myEditor = myPreferences.edit();

Объект SharedPreferences.Editor имеет несколько интуитивно понятных методов, которые можно использовать для сохранения новых пар ключ-значение в файле настроек. Например, вы можете использовать метод putString() для помещения пары ключ-значение, значение которой имеет тип String . Точно так же вы можете использовать метод putFloat() для помещения пары ключ-значение, значение которой имеет тип float . Следующий фрагмент кода создает три пары ключ-значение:

1
2
3
myEditor.putString(«NAME», «Alice»);
myEditor.putInt(«AGE», 25);
myEditor.putBoolean(«SINGLE?», true);

После добавления всех пар необходимо вызвать метод commit() объекта SharedPreferences.Editor чтобы они сохранялись.

1
myEditor.commit();

Чтение из объекта SharedPreferences намного проще. Все, что вам нужно сделать, это вызвать соответствующий метод get*() . Например, чтобы получить пару ключ-значение, значение которой имеет тип String , необходимо вызвать метод getString() . Вот фрагмент кода, который возвращает все значения, которые мы добавили ранее:

1
2
3
String name = myPreferences.getString(«NAME», «unknown»);
int age = myPreferences.getInt(«AGE», 0);
boolean isSingle = myPreferences.getBoolean(«SINGLE?», false);

Как видно из приведенного выше кода, в качестве второго параметра все методы get*() ожидают значение по умолчанию, то есть значение, которое должно быть возвращено, если ключ отсутствует в файле настроек.

Обратите внимание, что файлы настроек ограничиваются только строками и примитивными типами данных. Если вы хотите хранить более сложные типы данных или двоичные данные, вы должны выбрать другой вариант хранения.

Каждое приложение для Android может создавать и использовать базы данных SQLite для хранения больших объемов структурированных данных. Как вы, возможно, уже знаете, SQLite не только легкий, но и очень быстрый. Если у вас есть опыт работы с системами управления реляционными базами данных, и вы знакомы как с SQL (сокращение от языка структурированных запросов), так и с JDBC (сокращение от Java Database Connectivity), это может быть вашим предпочтительным вариантом хранения.

Чтобы создать новую базу данных SQLite или открыть уже существующую, вы можете использовать метод openOrCreateDatabase() внутри своей деятельности или службы. В качестве аргументов вы должны передать имя вашей базы данных и режим, в котором вы хотите ее открыть. Наиболее используемый режим — MODE_PRIVATE , который обеспечивает доступ к базе данных только для вашего приложения. Например, вот как вы можете открыть или создать базу данных с именем my.db :

1
2
SQLiteDatabase myDB =
   openOrCreateDatabase(«my.db», MODE_PRIVATE, null);

После того, как база данных создана, вы можете использовать метод execSQL() для запуска операторов SQL на ней. В следующем коде показано, как использовать оператор SQL CREATE TABLE для создания таблицы с именем user , которая имеет три столбца:

1
2
3
myDB.execSQL(
    «CREATE TABLE IF NOT EXISTS user (name VARCHAR(200), age INT, is_single INT)»
);

Хотя можно вставить новые строки в таблицу с помощью execSQL() , лучше вместо этого использовать метод insert() . Метод insert() ожидает объект ContentValues содержащий значения для каждого столбца таблицы. Объект ContentValues очень похож на объект Map и содержит пары ключ-значение.

Вот два объекта ContentValues вы можете использовать с user таблицей:

1
2
3
4
5
6
7
8
9
ContentValues row1 = new ContentValues();
row1.put(«name», «Alice»);
row1.put(«age», 25);
row1.put(«is_single», 1);
 
ContentValues row2 = new ContentValues();
row2.put(«name», «Bob»);
row2.put(«age», 20);
row2.put(«is_single», 0);

Как вы уже догадались, ключи, передаваемые методу put() должны совпадать с именами столбцов в таблице.

Когда ваши объекты ContentValues будут готовы, вы можете передать их методу insert() вместе с именем таблицы.

1
2
myDB.insert(«user», null, row1);
myDB.insert(«user», null, row2);

Для запроса базы данных вы можете использовать метод rawQuery() , который возвращает объект Cursor содержащий результаты запроса.

1
2
Cursor myCursor =
   myDB.rawQuery(«select name, age, is_single from user», null);

Объект Cursor может содержать ноль или более строк. Самый простой способ moveToNext() все его строки — вызвать метод moveToNext() внутри цикла while.

Чтобы получить значение отдельного столбца, вы должны использовать такие методы, как getString() и getInt() , которые ожидают индекс столбца. Например, вот как вы должны получить все значения, которые вы вставили в user таблицу:

1
2
3
4
5
while(myCursor.moveToNext()) {
    String name = myCursor.getString(0);
    int age = myCursor.getInt(1);
    boolean isSingle = (myCursor.getInt(2)) == 1 ?
}

После того, как вы получили все результаты вашего запроса, убедитесь, что вы вызываете метод close() объекта Cursor , чтобы высвободить все ресурсы, которые он содержит.

1
myCursor.close();

Точно так же, когда вы завершили все операции с базой данных, не забудьте вызвать метод close() объекта SQLiteDatabase .

1
myDB.close();

Каждое Android-приложение имеет собственный внутренний каталог хранения, в котором оно может хранить текстовые и двоичные файлы. Файлы внутри этого каталога недоступны пользователю или другим приложениям, установленным на устройстве пользователя. Они также автоматически удаляются, когда пользователь удаляет приложение.

Прежде чем вы сможете использовать каталог внутреннего хранилища, вы должны определить его местоположение. Для этого вы можете вызвать метод getFilesDir() , который доступен как для действий, так и для служб.

1
File internalStorageDir = getFilesDir();

Чтобы получить ссылку на файл внутри каталога, вы можете передать имя файла вместе с определенным вами местоположением. Например, вот как вы можете получить ссылку на файл с именем alice.csv :

1
File alice = new File(internalStorageDir, «alice.csv»);

С этого момента вы можете использовать свои знания классов и методов ввода / вывода Java для чтения или записи в файл. В следующем фрагменте кода показано, как использовать объект FileOutputStream и его метод write() для записи в файл:

1
2
3
4
5
6
// Create file output stream
fos = new FileOutputStream(alice);
// Write a line to the file
fos.write(«Alice,25,1».getBytes());
// Close the file output stream
fos.close();

Поскольку объем внутренней памяти устройств Android обычно фиксирован и зачастую весьма ограничен, некоторые устройства Android поддерживают внешние носители, такие как съемные карты micro-SD. Я рекомендую вам использовать эту опцию хранения для больших файлов, таких как фотографии и видео.

В отличие от внутреннего хранилища, внешнее хранилище может быть не всегда доступно. Поэтому вы всегда должны проверять, смонтирован ли он перед использованием. Для этого используйте метод getExternalStorageState() класса Environment .

1
2
3
4
5
6
7
if(Environment.getExternalStorageState()
              .equals(Environment.MEDIA_MOUNTED)) {
    // External storage is usable
} else {
    // External storage is not usable
    // Try again later
}

Убедившись, что внешнее хранилище доступно, вы можете получить путь к каталогу внешнего хранилища для вашего приложения, вызвав метод getExternalFilesDir() и передав ему значение null в качестве аргумента. Затем вы можете использовать путь для ссылки на файлы внутри каталога. Например, вот как вы можете сослаться на файл bob.jpg во внешнем каталоге вашего приложения:

1
File bob = new File(getExternalFilesDir(null), «bob.jpg»);

WRITE_EXTERNAL_STORAGE пользователя предоставить вам разрешение WRITE_EXTERNAL_STORAGE , вы можете получить доступ на чтение / запись ко всей файловой системе во внешнем хранилище. Затем вы можете использовать общедоступные общедоступные каталоги для хранения ваших фотографий, фильмов и других медиафайлов. Класс Environment предлагает метод getExternalStoragePublicDirectory() для определения путей этих общедоступных каталогов.

Например, передав в метод значение Environment.DIRECTORY_PICTURES , вы можете определить путь к общедоступному каталогу, в котором вы можете хранить фотографии. Аналогично, если вы передадите в метод значение Environment.DIRECTORY_MOVIES , вы получите путь к общедоступному каталогу, в котором можно хранить фильмы.

Вот как вы можете сослаться на файл с именем bob.jpg в каталоге общедоступных изображений:

1
2
3
4
5
File bobInPictures = new File(
    Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_PICTURES),
    «bob.jpg»
);

Получив объект File , вы снова можете использовать FileInputStream и FileOutputStream для чтения или записи в него.

Теперь вы знаете, как максимально использовать возможности локального хранилища, предоставляемые Android SDK. Независимо от выбранного варианта хранения операции чтения / записи могут занимать много времени, если задействованы большие объемы данных. Поэтому, чтобы убедиться, что основной поток пользовательского интерфейса всегда остается отзывчивым, необходимо рассмотреть возможность выполнения операций в другом потоке.

Чтобы узнать больше о локальном сохранении данных приложения, обратитесь к официальному руководству API хранения данных .