Платформа Android Ведущий семинара: Максим Лейкин, компания «МЕРА НН» Виды хранилищ данных На платформе Android имеются следующие варианты хранения данных: • Shared Preferences позволяет хранить набор пар «ключ-значение» • Files позволяет хранить данные произвольного типа • SQL Lite DB позволяет создавать локальные базы данных • Content Providers Интерфейс для доступа к данным, полученным из разных источников Shared Preferences С помощью Shared Preferences можно создавать именованные наборы пар «ключ-значение», которые могут совместно использоваться приложениями работающими в одном контексте (application context). Чаще всего используются для передачи данных между пользовательскими сессиями и компонентами одного приложения. Поддерживаются типы: boolean, string, float, long, integer. Shared Preferences Создание/модификация shared preferences: protected void savePreferences() { // Create or retrieve the shared preference object. public static final String MYPREFS = “mySharedPreferences”; int mode = Activity.MODE_PRIVATE; SharedPreferences mySharedPreferences = getSharedPreferences(MYPREFS, mode); // Retrieve an editor to modify the shared preferences. SharedPreferences.Editor editor = mySharedPreferences.edit(); // Store new primitive types in the shared preferences object. editor.putBoolean(“isTrue”, true); editor.putFloat(“lastFloat”, 1f); editor.putInt(“wholeNumber”, 2); editor.putLong(“aNumber”, 3l); editor.putString(“textEntryValue”, “Not Empty”); // Commit the changes. editor.commit(); } Shared Preferences Чтение shared preferences: public void loadPreferences() { // Get the stored preferences public static final String MYPREFS = “mySharedPreferences”; int mode = Activity.MODE_PRIVATE; SharedPreferences mySharedPreferences = getSharedPreferences(MYPREFS, mode); // Retrieve the saved values. boolean isTrue = mySharedPreferences.getBoolean(“isTrue”, false); float lastFloat = mySharedPreferences.getFloat(“lastFloat”, 0f); int wholeNumber = mySharedPreferences.getInt(“wholeNumber”, 1); long aNumber = mySharedPreferences.getLong(“aNumber”, 0); String stringPreference; stringPreference = mySharedPreferences.getString(“textEntryValue”,“”); } Сохранение состояния активностей Каждая активность поддерживает внутри себя безымянный SharedPreferences объект, доступ к которому ограничен этой активностью. Его можно использовать для сохранения данных, специфичных для данной активности. protected void saveActivityPreferences() { // Create or retrieve the activity preferences object. SharedPreferences activityPreferences = getPreferences(Activity.MODE_PRIVATE); // Retrieve an editor to modify the shared preferences. SharedPreferences.Editor editor = activityPreferences.edit(); // Retrieve the View TextView myTextView = (TextView)findViewById(R.id.myTextView); // Store new primitive types in the shared preferences object. editor.putString(“currentTextValue”, myTextView.getText().toString()); // Commit changes. editor.commit(); } Сохранение состояния активностей Каждая активность поддерживает внутри себя объект Bundle, который автоматически передается качестве параметра в метод: onCreate(Bundle icicle) public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); TextView myTextView = (TextView)findViewById(R.id.myTextView); String text = “”; if (icicle != null && icicle.containsKey(TEXTVIEW_STATE_KEY)) { text = icicle.getString(TEXTVIEW_STATE_KEY); } myTextView.setText(text); } Сохранение состояния активностей Сохранять состяние активностей надо в методе onPause() public void onPause() { super.onPause(); SharedPreferences settings=getPreferences(0); SharedPreferences.Editor editor=settings.edit(); editor.putBoolean("cb_checked", cb.isChecked()); editor.commit(); } Восстанавливать в onResume() public void onResume() { super.onResume(); SharedPreferences settings=getPreferences(0); cb.setChecked(settings.getBoolean("cb_checked", false)); } Работа с файлами Файлы можно хранить: -в ресурсах -во внутреннем хранилище (видны только самому приложению) - во внешнем хранилище (виды извне) Файлы как статические ресурсы Хранятся в каталоге /res/raw Доступ из кода осуществляется через метод: Resources getResources() Далее полученный статический ресурс считвается как байтовый поток ввода. Внимание! Данный способ подразумевает, что файлы можно только читать, но нельзя модифицировать. InputStream in=getResources().openRawResource(R.raw.words); Файлы во внутреннем хранилище Открыть файл для чтения: public FileInputStream openFileInput (String name) Открыть файл для записи (создать если не существует): public FileOutputStream openFileOutput (String name, int mode) name – имя файла (без путей!!!) mode – режим записи: Context.MODE_PRIVATE - созданный файл будет доступен только приложению, создавшему его Context.MODE_APPEND - если файл существует, то информация дописывается в конец Файлы во внутреннем хранилище Полезные методы: File getFilesDir() – путь к внутреннему хранилищу. File getDir() – создает (или открывает) каталог внутреннего хранилища. boolean deleteFile() – удаляет файл из внутреннего хранилища. String[ ] fileList() – возвращает массив файлов, сохраненных данным приложением Файлы во внешнем хранилище Внешнее хранилище может физически находиться на SDкарте, а может во внутренней памяти телефона (зависит от модели) Файлы во внешнем хранилище public boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; Проверка доступности внешнего хранилища на запись } public boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; } Проверка доступности внешнего хранилища на чтение Файлы во внешнем хранилище Создание файла во внешнем хранилище: String root = Environment.getExternalStorageDirectory().toString(); File dir = new File (root + "/download"); dir.mkdirs(); File file = new File(dir, FILENAME_SD); try { FileOutputStream f = new FileOutputStream(file); PrintWriter pw = new PrintWriter(f); pw.println("Hi , How are you"); pw.flush(); pw.close(); f.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } Базы данных SQLite В платформу Android встроена полноценная реляционная СУБД SQLite (www.sqlite.org ) Создаваемые приложением базы данных хранятся в: /data/data/your.app.package/databases/your-db-name.db и доступны только создавшему их приложению. SQLite не поддерживает некоторые функции настольных СУБД: FOREIGN KEY, вложенные транзакции, RIGHT OUTER JOIN, FULL OUTER JOIN, etc. Базы данных SQLite Создание схемы базы данных (наследование от класса BaseColumns обеспечивает автоматическое добавление поля _ID, содержащего первичный ключ: import android.provider.BaseColumns; public class DBScheme { public static abstract class Vendors implements BaseColumns { public static final String TABLE_NAME = "vendors"; public static final String COLUMN_NAME_ID = "vendor_id"; public static final String COLUMN_NAME_TITLE = "vendor_name"; public static final String COLUMN_NAME_COUNTRY = "vendor_country"; } public static abstract class Models implements BaseColumns { public static final String TABLE_NAME = "models"; public static final String COLUMN_NAME_ID = "model_id"; public static final String COLUMN_NAME_TUTLE = "model_name"; public static final String COLUMN_NAME_VENDOR_ID = "vendor_id"; } } Базы данных SQLite Создание базы данных: public static final String DATABASE_NAME = “Devices.db"; SQLiteDatabase db = openOrCreateDatabase (DATABASE_NAME, null); Создание таблиц и индексов: private static final String TEXT_TYPE = " TEXT"; private static final String COMMA_SEP = ","; private static final String SQL_CREATE_VENDORS = "CREATE TABLE " + DBScheme.Vendors.TABLE_NAME + " (" + DBScheme.Vendors._ID + " INTEGER PRIMARY KEY," + DBScheme.Vendors.COLUMN_NAME_ID + TEXT_TYPE + COMMA_SEP + DBScheme.Vendors.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + DBScheme.Vendors.COLUMN_NAME_COUNTRY + TEXT_TYPE + COMMA_SEP + " )"; db.execSQL(SQL_CREATE_VENDORS ); db.execSQL("CREATE INDEX vendorsByNameIdx “+ "ON vendors (vendor_name)"); Базы данных SQLite Запись информации в таблицу: 1-й способ: db.execSQL("INSERT INTO vendors (name, country)"+ "VALUES (‘Samsung', South Korea)"); Так удобно делать если запросы не меняются в runtime. 2-й способ: ContentValues values = new ContentValues(); values.put(DBScheme.Vendors.COLUMN_NAME_TITLE, “Samsung”); values.put(DBScheme.Vendors.COLUMN_NAME_COUNTRY, “South Korea”); long newRowId; newRowId = db.insert( DBScheme.Vendors.TABLE_NAME, null, values); Этот способ удобнее если запросы формируются динамически Базы данных SQLite Чтение информации из таблицы: 1-й способ: String[ ] parms={“Samsung"}; Cursor result = db.rawQuery ("SELECT name, country FROM vendors WHERE name=?“, parms); Так удобно делать если запросы не меняются в runtime. 2-й способ: String[ ] columns={“name“, “country”}; String[ ] parms={“Samsung"}; Cursor result=db.query(“vendors", columns, "name=?“, parms, null, null, null); Последние 3 параметра: group by, having by, order by соответственно Этот способ удобнее если запросы формируются динамически Базы данных SQLite Работа с полученным курсором: Cursor result= db.rawQuery("SELECT id, name, country FROM vendors"); cursor.moveToFirst(); while (!result.isAfterLast()) { int id=result.getInt(0); String name=result.getString(1); String country=result.getString(2); // do something useful with these result.next(); } result.close(); Важно! Не забывайте закрывать курсор после использования Базы данных SQLite Закрыть БД: db.close() Удалить БД: deleteDatabase() Что такое Content Provider Content Provider – механизм, позволяющий нескольким приложениям совместно пользоваться одним источником данных (чаще всего БД, хотя и необязательно) Content Provider абстрагирует для приложения конкретные детали хранилища данных, позволяя обращаться к хранилищу унифицированным образом, используя Uniform Resource Identifier (http://tools.ietf.org/html/rfc3986) Большинство стандартных приложений предоставляют доступ к своим внутренним базам данных через созданные ими Content Providers. Использование Content Provider Доступ к Content Provider осуществляется через объект ContentResolver класса Context. ContentResolver cr = getContentResolver(); Обычно провайдеры передают ссылку на хранилище данных через CONTENT_URI property. При этом существуют две формы URI: - для запроса всего массива данных, совпадает с CONTENT_URI - для запроса конкретной записи CONTENT_URI/RowID Использование Content Provider Пример: content://mleykin.hse.AddressBook/contacts/7 content:// - стандартное начало для адреса провайдера. mleykin.hse.AddressBook– authority. Определяет провайдера (аналогия с БД - имя базы). contacts – это path. Определяет, какие данные от провайдера нужны (таблица БД). 7 – это ID. Какая конкретно запись нужна (ID записи) CONTENT_URI = content://mleykin.hse.AddressBook/contacts Запрос данных через Content Provider Запрос данных через объект ContentResolver осуществляется с помощью метода query(). public final Cursor query (Uri uri, String[ ] projection, String selection, String[ ] selectionArgs, String sortOrder) Метод принимает: • uri – content URI • projection - массив столбцов таблицы, которые нужно получить • selection - условие where со занками ? в качестве параметров • selectionArgs – аргументы, подставляемые в условие • sortOrder – строка, задающая порядок сортировки результата запроса Запрос данных через Content Provider // Return all rows Cursor allRows = getContentResolver().query(MyProvider.CONTENT_URI, null, null, null, null); // Return all columns for rows where column 3 equals a set value and the rows are ordered by column 5. String where = KEY_COL3 + “=” + requiredValue; String order = KEY_COL5; Cursor someRows = getContentResolver().query(MyProvider.CONTENT_URI, null, where, null, order); Вставка через Content Provider 1-й способ: // Create a new row of values to insert. ContentValues newValues = new ContentValues(); // Assign values for each row. newValues.put(COLUMN_NAME, newValue); … getContentResolver().insert(MyProvider.CONTENT_URI, newValues); 2-й способ: // Create a new row of values to insert. ContentValues[] valueArray = new ContentValues[5]; // TODO: Create an array of new rows int count = getContentResolver().bulkInsert(MyProvider.CONTENT_URI, valueArray); Удаление через Content Provider // Remove a specific row. getContentResolver().delete(myRowUri, null, null); // Remove the first five rows. String where = “_id < 5”; getContentResolver().delete(MyProvider.CONTENT_URI, where, null); Обновление через Content Provider // Create a new row of values to insert. ContentValues newValues = new ContentValues(); // Create a replacement map, specifying which columns you want to // update, and what values to assign to each of them. newValues.put(COLUMN_NAME, newValue); // Apply to the first 5 rows. String where = “_id < 5”; getContentResolver().update(MyProvider.CONTENT_URI, newValues, where, null); Работа с файлами через Content Provider Файлы представляются через URI. Чтобы добавить файл в ContentProvider или получить доступ к имеющемуся файлу: try { // Open an output stream using the new row’s URI. OutputStream outStream = getContentResolver().openOutputStream(uri); // Compress your bitmap and save it into your provider. sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream); } catch (FileNotFoundException e) { } Нативные провайдеры - Browser – доступ к bookmarks, browser history, web searches. - CallLog – доступ к call history - Contacts – доступ к базе контактов - MediaStore – доступ к мультимедиа-ресурсам - Settings – доступ к настройкам устройства Внимание! Чтобы работать с нативными провадйерами чаще всего надо запросить у платформы разрешения, например для контактов: <uses-permission android:name="android.permission.READ_CONTACTS" /> Нативные провайдеры // Get a cursor over every contact. Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI, null, null, null, null); // Let the activity manage the cursor lifecycle. startManagingCursor(cursor); // Use the convenience properties to get the index of the columns int nameIdx = cursor.getColumnIndexOrThrow(Contacts.NAME); int phoneIdx = cursor. getColumnIndexOrThrow(Contacts.NUMBER); String[] result = new String[cursor.getCount()]; if (cursor.moveToFirst()) do { // Extract the name. String name = cursor.getString(nameIdx); // Extract the phone number. String phone = cursor.getString(phoneIdx); result[cursor.getPosition()] = name + “ (“ + phone + “)”; } while(cursor.moveToNext()); Создание собственного Content Provider Чтобы создать собственный ContentProvider необходимо унаследоваться от абстрактного класса ContentProvider и переопределить метод onCreate() public class MyProvider extends ContentProvider { @Override public boolean onCreate() { // TODO: Construct the underlying database. return true; } } Создание собственного Content Provider Также необходимо создать внутри класса переменную CONTENT_URI, по которой будет осуществляться доступ к ContentProvider. Т.к. у каждого провайдера д.б. уникальный URI, удобно использовать для URI имя пакета\приложения\вендора: content://com.<CompanyName>.provider.<ApplicationName>/<DataPath> Создание собственного Content Provider Создаваемый ContentProvider должен поддерживать две формы обращения: для получения всех данных и для получения одной записи. Проще всего это сделать так: public class MyProvider extends ContentProvider { private static final String myURI = “content://com.paad.provider.myapp/items”; public static final Uri CONTENT_URI = Uri.parse(myURI); public boolean onCreate() { // TODO: Construct the underlying database. return true; } // Create the constants used to differentiate between the different URI requests. private static final int ALLROWS = 1; private static final int SINGLE_ROW = 2; private static final UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(“com.paad.provider.myApp”, “items”, ALLROWS); uriMatcher.addURI(“com.paad.provider.myApp”, “items/#”, SINGLE_ROW); } } Создание собственного Content Provider Переопределяем методы query(), insert(), delete(), update() public class MyProvider extends ContentProvider { public boolean onCreate() { } public String getType(Uri url) { } public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { } public Uri insert(Uri _url, ContentValues _initialValues) { } public int delete(Uri url, String where, String[] whereArgs) { } public int update(Uri url, ContentValues values, String where, String[] wArgs) { } } Создание собственного Content Provider Переопределяем query() @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) { // If this is a row query, limit the result set to the passed in row. switch (uriMatcher.match(uri)) { case SINGLE_ROW : // TODO: Modify selection based on row id, where: // rowNumber = uri.getPathSegments().get(1)); } return null; } Создание собственного Content Provider Переопределяем insert() @Override public Uri insert(Uri _uri, ContentValues _initialValues) { long rowID = [ ... Add a new item ... ] // Return a URI to the newly added item. if (rowID > 0) { return ContentUris.withAppendedId(CONTENT_URI, rowID); } throw new SQLException(“Failed to add new item into “ + _uri); } Создание собственного Content Provider Переопределяем delete() @Override public int delete(Uri uri, String where, String[] whereArgs) { switch (uriMatcher.match(uri)) { case ALLROWS: case SINGLE_ROW: default: throw new IllegalArgumentException(“Unsupported URI:” + uri); } } Создание собственного Content Provider Также надо определить MIME тип данных, с которым работает данный провайдер: @Override public String getType(Uri _uri) { switch (uriMatcher.match(_uri)) { case ALLROWS: return “vnd.paad.cursor.dir/myprovidercontent”; case SINGLE_ROW: return “vnd.paad.cursor.item/myprovidercontent”; default: throw new IllegalArgumentException(“Unsupported URI: “ + _uri); } } Создание собственного Content Provider После создания надо зарегистрировать созданный ContentProvider в AndroidManifest.xml <provider android:name=”MyProvider” android:authorities=”com.paad.provider.myapp”/>