Пример простого 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

<?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>

Код разметки XML

Вид активности в режиме визуального редактирования

Подготовка 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 в нашем проекте:

Создание новой папки в проекте

Завершение создания папки

Созданная папка assets

Скопируем файл нашей базы данных:

Копирование файла базы данных

Вставка файла базы данных

Окно для изменения названия копируемого файла

Добавленный файл базы данных

Добавление класса для работы с БД

Для открытия и подготовки БД в Android используется наследник класса SQLiteOpenHelper. Мы тоже создадим наследник этого класса DatabaseHelper, но он будет сильно модифицированный, так как мы будем работать с готовой базой данных, а не создавать ей с помощью SQL запросов:

Создание нового Java класса

Ввод названия класса

Созданный класс

Ниже приведен текст всего класса, который нужно просто скопировать (не трогая свой первой строчки 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" />

Компонент ListView

Создадим файл разметки 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>

Создание нового файла разметки XML

Ввод названия файла разметки

Разметка элемента списка

Добавим, например, в метод 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>