понедельник, 23 апреля 2012 г.

7. Реализация БД

Поскольку в прошлой статье мы спроектировали базу данных, сегодня самое время перенести ее в код.

7. Реализация БД


Придерживаясь рекомендаций Google, мы будем предоставлять данные из БД через Content Provider, который требуется зарегистрировать в AndroidMainfest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.whiterabbit.tags"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="7" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <provider android:name="PuzzleProvider"
            android:authorities="com.WhiteRabbit.provider.Puzzle">
        </provider>
        <activity
            android:name=".PuzzleActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>


Действия по реализации встроенной БД рутинны и однообразны :( Вначале мы создаем в проекте класс PuzzleDb, который будет содержать строковые константы, описывающие все наши таблицы (вообще говоря, можно обойтись и без него, но в этом случае все наименования таблиц и полей будут содержаться непосредственно в коде, что может существенно усложнить последующее сопровождение проекта).

Я не буду приводить листинг всего класса целиком (желающие могут найти его в проекте, выложенном в конце статьи), поскольку он настолько однообразен, что в пору подумать о его автоматической генерации, на основании результата работы нашего CASE-средства (можно распарсить XML-файл или выгружаемые  SQL-скрипты, каким нибудь Perl-скриптом, генерируя непосредственно Java-код). 

package com.whiterabbit.tags;

import android.net.Uri;
import android.provider.BaseColumns;

public final class PuzzleDb {

    private static final String SCHEME = "content://";
    public static final String AUTHORITY = "com.WhiteRabbit.provider.Tags";
   
    private TagsDb() {}
   
    public static final class Locales implements BaseColumns {
       
        public static final String PATH_LOCALE = "locales";
        public static final Uri CONTENT_URI =  Uri.parse(SCHEME + AUTHORITY + "/" + PATH_LOCALE);
        public static final String CONTENT_DIR_TYPE = "vnd.android.cursor.dir/vnd.com.WhiteRabbit.tags.locales";
       
        private Locales() {}
       
        public static final int LOCALE_EN = 1;
        public static final int LOCALE_RU = 2;
       
        public static final String TABLE_NAME = "locale";
        public static final String COLUMN_NAME_NAME = "name";
        public static final String COLUMN_NAME_STRING_ID = "string_id";
        public static final String COLUMN_NAME_DESCRIPTION = "description";
        public static final String COLUMN_NAME_IS_DEFAULT = "is_default";
       
    }
    ...
}


Здесь следует обратить внимание на то, что значение константы AUTHORITY должно совпадать со значением атрибута android:authorities в описании Content Provider-а. Объявив пустой приватный конструктор (чтобы исключить возможность создания экземпляров класса TagsDb), переходим к описанию таблиц БД, используя вложенные классы.

Внутри вложенных классов мы определяем константами наименование таблицы (TABLE_NAME) и всех ее столбцов. Также мы можем определить заранее известные значения (такие как внутренние коды локалей, используемые нашей БД).

Константы PATH_LOCALE, CONTENT_URI, CONTENT_DIR_TYPE и CONTENT_ITEM_TYPE будут использоваться нами при реализации Content Provider-а, пока не обращаем на них внимания. Также как и для TagsDb, создаем приватный конструктор, чтобы исключить возможность инстанционирования класса.

Далее создаем расширение класса android.content.ContentProvider. Содержимое этого класса несколько более интересно и я приведу его полностью: 

package com.whiterabbit.tags;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;

public class PuzzleProvider extends ContentProvider {
 
   private static final String DATABASE_NAME = "puzzle.db";
   private static final int DATABASE_VERSION = 1;
  
   private DatabaseHelper mOpenHelper;
  
   @Override
   public boolean onCreate() {
       mOpenHelper = new DatabaseHelper(getContext());
       return true;
   }

   @Override
   public String getType(Uri uri) {
       // TODO:
       return null;
   }

   @Override
   public Uri insert(Uri uri, ContentValues values) {
       SQLiteDatabase db = mOpenHelper.getWritableDatabase();
       // TODO:
       return null;
   }

   @Override
   public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
       SQLiteDatabase db = mOpenHelper.getWritableDatabase();
       // TODO:
       return 0;
   }
  
   @Override
   public int delete(Uri uri, String selection, String[] selectionArgs) {
       int r = 0;
       SQLiteDatabase db = mOpenHelper.getWritableDatabase();
       // TODO:
       return r;
   }

   @Override
   public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
       SQLiteDatabase db = mOpenHelper.getReadableDatabase();
       // TODO:
       return null;
   }

   static class DatabaseHelper extends SQLiteOpenHelper {

       DatabaseHelper(Context context) {
          super(context, DATABASE_NAME, null, DATABASE_VERSION);
       }
      
       public void addString(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.Strings.TABLE_NAME + "(" +
                   PuzzleDb.Strings._ID + ") values (" + s + ")");
       }
      
       public void addValue(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.StringValues.TABLE_NAME + "(" +
                   PuzzleDb.StringValues._ID + "," +
                   PuzzleDb.StringValues.COLUMN_NAME_LOCALE_ID + "," +
                   PuzzleDb.StringValues.COLUMN_NAME_STRING_ID + "," +
                   PuzzleDb.StringValues.COLUMN_NAME_VALUE + ") values " +
                   "(" + s + ")");
       }
      
       public void addLocale(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.Locales.TABLE_NAME + "(" +
                   PuzzleDb.Locales._ID + "," +
                   PuzzleDb.Locales.COLUMN_NAME_NAME + "," +
                   PuzzleDb.Locales.COLUMN_NAME_STRING_ID + "," +
                   PuzzleDb.Locales.COLUMN_NAME_DESCRIPTION + ") values " +
                   "(" + s + ")");
       }
      
       public void addPosition(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.Positions.TABLE_NAME + "(" +
                   PuzzleDb.Positions._ID + "," +
                   PuzzleDb.Positions.COLUMN_NAME_IS_PROTECTED + ") values " +
                        "(" + s + ")");
       }
      
       public void addPuzzle(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.Puzzles.TABLE_NAME + "(" +
                   PuzzleDb.Puzzles._ID + "," +
                   PuzzleDb.Puzzles.COLUMN_NAME_STRING_ID + "," +
                   PuzzleDb.Puzzles.COLUMN_NAME_START_POSITION_ID + ") values " +
                        "(" + s + ")");
       }
          
       public void addParam(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.Params.TABLE_NAME + "(" +
                   PuzzleDb.Params._ID + "," +
                   PuzzleDb.Params.COLUMN_NAME_PARAM_TYPE_ID + "," +
                   PuzzleDb.Params.COLUMN_NAME_PUZZLE_ID + "," +
                   PuzzleDb.Params.COLUMN_NAME_VALUE + ") values " +
                        "(" + s + ")");
       }

       public void addEndPos(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.EndPositions.TABLE_NAME + "(" +
                   PuzzleDb.EndPositions._ID + "," +
                   PuzzleDb.EndPositions.COLUMN_NAME_PUZZLE_ID + "," +
                   PuzzleDb.EndPositions.COLUMN_NAME_POSITION_ID + ") values " +
                        "(" + s + ")");
       }
      
       public void addTag(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.Tags.TABLE_NAME + "(" +
                   PuzzleDb.Tags._ID + "," +
                   PuzzleDb.Tags.COLUMN_NAME_PUZZLE_ID + "," +
                   PuzzleDb.Tags.COLUMN_NAME_IX + ") values " +
                        "(" + s + ")");
       }

       public void addItem(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.Items.TABLE_NAME + "(" +
                   PuzzleDb.Items._ID + "," +
                   PuzzleDb.Items.COLUMN_NAME_TAG_ID + "," +
                   PuzzleDb.Items.COLUMN_NAME_IMG_ID + "," +
                   PuzzleDb.Items.COLUMN_NAME_X + "," +
                   PuzzleDb.Items.COLUMN_NAME_Y + ") values " +
                        "(" + s + ")");
       }
      
       public void addTagPos(SQLiteDatabase db, String s) {
           db.execSQL("insert into " + PuzzleDb.TagPositions.TABLE_NAME + "(" +
                   PuzzleDb.TagPositions._ID + "," +
                   PuzzleDb.TagPositions.COLUMN_NAME_TAG_ID + "," +
                   PuzzleDb.TagPositions.COLUMN_NAME_POSITION_ID + "," +
                   PuzzleDb.TagPositions.COLUMN_NAME_X + "," +
                   PuzzleDb.TagPositions.COLUMN_NAME_Y + ") values " +
                        "(" + s + ")");
       }

       @Override
       public void onCreate(SQLiteDatabase db) {

           db.execSQL("CREATE TABLE " + PuzzleDb.Strings.TABLE_NAME + " (" +
                   PuzzleDb.Strings._ID + " INTEGER PRIMARY KEY);");
           addString(db, "1"); // English
           addString(db, "2"); // Russian
           addString(db, "3"); // Size X
           addString(db, "4"); // Size Y
           addString(db, "11");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Locales.TABLE_NAME + " (" +
                   PuzzleDb.Locales._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Locales.COLUMN_NAME_NAME + " TEXT," +
                   PuzzleDb.Locales.COLUMN_NAME_STRING_ID + " INTEGER," +
                   PuzzleDb.Locales.COLUMN_NAME_IS_DEFAULT + " INTEGER," +
                   PuzzleDb.Locales.COLUMN_NAME_DESCRIPTION + " TEXT);");
           addLocale(db, PuzzleDb.Locales.LOCALE_EN + ", 'en_US', 6, 1");
           addLocale(db, PuzzleDb.Locales.LOCALE_RU + ", 'ru_RU', 7, 0");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.StringValues.TABLE_NAME + " (" +
                   PuzzleDb.StringValues._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.StringValues.COLUMN_NAME_LOCALE_ID + " INTEGER," +
                   PuzzleDb.StringValues.COLUMN_NAME_STRING_ID + " INTEGER," +
                   PuzzleDb.StringValues.COLUMN_NAME_VALUE + " TEXT);");
           addValue(db, "1, 1,  1, 'English'");
           addValue(db, "2, 2,  1, 'Английский'");
           addValue(db, "3, 1,  2, 'Russian'");
           addValue(db, "4, 2,  2, 'Русский'");
           addValue(db, "5, 1, 11, 'Donkey'");
           addValue(db, "6, 2, 11, 'Рыжий осел'");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Profiles.TABLE_NAME + " (" +
                   PuzzleDb.Profiles._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Profiles.COLUMN_NAME_LOGIN + " TEXT," +
                   PuzzleDb.Profiles.COLUMN_NAME_LOCALE_ID + " INTEGER," +
                   PuzzleDb.Profiles.COLUMN_NAME_PUZZLE_ID + " INTEGER," +
                   PuzzleDb.Profiles.COLUMN_NAME_IS_DEFAULT + " INTEGER);");
           db.execSQL("insert into " + PuzzleDb.Profiles.TABLE_NAME + "(" +
                   PuzzleDb.Profiles._ID + "," +
                   PuzzleDb.Profiles.COLUMN_NAME_LOGIN + "," +
                   PuzzleDb.Profiles.COLUMN_NAME_LOCALE_ID + "," +
                   PuzzleDb.Profiles.COLUMN_NAME_PUZZLE_ID + "," +
                   PuzzleDb.Profiles.COLUMN_NAME_IS_DEFAULT + ") values " +
                   "(1, 'default', 0, 3, 1)");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Sessions.TABLE_NAME + " (" +
                   PuzzleDb.Sessions._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Sessions.COLUMN_NAME_PROFILE_ID + " INTEGER," +
                   PuzzleDb.Sessions.COLUMN_NAME_PUZZLE_ID + " INTEGER," +
                   PuzzleDb.Sessions.COLUMN_NAME_POSITION_ID + " INTEGER," +
                   PuzzleDb.Sessions.COLUMN_NAME_IS_CURRENT + " INTEGER," +
                   PuzzleDb.Sessions.COLUMN_NAME_START_DATE + " TEXT," +
                   PuzzleDb.Sessions.COLUMN_NAME_END_DATE + " TEXT," +
                   PuzzleDb.Sessions.COLUMN_NAME_IS_CLOSED + " INTEGER);");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.StatTypes.TABLE_NAME + " (" +
                   PuzzleDb.StatTypes._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.StatTypes.COLUMN_NAME_STRING_ID + " INTEGER);");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Positions.TABLE_NAME + " (" +
                   PuzzleDb.Positions._ID + " INTEGER PRIMARY KEY," +
                      PuzzleDb.Positions.COLUMN_NAME_IS_PROTECTED + " INTEGER);");
           addPosition(db, "1, 1");
           addPosition(db, "2, 1");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Puzzles.TABLE_NAME + " (" +
                   PuzzleDb.Puzzles._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Puzzles.COLUMN_NAME_STRING_ID + " INTEGER," +
                   PuzzleDb.Puzzles.COLUMN_NAME_START_POSITION_ID + " INTEGER);");
           addPuzzle(db, "1, 1, 1");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.ParamTypes.TABLE_NAME + " (" +
                   PuzzleDb.ParamTypes._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.ParamTypes.COLUMN_NAME_STRING_ID + " INTEGER);");
           db.execSQL("insert into " + PuzzleDb.ParamTypes.TABLE_NAME + "(" +
                   PuzzleDb.ParamTypes._ID + "," +
                   PuzzleDb.ParamTypes.COLUMN_NAME_STRING_ID + ") values " +
                   "(" + Integer.toString(PuzzleDb.ParamTypes.SizeX) + ", 3)");
           db.execSQL("insert into " + PuzzleDb.ParamTypes.TABLE_NAME + "(" +
                   PuzzleDb.ParamTypes._ID + "," +
                   PuzzleDb.ParamTypes.COLUMN_NAME_STRING_ID + ") values " +
                   "(" + Integer.toString(PuzzleDb.ParamTypes.SizeY) + ", 4)");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Params.TABLE_NAME + " (" +
                   PuzzleDb.Params._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Params.COLUMN_NAME_PARAM_TYPE_ID + " INTEGER," +
                   PuzzleDb.Params.COLUMN_NAME_PUZZLE_ID + " INTEGER," +
                   PuzzleDb.Params.COLUMN_NAME_VALUE + " TEXT);");
           addParam(db, "1, " + Integer.toString(PuzzleDb.ParamTypes.SizeX) + ", 1, 4");
           addParam(db, "2, " + Integer.toString(PuzzleDb.ParamTypes.SizeY) + ", 1, 5");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Stats.TABLE_NAME + " (" +
                   PuzzleDb.Stats._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Stats.COLUMN_NAME_STAT_TYPE_ID + " INTEGER," +
                   PuzzleDb.Stats.COLUMN_NAME_SESSION_ID + " INTEGER," +
                   PuzzleDb.Stats.COLUMN_NAME_VALUE + " INTEGER);");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.EndPositions.TABLE_NAME + " (" +
                   PuzzleDb.EndPositions._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.EndPositions.COLUMN_NAME_PUZZLE_ID + " INTEGER," +
                   PuzzleDb.EndPositions.COLUMN_NAME_POSITION_ID + " INTEGER);");
           addEndPos(db, "1, 1, 2");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Tags.TABLE_NAME + " (" +
                   PuzzleDb.Tags._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Tags.COLUMN_NAME_PUZZLE_ID + " INTEGER," +
                   PuzzleDb.Tags.COLUMN_NAME_IX + " INTEGER);");
           addTag(db, "1, 1, 1");
           addTag(db, "2, 1, 2");
           addTag(db, "3, 1, 3");
           addTag(db, "4, 1, 4");
           addTag(db, "5, 1, 5");
           addTag(db, "6, 1, 6");
           addTag(db, "7, 1, 7");
           addTag(db, "8, 1, 8");
           addTag(db, "9, 1, 9");
           addTag(db, "10, 1, 10");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.Items.TABLE_NAME + " (" +
                   PuzzleDb.Items._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.Items.COLUMN_NAME_TAG_ID + " INTEGER," +
                   PuzzleDb.Items.COLUMN_NAME_IMG_ID + " INTEGER," +
                   PuzzleDb.Items.COLUMN_NAME_X + " INTEGER," +
                   PuzzleDb.Items.COLUMN_NAME_Y + " INTEGER);");
           addItem(db, "1,  1,  0, 0, 0");
           addItem(db, "2,  1,  0, 0, 1");
           addItem(db, "3,  2,  0, 0, 0");
           addItem(db, "4,  2,  0, 1, 0");
           addItem(db, "5,  2,  0, 0, 1");
           addItem(db, "6,  2,  0, 1, 1");
           addItem(db, "7,  3,  0, 0, 0");
           addItem(db, "8,  3,  0, 0, 1");
           addItem(db, "9,  4,  0, 0, 0");
           addItem(db, "10, 4,  0, 0, 1");
           addItem(db, "11, 5,  0, 0, 0");
           addItem(db, "13, 5,  0, 1, 0");
           addItem(db, "14, 6,  0, 0, 0");
           addItem(db, "15, 6,  0, 0, 1");
           addItem(db, "16, 7,  0, 0, 0");
           addItem(db, "17, 8,  0, 0, 0");
           addItem(db, "18, 9,  0, 0, 0");
           addItem(db, "19, 10, 0, 0, 0");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.TagPositions.TABLE_NAME + " (" +
                   PuzzleDb.TagPositions._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.TagPositions.COLUMN_NAME_TAG_ID + " INTEGER," +
                   PuzzleDb.TagPositions.COLUMN_NAME_POSITION_ID + " INTEGER," +
                   PuzzleDb.TagPositions.COLUMN_NAME_X + " INTEGER," +
                   PuzzleDb.TagPositions.COLUMN_NAME_Y + " INTEGER);");
           addTagPos(db, "1,  1,  1, 1, 1");
           addTagPos(db, "2,  2,  1, 2, 1");
           addTagPos(db, "3,  3,  1, 4, 1");
           addTagPos(db, "4,  4,  1, 1, 3");
           addTagPos(db, "5,  5,  1, 2, 3");
           addTagPos(db, "6,  6,  1, 4, 3");
           addTagPos(db, "7,  7,  1, 2, 4");
           addTagPos(db, "8,  8,  1, 3, 4");
           addTagPos(db, "9,  9,  1, 1, 5");
           addTagPos(db, "10, 10, 1, 4, 5");
           addTagPos(db, "11, 2,  2, 2, 4");
          
           db.execSQL("CREATE TABLE " + PuzzleDb.SolutionSteps.TABLE_NAME + " (" +
                   PuzzleDb.SolutionSteps._ID + " INTEGER PRIMARY KEY," +
                   PuzzleDb.SolutionSteps.COLUMN_NAME_SESSION_ID + " INTEGER," +
                   PuzzleDb.SolutionSteps.COLUMN_NAME_ORD_NUM + " INTEGER," +
                   PuzzleDb.SolutionSteps.COLUMN_NAME_TAG_ID + " INTEGER," +
                   PuzzleDb.SolutionSteps.COLUMN_NAME_DX + " INTEGER," +
                   PuzzleDb.SolutionSteps.COLUMN_NAME_DY + " INTEGER);");
       }

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


Наименование класса должно совпадать с именем Content Provider-а объявленного в AndroidManifest.xml. Также следует обратить внимание на константы  DATABASE_NAME и DATABASE_VERSION. В DATABASE_NAME задается имя файла, в  котором будет храниться БД (в каталоге /data/data/com.WhiteRabbit.tags/databases на устройстве или эмуляторе). В DATABASE_VERSION задается версия нашей БД. При любых изменениях БД номер следует увеличивать, чтобы Android мог отследить необходимость применения изменений БД.

Большинство методов, унаследованных от ContentProvider пока оставляем пустыми (ими мы займемся в следующих статьях). В метод onCreate внутреннего класса DatabaseHelper помещаем код, выполняющий создание БД, держа перед глазами ER-диаграмму, разработанную в предыдущей статье.

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

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

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