Pull to refresh

Comments 31

Спасибо, интересно. Перечитаю повнимательнее на досуге. Но я пока совсем отказался от SQLiteOpenHelper — оказалось, что в нем невозможно создать базу на SD карте. Подробностей сейчас не вспомню, но это было как-то связано с методом getDatabasePath. Теоретически, то, что он возвращает, должно использоваться как путь к базе. На практике SQLiteOpenHelper иногда использует его, а иногда обходит стороной — зависит от того, открываем ли мы базу на чтение или запись, существует ли она уже и т.д. Помню, что смотрел исходники и там было ясно видно, что путь используется криво. Если интересно, могу потом посмотреть еще раз и рассказать.
Кажется, кое-что нашел. SQLiteOpenHelper.getWritableDatabase вызывает Context.openOrCreateDatabase, который, в свою очередь, использует Context.validateFilePath, чтобы получить полный путь к файлу. Там используется приватный метод Context.getDatabasesDir, переопределить который нельзя — приехали. База будет создана в стандартной директории.

А вот если мы вызвали SQLiteOpenHelper.getReadableDatabase, сначала он попытается вызвать все тот же getWritableDatabase. Но если это не получится, то он пойдет в обход Context.openOrCreateDatabase — сам вызовет Context.getDatabasePath (вот его-то мы можем подправить) и сам откроет нужную базу. Этот способ нас бы устроил, если бы он использовался всегда. Но увы. :(

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

Вам, 2+ и с вашего позволения добавлю сказанное вами в пост:)
На счет мысли отказаться от SQLiteOpenHelper очень любопытно. Надо подумать:)

Оформите целостное решение, добавлю в статью. Будет полезно.
А что там такого? Конечно, кое-какие удобства теряются. Но в целом никаких проблем не вижу. Понадобилась база — открыли (там, где нужно, а не где получится). Не нужна — закрыли. Ну да, положил все это в специальный классик, у которого есть методы-аналоги getWritable/ReadableDatabase. Апгрейдить базу мне не нужно, так что вполне могу обойтись без умного хелпера.
Ну мне сложно судить, я не пробовал, но по идеи мы будем просто сами пути нужные для базы передавать куда нужно и все.

Я и не говорю, что это сложно.
Ребят, может вопрос и не в тему, но всё же…
Я далёк от разработки под Android, но как я понял SQLiteOpenHelper это часть API Android SDK. А это открытый проект, раз уж вы полезли в код, то почему бы хотя бы не создать тикет в багтрекере дабы в следующих версиях не натыкаться на такую засаду? Этот же класс доступен аж с API Level 1.

А все указанные мною ошибки уже содержат issues на гугл коде, ссылка на один в топике есть.
Остальное нам не под силу:)
Но мысль правильная, спасибо!
Тоже были мысли написать подобную статью. Спасибо что опередили. Один очень важный момент с которым мне приходилось столкнуться похож на ваше описание ошибки №1. Сейчас уже не вспомню детали но суть в том чтобы никогда и нигде не хранить ссылку на базу данных SQLiteDatabase dbRead = getReadableDatabase(), а работать на прямую с методами getReadableDatabase() и getWritebleDatabase(), а для закрытия базы использовать метод SQLiteOpenHelper.close().
Пожалуйста, всегда рад помочь людям.

Кроме того это накипело уже. Надоело столько лагов хватать и хочется найти уже решение, которое обходит многие(пускай не все) эти ошибки.

+1:)
А я правильно понял мысль? Ты предлагаешь постоянно вызывать методы помощника getReadableDatabase() и getWritebleDatabase(), получая ссылку и тут же делать операци к базе?

То бишь, например: dbHelper.getWritebleDatabase().insertData(list); dbHelper.getWritebleDatabase().getData(id);
да, только для операций с базой я бы предпочел обертку в виде метода для dbHelper, либо класса обертки над dbHelper.
Я понял. Такой принцип используется в туториале(ссылка в топике).
Хорошая статья, спасибо. вообще нельзя запускать ресурсоемкие вещи на ui потоке, т.к. чревато force close.
По поводу импорта собственной БД из sql в ресурсах — вполне нормальная практика, у вас есть дамп базы в виде sql скрипта, запуск и выполнение которого будет легче и быстрее парсинга того же ресурсного xml
Да, но если данных много то либо понадобится тулза для того, чтобы сгенерировать такой sql-скрипт и не факт, что найдется(я пока не искал, но тем инструментом, что я пользуюсь такое сделать нельзя), либо писать самому парсер.

А вообще, я с Вами согласен, xml подольше будет, правда тоже еще зависит от используемого способа парсинга.
Да ну? а разные там SQLyog которые коннектятся к примеру к mysql бд, и её можно оттуда экспортить сразу в скрипт. причем импорт/экспорт потом нормально работают. я пока еще не пользовался такой тулзой которая не умеет делать экспорт базы =)

Если xml маленький то пожалуйста, но вы представьте xml-ину в 2-3 мега?
Не будем разводить спор.
SAX по идеи быстр, хоть я и не пробовал на больших объемах. А если уж данных так много, то лучше подкачивать их походу из сети, пользователи не любят большие apk-и.
У нас sqlite база, а хорошие тулзы всегда платные. Вот они справятся без проблем.
=)
я и не спорю, просто меня в свое время устраивал xml ипорт до определенного момента)
кстати о больших данных, ведь апк при сборке ужимается. а xml даже в 30 мег обычно жмется в килобайт 200… другое дело когда развернется приложение на девайсе…

а вообще если данных много, то да, имеет смысл подгружать их с сервера + фрагментировать под пользователя, и остальные данные грузить по мере надобности
смотря куда положите xml, в assets ничего не будет сжиматься, только в res(кроме подпапки raw).

Про подгрузку я тоже самое говорил:)
Добавлю небольшой код, позволяет накатывать инкрементальные изменения в БД на любую версию программы:

abstract public class DBHelper extends SQLiteOpenHelper {

private SQLiteDatabase database = null;
private int version = -1;

public DBHelper(Context context, String path, int version) {
super(context, path, null, version);
this.version = version;
}

public boolean open() {
try {
SQLiteDatabase db = this.getWritableDatabase();
if (db != null) {
return true;
}
} catch (Exception e) {
Log.e(TAG, "Error opening DB", e);
}
return false;
}

@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, version);
}

abstract public void migrate(SQLiteDatabase db, int version);

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (int i = oldVersion+1; i <= newVersion; i++) {
migrate(db, i);
}
}

@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
this.database = db;
}

public SQLiteDatabase getDatabase() {
return database;
}
}



реализация migrate — обычный switch c DDL коммандами.
При изменении БД — увеличиваем версию в конструкторе и добавляем в migrate новый case в switch. Класс заботится, чтобы все DDL комманды были последовательно вызваны
Ну вот о чем я и говорил. См мой коммент выше. Отдавая прямую ссылку на БД вы рискуете стать жертвой непредсказуемых падений приложения.
Нет, но обязательно посмотрю. Из сэмплов у меня есть вот такая ссылочка — code.google.com/p/krvarma-android-samples/

Еще было бы лучше, если бы Вы уточнили, что имеете ввиду.
О, пасиба за линк, довольно много чего вкусного.

Я имел ввиду один из способов работы с БД, а именно использование сторонней библиотеки в которой уже почти все есть =). Конечно, лучше наверно сразу использовать какой-нибудь ormlite, но все же.
Да, я думал об этом. Впринципе вариант, но либа это все равно обертка. Просто удобная. Важнее знать как правильно работать с тем, что есть.

А так да, можно.
Реквестую статью по правильной работе с БД на SD-карте(картах, ибо есть китайские девайсы с двумя картами).

Хотелось бы увидеть описание правильного создания БД, причем для разных API Level'ов — не секрет что разрабочики приложений часто плодят в корне SD-карты кучу папок, по папке на приложение, хотя для таких целей есть общий путь типа /корень-SD-карты/Android/data/имя-приложения/(databases|cache|etc).

Как получить этот путь для разных API — вот интересный вопрос. Если для API8 есть getExternalFilesDir, то для API1 есть только getExternalStorageDirectory которая возвращает только путь до корня карты. И, если писать приложение для Андроид 1.6, то приходится изворачиваться.

Еще бы хотелось чтобы были освещены моменты для случаев, когда карта недоступна (девайс подключен по USB, пользователь размонтировал карту и т.п.)
Я думаю, что не имеет смысл поддерживать такой низкий API Level. Да, желательно, но по мне так это уже перебор. Посмотрите статистику андроид-девайсов на developer.android.com, которая обновляется достаточно часто. Когда я смотрел больше половины всех девайсов работали на Андроид 2.2, большой % был у 2.1. Подозреваю, что сейчас % сместится в сторону 2.3.

А так, да, я уже в своих комментариях как-то обращал на это внимание, на то что Вы указали(к примеру Galaxy тоже по суте 2 карты содержит) и как будет мной сформирован более целостный подход по работе с БД, то обновится данная статья, либо напишу новую.

Следите за обновлениями. И спасибо за коммент, все учту:)
Да, если верить developer.android.com/resources/dashboard/platform-versions.html сейчас больше всего устройств с Андроид 2.2 (API8) — почти 56%

Устройств с версией 1.6 (API4) всего 2%. Это устройства хозяева которых:
— либо не знают как прошится на новую версию (или боятся это делать из риска сломать устройство по неосторожности)
— либо не могут, т.к. уже прошились с 1.5 до 1.6 (а более новой версии прошивки производитель устройства не выпустил и уже не выпустит).
Можно, конечно, на них забить при разработке приложений.

Устройства с 2.1 где-то посередине — чуть больше 15%. Это API7.
Забивать на этих пользователей уже совсем не хочется. Их довольно много. И большинство из них тоже «застряло», уже обновившись с 1.6 до 2.1 (и производители многих таких устройств тоже не выпустят обновлений даже до 2.2). И менять свои устройства на новые они будут еще года 2-3, а то и дольше.

А в API7 как я писал чуть выше, нет функции getExternalFilesDir.
Что делать?
Sign up to leave a comment.

Articles