Android Room - простой запрос выбора - нет доступа к базе данных в основном потоке

Я пробую образец с Библиотекой сохраняемости помещения. Я создал Сущность:

@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));
    }

Я что-то здесь упускаю? Как я могу заставить его работать без сбоев? Пожалуйста, предложите.


person Devarshi    schedule 24.05.2017    source источник
comment
Хотя эта статья написана для Kotlin, она очень хорошо объясняет основную проблему!   -  person Peter Lehnhardt    schedule 28.09.2019
comment
Взгляните на stackoverflow.com / questions / 58532832 /   -  person nnyerges    schedule 24.10.2019
comment
Посмотри на этот ответ. Этот ответ работает для меня stackoverflow.com/a/51720501/7655085   -  person Somen Tushir    schedule 12.04.2020


Ответы (19)


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

- РЕДАКТИРОВАНИЕ 2 -

Поскольку многие люди могут столкнуться с этим ответом ... Лучший вариант в настоящее время, вообще говоря, - это Kotlin Coroutines. Room теперь поддерживает его напрямую (в настоящее время находится в стадии бета-тестирования). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

- РЕДАКТИРОВАНИЕ 1 -

Для людей, интересующихся ... У вас есть другие варианты. Я рекомендую взглянуть на новые компоненты ViewModel и LiveData. LiveData отлично работает с Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Другой вариант - это RxJava / RxAndroid. Более мощный, но более сложный, чем LiveData. https://github.com/ReactiveX/RxJava

--Оригинальный ответ -

Создайте статический вложенный класс (для предотвращения утечки памяти) в своей Activity, расширяющей AsyncTask.

private static class AgentAsyncTask extends AsyncTask<Void, Void, Integer> {

    //Prevent leak
    private WeakReference<Activity> weakActivity;
    private String email;
    private String phone;
    private String license;

    public AgentAsyncTask(Activity activity, String email, String phone, String license) {
        weakActivity = new WeakReference<>(activity);
        this.email = email;
        this.phone = phone;
        this.license = license;
    }

    @Override
    protected Integer doInBackground(Void... params) {
        AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
        return agentDao.agentsCount(email, phone, license);
    }

    @Override
    protected void onPostExecute(Integer agentsCount) {
        Activity activity = weakActivity.get();
        if(activity == null) {
            return;
        }

        if (agentsCount > 0) {
            //2: If it already exists then prompt user
            Toast.makeText(activity, "Agent already exists!", Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(activity, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
            activity.onBackPressed();
        }
    }
}

Или вы можете создать последний класс в отдельном файле.

Затем выполните его в методе signUpAction (View view):

new AgentAsyncTask(this, email, phone, license).execute();

В некоторых случаях вы также можете сохранить ссылку на AgentAsyncTask в своей деятельности, чтобы вы могли отменить ее, когда действие будет уничтожено. Но вам придется самому прерывать любые транзакции.

Кроме того, ваш вопрос о тестовом примере Google ... Они заявляют на этой веб-странице:

Рекомендуемый подход для тестирования реализации вашей базы данных - это написание теста JUnit, который запускается на устройстве Android. Поскольку эти тесты не требуют создания активности, они должны выполняться быстрее, чем тесты пользовательского интерфейса.

Нет активности, нет пользовательского интерфейса.

person mcastro    schedule 24.05.2017
comment
Должен ли я выполнять эту огромную асинхронную задачу (которую вы предложили в примере кода) каждый раз, когда мне нужно получить доступ к базе данных? Это дюжина или около того строк кода вместо одной для получения данных из базы данных. Вы также предложили создать новый класс, но означает ли это, что мне нужно создавать новый класс AsyncTask для каждого вызова вставки / выбора базы данных? - person Piotrek; 30.07.2017
comment
Да, у вас есть и другие варианты. Возможно, вы захотите познакомиться с новыми компонентами ViewModel и LiveData. При использовании LiveData вам не нужна AsyncTask, объект будет уведомлен всякий раз, когда что-то изменится. developer.android.com/topic/libraries/architecture/ developer.android.com/topic/libraries/architecture/ Также есть AndroidRx ( хотя он делает в значительной степени то же самое, что и LiveData) и Promises. При использовании AsyncTask вы можете организовать архитектуру таким образом, чтобы вы могли включать несколько операций в одну AsyncTask или разделять каждую из них. - person mcastro; 31.07.2017
comment
@Piotrek - Kotlin теперь имеет встроенную асинхронность (хотя он отмечен как экспериментальный). Смотрите мой ответ, который сравнительно тривиален. Ответ Сэмюэля Роберта касается Rx. Я не вижу здесь ответа LiveData, но это может быть лучшим выбором, если вам нужна наблюдаемая. - person charles-allen; 17.01.2018
comment
Использование обычного AsyncTask, похоже, сейчас даже не работает, по-прежнему возникает исключение недопустимого состояния - person Peterstev Uremgba; 11.05.2018
comment
@Piotrek, вы говорите мне, что вы привыкли выполнять доступ к базе данных в основном потоке на всем протяжении вашего опыта? - person mr5; 31.05.2018
comment
@ mr5, я не совсем понимаю ваше сообщение. Я просил совета, не имея опыта. - person Piotrek; 07.06.2018
comment
@Piotrek обычно фреймворк / приложение выполняет свои элементы управления пользовательским интерфейсом в основном потоке, поэтому вы не хотите блокировать их рендеринг с помощью операций ввода-вывода, таких как чтение файлов, сетевые операции и т. Д. Так что вы должны и должны делать весь доступ к базе данных в другом потоке, чтобы этого избежать. Если вы не используете язык, поддерживающий async / await, например C # - person mr5; 07.06.2018
comment
Sh *, я возвращаюсь к написанию SQLite, по крайней мере, это шаблон разумный и действительно необходимый. Черт тебя побери, Гугл, черт тебя побери! - person samuel owino; 18.10.2019
comment
СИНХРОНИЧЕСКИ stackoverflow.com/questions/ 58532832 / - person nnyerges; 24.10.2019

Это не рекомендуется, но вы можете получить доступ к базе данных в основном потоке с помощью allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
person mpolat    schedule 01.07.2017
comment
Комната не позволяет получить доступ к базе данных в основном потоке, если вы не вызвали allowMainThreadQueries() в построителе, потому что это потенциально может заблокировать пользовательский интерфейс на длительный период времени. Асинхронные запросы (запросы, возвращающие LiveData или RxJava Flowable) исключены из этого правила, поскольку они асинхронно запускают запрос в фоновом потоке, когда это необходимо. - person pRaNaY; 17.12.2017
comment
Спасибо, это очень полезно для миграции, так как я хочу проверить, работает ли Room должным образом, прежде чем переходить с загрузчиков на живые данные. - person SammyT; 16.09.2018
comment
@JideGuruTheProgrammer Нет, не должно. В некоторых случаях это может сильно замедлить работу вашего приложения. Операции должны выполняться асинхронно. - person Alex; 20.02.2019
comment
@Alex Никогда не бывает случая делать запрос в основном потоке? - person Justin Meiners; 05.08.2019
comment
@JustinMeiners - это просто плохая практика, вы будете в порядке, пока база данных останется небольшой. - person lasec0203; 14.09.2019
comment
Это полезно для модульного тестирования функций dao. - person Tom; 30.11.2020
comment
@Alex Что сказал JideGuruTheProgrammer? А где его комментарий? Это исчезло? - person The incredible Jan; 08.02.2021
comment
@TheincredibleJan Человек, это было где-то год назад, поэтому я не помню. Вероятно, он удалил свой комментарий. Это действительно очень плохая практика - делать любые запросы к базе данных в основном потоке приложения для Android. - person Alex; 12.02.2021

Котлинские сопрограммы (ясные и лаконичные)

AsyncTask действительно неуклюжий. Сопрограммы - более чистая альтернатива (просто добавьте пару ключевых слов, и ваш код синхронизации станет асинхронным).

// Step 1: add `suspend` to your fun
suspend fun roomFun(...): Int
suspend fun notRoomFun(...) = withContext(Dispatchers.IO) { ... }

// Step 2: launch from coroutine scope
private fun myFun() {
    lifecycleScope.launch { // coroutine on Main
        val queryResult = roomFun(...) // coroutine on IO
        doStuff() // ...back on Main
    }
}

Зависимости (добавляет области сопрограмм для компонентов арки):

// lifecycleScope:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha04'

// viewModelScope:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha04'

- Обновления:
8 мая 2019 г .: Room 2.1 теперь поддерживает suspend
13 сентября 2019 г .: Обновлено для использования Компоненты архитектуры объем

person charles-allen    schedule 15.01.2018
comment
Есть ли у вас какие-либо ошибки компиляции с @Query abstract suspend fun count() при использовании ключевого слова suspend? Не могли бы вы изучить аналогичный вопрос: stackoverflow.com/questions/48694449/ - person Robin; 09.02.2018
comment
@Robin - Да, я знаю. Виноват; Я использовал приостановку для общедоступного (неаннотированного) метода DAO, который вызывал защищенную непостоянную @Query функцию. Когда я добавляю ключевое слово suspend во внутренний метод @Query, он действительно не компилируется. Похоже, что это умный материал для приостановки и столкновения комнат (как вы упомянули в своем другом вопросе, скомпилированная версия приостановки возвращает продолжение, с которым Room не может справиться). - person charles-allen; 09.02.2018
comment
Имеет большой смысл. Вместо этого я собираюсь вызвать его с помощью функций сопрограмм. - person Robin; 09.02.2018
comment
@Robin - К вашему сведению, они добавили поддержку приостановки в комнате 2.1 :) - person charles-allen; 16.05.2019

Для всех RxJava или RxAndroid или любители RxKotlin

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }
person Samuel Robert    schedule 25.10.2017
comment
Если я помещу этот код в метод, как вернуть результат работы с базой данных? - person Eggakin Baconwalker; 13.11.2017
comment
@EggakinBaconwalker У меня есть 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()) - person noloman; 23.11.2017
comment
Это не сработает для IntentService. Потому что IntentService будет выполнен после завершения потока. - person Umang Kothari; 29.10.2018
comment
@UmangKothari. Вы не получите исключения, если используете IntentService#onHandleIntent, потому что этот метод выполняется в рабочем потоке, поэтому вам не понадобится какой-либо механизм потоковой передачи для выполнения операции с базой данных Room - person Samuel Robert; 29.10.2018
comment
@SamuelRobert, да, согласен, плохо. Это выскользнуло из головы. - person Umang Kothari; 29.10.2018

Вы не можете запустить его в основном потоке, вместо этого используйте обработчики, асинхронные или рабочие потоки. Пример кода доступен здесь, а статью о библиотеке комнат можно прочитать здесь: Библиотека комнат Android

/**
 *  Insert and get data using Database Async way
 */
AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        // Insert Data
        AppDatabase.getInstance(context).userDao().insert(new User(1,"James","Mathew"));

        // Get Data
        AppDatabase.getInstance(context).userDao().getAllUsers();
    }
});

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

Вы можете использовать этот метод для достижения в основном потоке Room.inMemoryDatabaseBuilder()

person Rizvan    schedule 06.11.2017
comment
Если я использую этот метод для получения данных (в данном случае только getAllUsers ()), как вернуть данные из этого метода? Если я поставлю слово return внутри run, появится ошибка. - person Eggakin Baconwalker; 11.11.2017
comment
создайте где-нибудь интерфейсный метод и добавьте анонимный класс для получения данных отсюда. - person Rizvan; 29.11.2017
comment
Это самое простое решение для прошивки / обновления. - person Beer Me; 24.01.2019

С lambda его легко запустить с помощью AsyncTask

 AsyncTask.execute(() -> //run your query here );
person Afjalur Rahman Rana    schedule 14.03.2019
comment
Это удобно, спасибо. Кстати, с Kotlin еще проще: AsyncTask.execute {} - person alexrnov; 21.02.2020
comment
но как получить результат с помощью этого метода? - person leeCoder; 05.05.2020

Просто выполняйте операции с базой данных в отдельном потоке. Вот так (Котлин):

Thread {
   //Do your database´s operations here
}.start()
person Angel Enrique Herrera Crespo    schedule 30.04.2019

С библиотекой Jetbrains Anko вы можете использовать метод doAsync {..} для автоматического выполнения вызовов базы данных. Это решает проблему многословия, которая у вас, казалось, была с ответом mcastro.

Пример использования:

    doAsync { 
        Application.database.myDAO().insertUser(user) 
    }

Я часто использую это для вставок и обновлений, однако для избранных запросов я рекомендую использовать рабочий процесс RX.

person Arsala Bangash    schedule 07.08.2017

Просто вы можете использовать этот код для его решения:

Executors.newSingleThreadExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        appDb.daoAccess().someJobes();//replace with your code
                    }
                });

Или в лямбде вы можете использовать этот код:

Executors.newSingleThreadExecutor().execute(() -> appDb.daoAccess().someJobes());

Вы можете заменить appDb.daoAccess().someJobes() своим собственным кодом;

person Mostafa Rostami    schedule 02.02.2019

Вы должны выполнить запрос в фоновом режиме. Простым способом может быть использование Executors:

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}
person Phil    schedule 11.07.2018
comment
как вернуть результат? - person leeCoder; 05.05.2020

Поскольку asyncTask устарели, мы можем использовать службу исполнителя. ИЛИ вы также можете использовать ViewModel с LiveData как объяснил в других ответах.

Для использования службы исполнителя вы можете использовать что-то вроде ниже.

public class DbHelper {

    private final Executor executor = Executors.newSingleThreadExecutor();

    public void fetchData(DataFetchListener dataListener){
        executor.execute(() -> {
                Object object = retrieveAgent(agentId);
                new Handler(Looper.getMainLooper()).post(() -> {
                        dataListener.onFetchDataSuccess(object);
                });
        });
    }
}

Используется основной цикл, поэтому вы можете получить доступ к элементу пользовательского интерфейса из обратного вызова onFetchDataSuccess.

person Sasuke Uchiha    schedule 05.01.2020

Для быстрых запросов вы можете оставить место для выполнения в потоке пользовательского интерфейса.

AppDatabase db = Room.databaseBuilder(context.getApplicationContext(),
        AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build();

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

       @Override
        public void onClick(View view) {



            int position = getAdapterPosition();

            User user = new User();
            String name = getName(position);
            user.setName(name);

            AppDatabase appDatabase = DatabaseCreator.getInstance(mContext).getDatabase();
            UserDao userDao = appDatabase.getUserDao();
            ArrayList<User> users = new ArrayList<User>();
            users.add(user);
            List<Long> ids = userDao.insertAll(users);

            Long id = ids.get(0);
            if(id == -1)
            {
                user = userDao.getUser(name);
                user.setId(user.getId());
            }
            else
            {
                user.setId(id);
            }

            Intent intent = new Intent(mContext, ChatActivity.class);
            intent.putExtra(ChatActivity.EXTRAS_USER, Parcels.wrap(user));
            mContext.startActivity(intent);
        }
    }
person Vihaan Verma    schedule 07.08.2017

Элегантное решение RxJava / Kotlin - использовать _ 1_, что даст вам Observable, который не возвращает значение, но может наблюдать и подписываться в другом потоке.

public Completable insert(Event event) {
    return Completable.fromCallable(new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            return database.eventDao().insert(event)
        }
    }
}

Или в Котлине:

fun insert(event: Event) : Completable = Completable.fromCallable {
    database.eventDao().insert(event)
}

Вы можете наблюдать и подписываться, как обычно:

dataManager.insert(event)
    .subscribeOn(scheduler)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...)
person dcr24    schedule 02.11.2017

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

Вот причина.

Примечание. Room не поддерживает доступ к базе данных в основном потоке, если вы не вызвали allowMainThreadQueries () в построителе, потому что это может заблокировать пользовательский интерфейс на длительный период времени. Асинхронные запросы - запросы, возвращающие экземпляры LiveData или Flowable - не подпадают под это правило, поскольку они асинхронно запускают запрос в фоновом потоке, когда это необходимо.

person Nilesh Rathore    schedule 12.12.2017

Если вам удобнее использовать Асинхронную задачу:

  new AsyncTask<Void, Void, Integer>() {
                @Override
                protected Integer doInBackground(Void... voids) {
                    return Room.databaseBuilder(getApplicationContext(),
                            AppDatabase.class, DATABASE_NAME)
                            .fallbackToDestructiveMigration()
                            .build()
                            .getRecordingDAO()
                            .getAll()
                            .size();
                }

                @Override
                protected void onPostExecute(Integer integer) {
                    super.onPostExecute(integer);
                    Toast.makeText(HomeActivity.this, "Found " + integer, Toast.LENGTH_LONG).show();
                }
            }.execute();
person Hitesh Sahu    schedule 09.02.2018

Обновление: я также получил это сообщение, когда пытался создать запрос с использованием @RawQuery и SupportSQLiteQuery внутри DAO.

@Transaction
public LiveData<List<MyEntity>> getList(MySettings mySettings) {
    //return getMyList(); -->this is ok

    return getMyList(new SimpleSQLiteQuery("select * from mytable")); --> this is an error

Решение: создайте запрос внутри ViewModel и передайте его в DAO.

public MyViewModel(Application application) {
...
        list = Transformations.switchMap(searchParams, params -> {

            StringBuilder sql;
            sql = new StringBuilder("select  ... ");

            return appDatabase.rawDao().getList(new SimpleSQLiteQuery(sql.toString()));

        });
    }

Or...

Вы не должны обращаться к базе данных непосредственно в основном потоке, например:

 public void add(MyEntity item) {
     appDatabase.myDao().add(item); 
 }

Вы должны использовать AsyncTask для операций обновления, добавления и удаления.

Пример:

public class MyViewModel extends AndroidViewModel {

    private LiveData<List<MyEntity>> list;

    private AppDatabase appDatabase;

    public MyViewModel(Application application) {
        super(application);

        appDatabase = AppDatabase.getDatabase(this.getApplication());
        list = appDatabase.myDao().getItems();
    }

    public LiveData<List<MyEntity>> getItems() {
        return list;
    }

    public void delete(Obj item) {
        new deleteAsyncTask(appDatabase).execute(item);
    }

    private static class deleteAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        deleteAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().delete((params[0]));
            return null;
        }
    }

    public void add(final MyEntity item) {
        new addAsyncTask(appDatabase).execute(item);
    }

    private static class addAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        addAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().add((params[0]));
            return null;
        }

    }
}

Если вы используете LiveData для выбранных операций, вам не понадобится AsyncTask.

person live-love    schedule 05.01.2018

Сообщение об ошибке,

Невозможно получить доступ к базе данных в основном потоке, так как он может заблокировать пользовательский интерфейс на длительные периоды времени.

Достаточно информативен и точен. Вопрос в том, как избежать доступа к базе данных в основном потоке. Это огромная тема, но для начала ознакомьтесь с AsyncTask (нажмите здесь)

-----РЕДАКТИРОВАТЬ----------

Я вижу, у вас проблемы при запуске модульного теста. У вас есть несколько способов исправить это:

  1. Запустите тест непосредственно на машине разработки, а не на устройстве Android (или эмуляторе). Это работает для тестов, которые ориентированы на базу данных и не заботятся о том, выполняются ли они на устройстве.

  2. Используйте аннотацию @RunWith(AndroidJUnit4.class) для запуска теста на устройстве Android, но не в действии с пользовательским интерфейсом. Более подробную информацию об этом можно найти в этом руководстве < / а>

person Dale Wilson    schedule 24.05.2017
comment
Я понимаю вашу точку зрения, я предполагаю, что та же точка действительна, когда вы пытаетесь протестировать любую операцию db через JUnit. Однако в developer.android.com/topic/libraries/architecture/room.html образец тестового метода writeUserAndReadInList не вызывает запрос вставки в фоновом потоке. Я что-то здесь упускаю? Пожалуйста, предложите. - person Devarshi; 24.05.2017
comment
Извините, я пропустил тот факт, что в этом тесте возникли проблемы. Я отредактирую свой ответ, чтобы добавить дополнительную информацию. - person Dale Wilson; 24.05.2017

Вы можете использовать Future и Callable. Таким образом, вам не нужно будет писать длинную асинхронную задачу и вы можете выполнять свои запросы без добавления allowMainThreadQueries ().

Мой запрос дао: -

@Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();

Мой метод репозитория: -

public UserData getDefaultData() throws ExecutionException, InterruptedException {

    Callable<UserData> callable = new Callable<UserData>() {
        @Override
        public UserData call() throws Exception {
            return userDao.getDefaultData();
        }
    };

    Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);

    return future.get();
}
person beginner    schedule 07.08.2018
comment
Вот почему мы используем Callable / Future, потому что Android не позволяет запускать запросы в основном потоке. Как указано в вопросе выше - person beginner; 17.08.2018
comment
Я имею в виду, что хотя код из вашего ответа делает запрос в фоновом потоке, основной поток заблокирован и ожидает завершения запроса. Так что в итоге это не намного лучше, чем allowMainThreadQueries(). Основной поток все еще заблокирован в обоих случаях - person eugeneek; 17.08.2018

На мой взгляд, правильнее всего делегировать запрос потоку ввода-вывода с помощью RxJava.

У меня есть пример решения аналогичной проблемы, с которой я только что столкнулся.

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            //Creating view model requires DB access
            homeViewModel = new ViewModelProvider(this, factory).get(HomeViewModel.class);
        }).subscribeOn(Schedulers.io())//The DB access executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    mAdapter = new MyAdapter(homeViewModel.getExams());
                    recyclerView.setAdapter(mAdapter);
                    ((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.INVISIBLE);
                },
                error -> error.printStackTrace()
        );

И если мы хотим обобщить решение:

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            someTaskThatTakesTooMuchTime();
        }).subscribeOn(Schedulers.io())//The long task executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    taskIWantToDoOnTheMainThreadWhenTheLongTaskIsDone();
                },
                error -> error.printStackTrace()
        );
person Rotem Barak    schedule 02.04.2020