Я пробую образец с библиотекой сохранения состояния комнаты . Я создал Сущность:
@Entity
public class Agent {
@PrimaryKey
public String guid;
public String name;
public String email;
public String password;
public String phone;
public String licence;
}
Создал класс DAO:
@Dao
public interface AgentDao {
@Query("SELECT COUNT(*) FROM Agent where email = :email OR phone = :phone OR licence = :licence")
int agentsCount(String email, String phone, String licence);
@Insert
void insertAgent(Agent agent);
}
Создал класс базы данных:
@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AgentDao agentDao();
}
Открытая база данных с использованием подкласса ниже в Котлине:
class MyApp : Application() {
companion object DatabaseSetup {
var database: AppDatabase? = null
}
override fun onCreate() {
super.onCreate()
MyApp.database = Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").build()
}
}
В моей деятельности реализована функция ниже:
void signUpAction(View view) {
String email = editTextEmail.getText().toString();
String phone = editTextPhone.getText().toString();
String license = editTextLicence.getText().toString();
AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
//1: Check if agent already exists
int agentsCount = agentDao.agentsCount(email, phone, license);
if (agentsCount > 0) {
//2: If it already exists then prompt user
Toast.makeText(this, "Agent already exists!", Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(this, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
onBackPressed();
}
}
К сожалению, при выполнении вышеуказанного метода происходит сбой с трассировкой стека ниже:
FATAL EXCEPTION: main
Process: com.example.me.MyApp, PID: 31592
java.lang.IllegalStateException: Could not execute method for android:onClick
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
at android.view.View.performClick(View.java:5612)
at android.view.View$PerformClick.run(View.java:22288)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
at android.view.View.performClick(View.java:5612)
at android.view.View$PerformClick.run(View.java:22288)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:137)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:165)
at com.example.me.MyApp.RoomDb.Dao.AgentDao_Impl.agentsCount(AgentDao_Impl.java:94)
at com.example.me.MyApp.View.SignUpActivity.signUpAction(SignUpActivity.java:58)
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
at android.view.View.performClick(View.java:5612)
at android.view.View$PerformClick.run(View.java:22288)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6123)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
Похоже, эта проблема связана с выполнением операции db в основном потоке. Однако пример кода теста, приведенный в приведенной выше ссылке, не запускается в отдельном потоке:
@Test
public void writeUserAndReadInList() throws Exception {
User user = TestUtil.createUser(3);
user.setName("george");
mUserDao.insert(user);
List<User> byName = mUserDao.findUsersByName("george");
assertThat(byName.get(0), equalTo(user));
}
Я что-нибудь здесь упускаю? Как я могу заставить его работать без сбоев? Пожалуйста, предложите.
android
crash
kotlin
android-studio-3.0
android-room
Devarshi
источник
источник
Ответы:
Как сказал Дейл, доступ к базе данных при блокировке пользовательского интерфейса в основном потоке является ошибкой.
Создайте статический вложенный класс (для предотвращения утечки памяти) в своей деятельности, расширяющей AsyncTask.
Или вы можете создать последний класс в отдельном файле.
Затем выполните его в методе signUpAction (View view):
В некоторых случаях вы также можете захотеть сохранить ссылку на AgentAsyncTask в своей деятельности, чтобы вы могли отменить ее, когда действие будет уничтожено. Но вам придется самому прерывать любые транзакции.
Кроме того, ваш вопрос о тестовом примере Google ... Они заявляют на этой веб-странице:
Нет активности, нет пользовательского интерфейса.
--РЕДАКТИРОВАТЬ--
Для людей, интересующихся ... У вас есть другие варианты. Я рекомендую ознакомиться с новыми компонентами ViewModel и LiveData. LiveData отлично работает с Room. https://developer.android.com/topic/libraries/architecture/livedata.html
Другой вариант - это RxJava / RxAndroid. Более мощный, но более сложный, чем LiveData. https://github.com/ReactiveX/RxJava
- РЕДАКТИРОВАНИЕ 2 -
Поскольку многие люди могут столкнуться с этим ответом ... Лучший вариант в настоящее время, вообще говоря, - это Kotlin Coroutines. Room теперь поддерживает его напрямую (в настоящее время находится в стадии бета-тестирования). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01
источник
Это не рекомендуется, но вы можете получить доступ к базе данных в основном потоке с помощью
allowMainThreadQueries()
источник
allowMainThreadQueries()
построитель, потому что он потенциально может заблокировать пользовательский интерфейс на длительный период времени. Асинхронные запросы (запросы, которые возвращаютLiveData
или RxJavaFlowable
) исключаются из этого правила, поскольку они асинхронно запускают запрос в фоновом потоке, когда это необходимо.Kotlin Coroutines (ясные и лаконичные)
AsyncTask действительно неуклюжий. Сопрограммы - более чистая альтернатива (просто добавьте пару ключевых слов, и ваш код синхронизации станет асинхронным).
Зависимости (добавляет области сопрограмм для компонентов арки):
- Обновление:
08-May-2019: номер 2,1 теперь поддерживает
suspend
13-Sep-2019: Обновленный использовать Архитектура компонентов сферы
источник
@Query abstract suspend fun count()
использованием ключевого слова suspend? Не могли бы вы изучить аналогичный вопрос: stackoverflow.com/questions/48694449/…@Query
функцию без приостановки . Когда я добавляю ключевое слово suspend во внутренний@Query
метод, он действительно не компилируется. Похоже, что это умная штука под капотом для приостановки и столкновения комнат (как вы упомянули в своем другом вопросе, скомпилированная версия приостановки возвращает продолжение, с которым Room не может справиться).launch
ключевых слов больше, запускают с областью, например,GlobalScope.launch
Для всех RxJava или RxAndroid или RxKotlin любителей там ,
источник
override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }
там , гдеapplySchedulers()
я только что делаюfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
IntentService#onHandleIntent
потому что этот метод выполняется в рабочем потоке, поэтому вам не понадобится какой-либо механизм потоковой передачи для выполнения операции с базой данных Room,Вы не можете запустить его в основном потоке, вместо этого используйте обработчики, асинхронные или рабочие потоки. Пример кода доступен здесь, а статью о библиотеке комнат можно прочитать здесь: Библиотека комнат Android
Если вы хотите запустить его в основном потоке, что не является предпочтительным способом.
Вы можете использовать этот метод для достижения в основном потоке
Room.inMemoryDatabaseBuilder()
источник
С lambda его легко запустить с помощью AsyncTask
источник
С библиотекой Jetbrains Anko вы можете использовать метод doAsync {..} для автоматического выполнения вызовов базы данных. Это решает проблему многословия, которая у вас, казалось, возникла с ответом mcastro.
Пример использования:
Я часто использую это для вставок и обновлений, однако для избранных запросов я рекомендую использовать рабочий процесс RX.
источник
Просто выполняйте операции с базой данных в отдельном потоке. Вот так (Котлин):
источник
Вы должны выполнить запрос в фоновом режиме. Простым способом может быть использование Executors :
источник
Следует использовать элегантное решение RxJava / Kotlin
Completable.fromCallable
, которое даст вам Observable, который не возвращает значение, но может наблюдать и подписываться в другом потоке.Или в Котлине:
Вы можете наблюдать и подписываться, как обычно:
источник
Вы можете разрешить доступ к базе данных в основном потоке, но только в целях отладки, вы не должны этого делать в производственной среде.
Вот в чем причина.
Примечание. Room не поддерживает доступ к базе данных в основном потоке, если вы не вызвали allowMainThreadQueries () в построителе, потому что это может заблокировать пользовательский интерфейс на длительный период времени. Асинхронные запросы - запросы, возвращающие экземпляры LiveData или Flowable - не подпадают под действие этого правила, поскольку они асинхронно запускают запрос в фоновом потоке, когда это необходимо.
источник
Просто вы можете использовать этот код для его решения:
Или в лямбде вы можете использовать этот код:
Вы можете заменить
appDb.daoAccess().someJobes()
своим кодом;источник
Поскольку asyncTask устарели, мы можем использовать службу исполнителя. ИЛИ вы также можете использовать ViewModel с LiveData, как описано в других ответах.
Для использования службы исполнителя вы можете использовать что-то вроде ниже.
Используется основной цикл, поэтому вы можете получить доступ к элементу пользовательского интерфейса из
onFetchDataSuccess
обратного вызова.источник
Сообщение об ошибке,
Довольно информативен и точен. Вопрос в том, как избежать доступа к базе данных в основном потоке. Это огромная тема, но для начала прочтите об AsyncTask (нажмите здесь)
-----РЕДАКТИРОВАТЬ----------
Я вижу, у вас проблемы при запуске модульного теста. У вас есть несколько способов исправить это:
Запустите тест непосредственно на машине разработки, а не на устройстве Android (или эмуляторе). Это работает для тестов, которые ориентированы на базу данных и не заботятся о том, запущены ли они на устройстве.
Используйте аннотацию
@RunWith(AndroidJUnit4.class)
для запуска теста на устройстве Android, но не в действии с пользовательским интерфейсом. Более подробную информацию об этом можно найти в этом руководстве.источник
Если вам удобнее использовать задачу Async :
источник
Обновление: я также получил это сообщение, когда пытался создать запрос с использованием @RawQuery и SupportSQLiteQuery внутри DAO.
Решение: создайте запрос внутри ViewModel и передайте его в DAO.
Или...
Вы не должны обращаться к базе данных непосредственно в основном потоке, например:
Вы должны использовать AsyncTask для операций обновления, добавления и удаления.
Пример:
Если вы используете LiveData для выбранных операций, вам не нужна AsyncTask.
источник
Для быстрых запросов вы можете оставить место для выполнения в потоке пользовательского интерфейса.
В моем случае мне пришлось выяснить, существует ли в базе данных выбранный пользователь в списке. Если нет, то создайте пользователя и начните другое действие.
источник
Вы можете использовать Future и Callable. Таким образом, вам не потребуется писать длинную асинтаксическую задачу и вы можете выполнять свои запросы без добавления allowMainThreadQueries ().
Мой запрос дао: -
Мой метод репозитория: -
источник
allowMainThreadQueries()
. Основная тема по-прежнему заблокирована в обоих случаяхНа мой взгляд, правильное решение - делегировать запрос потоку ввода-вывода с помощью RxJava.
У меня есть пример решения аналогичной проблемы, с которой я только что столкнулся.
И если мы хотим обобщить решение:
источник