Подключение существующей БД SQLite в Android Studio
Пример простого Android приложения, в котором подключаемся к заранее подготовленной базе данных SQLite.
Введение
Есть два подхода к работе с БД в Android приложениях.
В первом варианте БД создается в событии OnCreate
главной активности. Данный вариант хорош для случая, когда база данных при установке приложения пуста либо заполнена небольшим количеством данных, а также в БД активно производятся записи в дальнейшем.
Но данный способ не очень хорош, если, например, пишите какой-нибудь справочник или другое приложение, когда БД при установке приложения уже должна быть заполнена большим количеством записей. На мой взгляд, в этом случае лучше БД подготовить заранее, а потом уже её подключить как отдельный файл в ресурсах приложения. В данной статье рассмотрен данный случай.
Создание базы данных
Для создания БД SQLite будем использовать, например, DB Browser for SQLite. Скачиваем и устанавливаем.
Буем создавать БД с одной таблицей такого вида.
_id | name | age |
---|---|---|
1 | Anton | 30 |
2 | Alina | 24 |
3 | Dima | 28 |
4 | Dasha | 23 |
Итак, создаем базу данных:
Где-нибудь сохраняем и называем, например, info.db
:
Создаем таблицу, например, clients
. И добавляем там поле:
Первым полем у нас будет номер записи _id
. Поле будет также первичным ключом:
Аналогичным способом создаем поля age
и name
. И жмем OK
:
В списке таблиц у нас появилась наша таблица clients
:
Переходим в режим заполнения таблицы:
Выбираем там нашу таблицу и жмем Добавить запись
:
Заполняем наши данные и сохраняем изменения в БД:
Файл подготовленной базы данных можно взять из архива: info.zip.
Создание Android проекта
Открываем Android Studio и создаем там новый проект с пустой активностью. Всё как обычно:
Разметка активности
Так как мы создаем простейшее приложение, но в XML файле активности разместим только кнопку и поле для вывода текста:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical" >
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="Read DB" />
</LinearLayout>
Подготовка Java кода
Нам потребуется обработать клик на кнопку button
и что-то записать в textView
.
Поэтому найдем данные компоненты и свяжем их в Java коде с XML:
Объявим переменные компонентов:
Button button;
TextView textView;
Найдем компоненты в XML разметке:
button = (Button) findViewById(R.id.button);
textView = (TextView) findViewById(R.id.textView);
Пропишем обработчик клика кнопки:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
Полный код Java файла (без строчки package
, которая у вас должна быть своей):
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Объявим переменные компонентов
Button button;
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Найдем компоненты в XML разметке
button = (Button) findViewById(R.id.button);
textView = (TextView) findViewById(R.id.textView);
// Пропишем обработчик клика кнопки
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
Добавление БД в проект
Все приготовления сделаны. Теперь можем начать работать по теме статьи. Вначале добавим файл базы данных в проект.
Создадим папку assets
в нашем проекте:
Скопируем файл нашей базы данных:
Добавление класса для работы с БД
Для открытия и подготовки БД в Android используется наследник класса SQLiteOpenHelper
. Мы тоже создадим наследник этого класса DatabaseHelper
, но он будет сильно модифицированный, так как мы будем работать с готовой базой данных, а не создавать ей с помощью SQL запросов:
Ниже приведен текст всего класса, который нужно просто скопировать (не трогая свой первой строчки package
):
import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class DatabaseHelper extends SQLiteOpenHelper {
private static String DB_NAME = "info.db";
private static String DB_PATH = "";
private static final int DB_VERSION = 1;
private SQLiteDatabase mDataBase;
private final Context mContext;
private boolean mNeedUpdate = false;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
if (android.os.Build.VERSION.SDK_INT >= 17)
DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
else
DB_PATH = "/data/data/" + context.getPackageName() + "/databases/";
this.mContext = context;
copyDataBase();
this.getReadableDatabase();
}
public void updateDataBase() throws IOException {
if (mNeedUpdate) {
File dbFile = new File(DB_PATH + DB_NAME);
if (dbFile.exists())
dbFile.delete();
copyDataBase();
mNeedUpdate = false;
}
}
private boolean checkDataBase() {
File dbFile = new File(DB_PATH + DB_NAME);
return dbFile.exists();
}
private void copyDataBase() {
if (!checkDataBase()) {
this.getReadableDatabase();
this.close();
try {
copyDBFile();
} catch (IOException mIOException) {
throw new Error("ErrorCopyingDataBase");
}
}
}
private void copyDBFile() throws IOException {
InputStream mInput = mContext.getAssets().open(DB_NAME);
OutputStream mOutput = new FileOutputStream(DB_PATH + DB_NAME);
byte[] mBuffer = new byte[1024];
int mLength;
while ((mLength = mInput.read(mBuffer)) > 0)
mOutput.write(mBuffer, 0, mLength);
mOutput.flush();
mOutput.close();
mInput.close();
}
public boolean openDataBase() throws SQLException {
mDataBase = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.CREATE_IF_NECESSARY);
return mDataBase != null;
}
@Override
public synchronized void close() {
if (mDataBase != null)
mDataBase.close();
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion > oldVersion)
mNeedUpdate = true;
}
}
Для работы в последующим с другими базами данных вам ничего не нужно будет менять в данном классе, кроме строчек:
private static String DB_NAME = "info.db";
private static String DB_PATH = "";
private static final int DB_VERSION = 1;
Разберем что означают эти строчки.
DB_NAME
— имя файла БД. Какой файл БД вы создали, такое название сюда и копируем.
DB_PATH
— путь к БД. Каждое приложение в Android имеет свою область памяти, куда складываются файлы программы. Вдруг вы захотите вывернуть путь к файлу БД. Я бы ничего не трогал.
DB_VERSION
— самая интересная переменная (причем в примерах в сети по работе с готовой БД её обходят стороной). Это номер версии БД. Ниже описан принцип работы данного класса. Например, вы пишите справочник рецептов под Android и рецепты храните в БД. В момент создания установки приложения программа должна скопировать БД на устройство. Потом через какое-то время вы решили обновить приложение, и БД у вас обновилась: структура БД поменялась, добавились новые рецепты. И вам нужно заменить старую БД на новую. Вот тут вы и пропишите в данной переменной новую версию БД. И при открытии приложения будет произведена проверки версии БД, и файл БД обновится. Вначале версия БД равна 1.
Итак, логика работы класса DatabaseHelper
в подготовке базы данных:
-
Копируем файл БД, если этого файла нет (при установке приложения).
-
Если номер БД обновлен, то заменяем один файл базы данных на другой:
private static final int DB_VERSION = 2;
- После работы с базой данных из данного класса вытаскиваем экземпляр
SQLiteDatabase
, с которым будем работать в дальнейшем: осуществлять запросы и так далее.
Подключаемся к БД
Перейдем в класс нашей активности. В нем создадим экземпляр класса DatabaseHelper
, попытаемся обновить БД, если это требуется, а потом вытащим экземпляр SQLiteDatabase
.
Создадим переменные в классе:
// Переменная для работы с БД
private DatabaseHelper mDBHelper;
private SQLiteDatabase mDb;
В методе onCreate
выполним подготовительные действия:
mDBHelper = new DatabaseHelper(this);
try {
mDBHelper.updateDataBase();
} catch (IOException mIOException) {
throw new Error("UnableToUpdateDatabase");
}
try {
mDb = mDBHelper.getWritableDatabase();
} catch (SQLException mSQLException) {
throw mSQLException;
}
Работа с базой данных
Теперь мы можем наконец в клике кнопки соединиться с базой данной и вытащить нужные нам данные.
Давайте при клике кнопки в textView
отобразятся все имена учеников в строчку. Будем работать с помощью Cursor
.
В setOnClickListener
припишем такой, например, код:
String product = "";
Cursor cursor = mDb.rawQuery("SELECT * FROM clients", null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
product += cursor.getString(1) + " | ";
cursor.moveToNext();
}
cursor.close();
textView.setText(product);
Полный код Java файла активности у меня получился такой (без первой строчки package
):
import androidx.appcompat.app.AppCompatActivity;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
// Объявим переменные компонентов
Button button;
TextView textView;
// Переменная для работы с БД
private DatabaseHelper mDBHelper;
private SQLiteDatabase mDb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDBHelper = new DatabaseHelper(this);
try {
mDBHelper.updateDataBase();
} catch (IOException mIOException) {
throw new Error("UnableToUpdateDatabase");
}
try {
mDb = mDBHelper.getWritableDatabase();
} catch (SQLException mSQLException) {
throw mSQLException;
}
// Найдем компоненты в XML разметке
button = (Button) findViewById(R.id.button);
textView = (TextView) findViewById(R.id.textView);
// Пропишем обработчик клика кнопки
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String product = "";
Cursor cursor = mDb.rawQuery("SELECT * FROM clients", null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
product += cursor.getString(1) + " | ";
cursor.moveToNext();
}
cursor.close();
textView.setText(product);
}
});
}
}
Запустим приложение:
Вот так приложение выглядит при запуске:
При нажатии на кнопку получим список имен из БД:
Фактически это всё. У нас есть экземпляр SQLiteDatabase mDb
, с которым мы можем работать так как нам нужно. Дальше будут рассмотрены некоторые особенности работы с БД.
Обновление БД
Откроем в программе DB Browser for SQLite
файл БД, который располагается в папке assets
нашей программы. И внесем какие-нибудь изменения и сохраним. Я для примера поменял имя в первой строке таблицы:
Итак, в исходниках программы у нас файл БД поменялся. Запустим приложение.
И увидим, что в приложении изменения не проявились. Почему? Потому что приложение не обновляет файл БД каждый раз при запуске приложения. А вдруг вы записываете в БД какие-то записи: тогда при обновлении файла все добавленные записи сотрутся:
Нам нужно в файле класса DatabaseHelper поменять номер версии БД в сторону увеличения:
private static final int DB_VERSION = 2;
Теперь при запуске приложения данные обновятся:
Обратите внимание на то, что обновление БД произойдет только один раз. И до следующего изменения переменной DB_VERSION
файл БД обновляться файлом из папки assets
не будет.
Внимание! При обновлении БД заменяется файл БД, а, значит, все внесенные изменения в БД на приложении в Android (через запросы INSERT
, UPDATE
) будут удалены! Поэтому, если вам внесенные изменения нужны, то не вызывайте метод updateDataBase
, а в методе onUpgrade
внесите стандартным способом обновления в БД. При этом замена файла БД не будет происходить. Например, так можно вставить в таблицу новый столбец:
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion > oldVersion) {
String q = "ALTER TABLE clients ADD height INTEGER";
db.execSQL(q);
}
}
Работа с большой БД
Когда готовил статью, то я часто встречал замечания, что файлы больше 1 Мб или 8 Мб из папки assets
не копируются. Хотя, я пробовал работать с файлом в 14 Мб. Запускал на разных устройствах и никаких проблем не заметил.
Если что, то вот этот файл info_large.zip
Но мало ли. Вдруг у вас проблемы будут замечены. В качестве решения можно размещать файл БД не папке assets
, а в папке res/raw
:
И файл БД копируем в эту папку:
В классе DatabaseHelper
нам нужно поменять только одну строчку в методе copyDBFile
.
Было:
InputStream mInput = mContext.getAssets().open(DB_NAME);
Стало:
InputStream mInput = mContext.getResources().openRawResource(R.raw.info_large);
Да, если вы перед этим использовали старый вариант на том устройстве, где тестируете приложение, то не забудьте поменять версию базы данных DB_VERSION
, а то вы не увидите изменений в базе данных. Можно также удалить приложение вначале, а заново его установить.
Полный код класса (без строчки package
):
import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class DatabaseHelper extends SQLiteOpenHelper {
private static String DB_NAME = "info.db";
private static String DB_PATH = "";
private static final int DB_VERSION = 1;
private SQLiteDatabase mDataBase;
private final Context mContext;
private boolean mNeedUpdate = false;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
if (android.os.Build.VERSION.SDK_INT >= 17)
DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
else
DB_PATH = "/data/data/" + context.getPackageName() + "/databases/";
this.mContext = context;
copyDataBase();
this.getReadableDatabase();
}
public void updateDataBase() throws IOException {
if (mNeedUpdate) {
File dbFile = new File(DB_PATH + DB_NAME);
if (dbFile.exists())
dbFile.delete();
copyDataBase();
mNeedUpdate = false;
}
}
private boolean checkDataBase() {
File dbFile = new File(DB_PATH + DB_NAME);
return dbFile.exists();
}
private void copyDataBase() {
if (!checkDataBase()) {
this.getReadableDatabase();
this.close();
try {
copyDBFile();
} catch (IOException mIOException) {
throw new Error("ErrorCopyingDataBase");
}
}
}
private void copyDBFile() throws IOException {
//InputStream mInput = mContext.getAssets().open(DB_NAME);
InputStream mInput = mContext.getResources().openRawResource(R.raw.info_large);
OutputStream mOutput = new FileOutputStream(DB_PATH + DB_NAME);
byte[] mBuffer = new byte[1024];
int mLength;
while ((mLength = mInput.read(mBuffer)) > 0)
mOutput.write(mBuffer, 0, mLength);
mOutput.flush();
mOutput.close();
mInput.close();
}
public boolean openDataBase() throws SQLException {
mDataBase = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.CREATE_IF_NECESSARY);
return mDataBase != null;
}
@Override
public synchronized void close() {
if (mDataBase != null)
mDataBase.close();
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion > oldVersion)
mNeedUpdate = true;
}
}
Отображение списка данных по запросу
В примерах выше мы выводили информацию просто в строку в textView
. Для демонстрации работы класса это достаточно, но чаще всего требуется данные из БД выводить списком. Напоследок приведу пример, когда на экран список людей из таблицы БД выведется не в строчку, а списком.
Делается это через обычный адаптер. Особенно не буду перегружать объяснением.
В activity_main.xml
добавляем ListView
:
<ListView android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Создадим файл разметки adapter_item.xml
, в котором опишем внешний вид одного элемента списка с таким содержанием:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Имя"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Возраст"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
Добавим, например, в метод onCreate главной активности код:
// Список клиентов
ArrayList<HashMap<String, Object>> clients = new ArrayList<HashMap<String, Object>>();
// Список параметров конкретного клиента
HashMap<String, Object> client;
// Отправляем запрос в БД
Cursor cursor = mDb.rawQuery("SELECT * FROM clients", null);
cursor.moveToFirst();
// Пробегаем по всем клиентам
while (!cursor.isAfterLast()) {
client = new HashMap<String, Object>();
// Заполняем клиента
client.put("name", cursor.getString(1));
client.put("age", cursor.getString(2));
// Закидываем клиента в список клиентов
clients.add(client);
// Переходим к следующему клиенту
cursor.moveToNext();
}
cursor.close();
// Какие параметры клиента мы будем отображать в соответствующих
// элементах из разметки adapter_item.xml
String[] from = { "name", "age"};
int[] to = { R.id.textView, R.id.textView2};
// Создаем адаптер
SimpleAdapter adapter = new SimpleAdapter(this, clients, R.layout.adapter_item, from, to);
ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
Запускаем приложение. Видим список наших клиентов:
Помните, что запросы к БД могут быть длительными, поэтому работу с БД лучше запихивать в другой поток, например, через ASyncTask
.
Добавление новых записей из Android приложения
В предыдущей версии статьи многие спрашивали, а как добавлять новые записи в таблицу. Абсолютно также, как и в обычном подходе в работе с базой данных. Например, вот код добавления новой записи:
String query = "INSERT INTO clients (name, age) VALUES ('Tom', '11')";
mDb.execSQL(query);
И полный код примера файла активности с данным кодом (без строки package
):
import androidx.appcompat.app.AppCompatActivity;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
public class MainActivity extends AppCompatActivity {
// Объявим переменные компонентов
Button button;
TextView textView;
// Переменная для работы с БД
private DatabaseHelper mDBHelper;
private SQLiteDatabase mDb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDBHelper = new DatabaseHelper(this);
try {
mDBHelper.updateDataBase();
} catch (IOException mIOException) {
throw new Error("UnableToUpdateDatabase");
}
try {
mDb = mDBHelper.getWritableDatabase();
} catch (SQLException mSQLException) {
throw mSQLException;
}
// Найдем компоненты в XML разметке
button = (Button) findViewById(R.id.button);
textView = (TextView) findViewById(R.id.textView);
// Пропишем обработчик клика кнопки
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String query = "INSERT INTO clients (name, age) VALUES ('Tom', '11')";
mDb.execSQL(query);
updateList();
}
});
updateList();
}
void updateList() {
// Список клиентов
ArrayList<HashMap<String, Object>> clients = new ArrayList<HashMap<String, Object>>();
// Список параметров конкретного клиента
HashMap<String, Object> client;
// Отправляем запрос в БД
Cursor cursor = mDb.rawQuery("SELECT * FROM clients", null);
cursor.moveToFirst();
// Пробегаем по всем клиентам
while (!cursor.isAfterLast()) {
client = new HashMap<String, Object>();
// Заполняем клиента
client.put("name", cursor.getString(1));
client.put("age", cursor.getString(2));
// Закидываем клиента в список клиентов
clients.add(client);
// Переходим к следующему клиенту
cursor.moveToNext();
}
cursor.close();
// Какие параметры клиента мы будем отображать в соответствующих
// элементах из разметки adapter_item.xml
String[] from = { "name", "age"};
int[] to = { R.id.textView, R.id.textView2};
// Создаем адаптер
SimpleAdapter adapter = new SimpleAdapter(this, clients, R.layout.adapter_item, from, to);
ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
}
}
Но я крайне не рекомендую добавлять новые записи в таблицу из-под Android приложения, если база данных представлена в виде файла. Так как если вы потом обновите файл базы данных в другой версии вашего приложения, то пользователь потеряет все свои данные. Либо пропишите бэкап пользовательских записей, либо используете для них вторую базу данных, созданную стандартным способом.<ul><li>Android Studio icon.svg by Google Inc. / (2019-06-07)</li><li>Sqlite-square-icon.svg by Mike Toews / (2019-01-26)</li></ul>
Статья обновлена 2020-01-20
Тэги:
- Android
- Android Studio
- Java
- SQLite
- Базы данных
- SQL
Категории:
- blog
- it
- programming
Пример простого Android приложения, в котором подключаемся к заранее подготовленной базе данных SQLite.
Пример простого Android приложения, в котором подключаемся к заранее подготовленной базе данных SQLite.