2012-05-29 1 views
10

У меня серьезная проблема с базой данных android sqlite и одновременной записью. Для лучшего объяснения, я приведу вам пример реальной жизни:Одновременная запись в базу данных Android (из нескольких служб)?

У меня виджет рабочего стола, где я показываю список предметов из моей базы данных (а на заднем плане у меня есть DataService, который через регулярные промежутки времени собирает свежие данные с моего удаленного сервера и обновить мою базу данных). Итак - когда я нажимаю на какой-либо элемент в списке, мне нужно обновить элемент clicked (= do write operation) в базе данных. НО когда я нажимаю на деталь точно в момент, когда DataService обновляет свежие данные в моей базе данных, это, конечно, регистрирует ошибку, как это:

android.database.sqlite.SQLiteException: error code 5: database is locked 

Обычно его трудно имитировать, но если и график DataService баллотироваться пример каждые 10 секунд (только для демонстрации), вы можете очень легко имитировать эту ошибку.

И мой вопрос в том, как с этим справиться? Я прочитал в документах, что если в одно время будут два события записи, будет выполнено только первое, второе будет зарегистрировано как ошибка. Звучит странно, должны быть и другие варианты, например, вторая запись будет ждать до первого написания. Или, может быть, другое решение? Попытка читать документы, но кажется, что этот элемент не очень хорошо освещен в документах Google ... Почти у каждой информации, которую я нашел, я нашел на других официальных страницах.

PS: Это мой сокращенный вариант моего класса DBHelper:

public class DBHelper extends SQLiteOpenHelper { 

    private static final String TABLE_NEWS = "News";  
    private static final String COL_ID = "id"; 
    private static final String COL_TITLE = "title"; 
    private static final String COL_ALERT = "alert"; 

    public DBHelper(Context context) { 
     super(context, "MY_DB_NAME", null, 1); 
    } 

    @Override 
    public void onCreate(SQLiteDatabase db) { 
     db.execSQL("CREATE TABLE " + TABLE_NEWS + "(" + COL_ID + " TEXT PRIMARY KEY," + COL_TITLE + " TEXT," + COL_ALERT + " INTEGER" + ")"); 
    } 

    @Override 
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
     db.execSQL("DROP TABLE IF EXISTS " + TABLE_NEWS); 
     onCreate(db); 
    } 

    public void addRecords(ArrayList<NewsItem> items) { 
     SQLiteDatabase db = this.getWritableDatabase();  
     for (int i = 0; i < items.size(); i++) { 
      NewsItem item = items.get(i);  
      ContentValues values = new ContentValues(); 
      values.put(COL_ID, item.getId()); 
      values.put(COL_TITLE, item.getTitle()); 
      values.put(COL_ALERT, item.getAlertMe());  
      db.insert(TABLE_NEWS, null, values); 
     }  
     db.close(); 
    } 

    public int updateRecord(NewsItem item) { 
     SQLiteDatabase db = this.getWritableDatabase();  
     ContentValues values = new ContentValues(); 
     values.put(COL_ALERT_ME, item.getAlertMe()); 
     int updated = db.update(TABLE_NEWS, values, COL_ID + " = ?", new String[] { item.getId() }); 
     db.close();  
     return updated; 
    } 
} 
+0

Возможно, вы открываете и закрываете несколько соединений с базами данных в разных потоках. Это не очень хорошо. Вы должны открыть одно соединение с базой данных. – Sajmon

+0

thats true, я делаю что-то вроде этого в моих сервисах: DBHelper dbHelper = new DBHelper (this); и т. д. ... но как создать только один экземпляр - что произойдет, если мой процесс будет уничтожен? PS: пожалуйста, нажмите «Ответ на ваш запрос», - я могу написать вам прямо, (если кто-то еще комментирует здесь, это будет многопользовательский разговор) – qkx

+0

обычно добавить синхронизированный для каждого метода, связан с db open/close. – Richard

ответ

21

Вы должны использовать один SQLiteDatabase объект, между всеми потоками (и их хостинг компонентов), чтобы получить безопасность потока. Сделайте свой DBHelper синглом, или используйте ContentProvider, чтобы добиться этого эффекта.

+0

thanx, вот именно то, что я хотел услышать - все возможные варианты :) Я выбираю сначала тест singleton (более простой), и он работает как шарм (пока), поэтому я закрываю вопрос. – qkx

12

ContentProvider был создан по этой причине. Вы можете вызвать его из нескольких потоков для операций insert/update/delete.

http://developer.android.com/reference/android/content/ContentProvider.html

Многие люди чувствуют, что вы только должны хотеть использовать ContentProvider, если вы хотите обмениваться данными. Это большое преимущество ContentProvider, но это не единственное преимущество. Главное преимущество заключается в том, что при использовании ContentProvider Android будет управлять соединениями с базой данных для вас.

Это хороший учебник по контент-провайдеров

http://www.vogella.com/articles/AndroidSQLite/article.html

Примечание: Хотя JavaDoc для ContentProvider делает состояние, что требуется для того, чтобы обмениваться данными, это не значит, что она должна только быть используется для обмена данными. В документации по основам применения, то есть это, чтобы сказать о ContentProviders, а ..

Контент-провайдеры также полезны для чтения и записи данных, которые приватных вашего приложения, а не общий. Например, пример приложения Note Pad использует поставщик контента для сохранения заметок.

http://developer.android.com/guide/topics/fundamentals.html#lcycles

+0

Возможно, я такой же, как и многие люди :), но когда я читаю документы, «поставщик контента необходим только в том случае, если вам нужно обмениваться данными между несколькими приложениями», что не является моим делом. Мне не нужно делиться соединением db между приложениями, только между службами. Но у меня действительно нет большого опыта в Android, так что это лучший способ справиться с такими проблемами? – qkx

+0

Опять же, это НЕОБХОДИМО, если вы хотите обмениваться данными между приложениями, но это, безусловно, единственная причина для его использования. Если вы читаете javadoc для 'insert()' (и других методов), то вы увидите, что он четко заявляет, что поддерживает несколько потоков. Вы можете создать свой собственный менеджер потокового db, или вы можете просто использовать контент-провайдера и позволить выполнять эту работу. Мне лично не нравится использовать контент-провайдеры, но если бы я работал над несколькими потоками, я бы подумал об этом. – stuckless

+0

Но в вашем случае, если вы не хотите использовать ContentProvider, возможно, сохраните экземпляр базы данных, пригодный для записи, в подклассе Application. Вы можете открыть его в onStart() и закрыть его в onTerminate(). Ключ должен гарантировать, что у вас никогда не будет более одного экземпляра доступной для записи базы данных в любой момент времени, а жизненный цикл приложения - хорошее место для обеспечения этого. – stuckless