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

126

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

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

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

Devarshi
источник
1
Хотя эта статья написана для Kotlin, она очень хорошо объясняет основную проблему!
Питер Ленхардт
Взгляните на stackoverflow.com/questions/58532832/…
nnyerges
Посмотри на этот ответ. Этот ответ работает для меня stackoverflow.com/a/51720501/7655085
Сомен Тушир

Ответы:

59

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

Создайте статический вложенный класс (для предотвращения утечки памяти) в своей деятельности, расширяющей 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. Поскольку эти тесты не требуют создания активности, они должны выполняться быстрее, чем тесты пользовательского интерфейса.

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

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

Для людей, интересующихся ... У вас есть другие варианты. Я рекомендую ознакомиться с новыми компонентами 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

mcastro
источник
31
Должен ли я выполнять эту огромную асинхронную задачу (которую вы предложили в примере кода) каждый раз, когда мне нужен доступ к базе данных? Это дюжина или около того строк кода вместо одной для получения данных из базы данных. Вы также предложили создать новый класс, но означает ли это, что мне нужно создавать новый класс AsyncTask для каждого вызова вставки / выбора базы данных?
Piotrek
7
Да, у вас есть и другие варианты. Возможно, вам захочется изучить новые компоненты ViewModel и LiveData. При использовании LiveData вам не нужна AsyncTask, объект будет уведомлен всякий раз, когда что-то изменится. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/ ... Также есть AndroidRx (хотя он делает почти то же, что и LiveData) и Promises. При использовании AsyncTask вы можете организовать архитектуру таким образом, чтобы вы могли включать несколько операций в одну AsyncTask или разделять каждую из них.
mcastro
@Piotrek - Kotlin теперь имеет встроенную асинхронность (хотя он отмечен как экспериментальный). Смотрите мой ответ, который сравнительно тривиален. Ответ Сэмюэля Роберта касается Rx. Я не вижу здесь ответа LiveData, но это может быть лучшим выбором, если вам нужна наблюдаемая.
AjahnCharles
Использование обычного AsyncTask, похоже, сейчас даже не работает, все еще
возникает
@Piotrek, вы говорите мне, что вы привыкли выполнять доступ к базе данных в основном потоке на всем протяжении вашего опыта?
mr5
142

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

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
mpolat
источник
10
Room не позволяет получить доступ к базе данных в основном потоке, если вы не вызываете allowMainThreadQueries()построитель, потому что он потенциально может заблокировать пользовательский интерфейс на длительный период времени. Асинхронные запросы (запросы, которые возвращают LiveDataили RxJava Flowable) исключаются из этого правила, поскольку они асинхронно запускают запрос в фоновом потоке, когда это необходимо.
pRaNaY
4
Спасибо, это очень полезно для миграции, так как я хочу проверить, работает ли Room
должным образом,
5
@JideGuruTheProgrammer Нет, не должно. В некоторых случаях это может сильно замедлить работу вашего приложения. Операции должны выполняться асинхронно.
Alex
@Alex Никогда не бывает необходимости делать запрос в основном потоке?
Джастин Майнерс
2
@JustinMeiners - это просто плохая практика, вы будете в порядке, пока база данных останется небольшой.
lasec0203
54

Kotlin Coroutines (ясные и лаконичные)

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'

- Обновление:
08-May-2019: номер 2,1 теперь поддерживает suspend
13-Sep-2019: Обновленный использовать Архитектура компонентов сферы

AjahnCharles
источник
1
Есть ли у вас какие-либо ошибки компиляции с @Query abstract suspend fun count()использованием ключевого слова suspend? Не могли бы вы изучить аналогичный вопрос: stackoverflow.com/questions/48694449/…
Робин
@Robin - Да, я знаю. Моя ошибка; Я использовал приостановку для общедоступного (неаннотированного) метода DAO, который вызывал защищенную @Queryфункцию без приостановки . Когда я добавляю ключевое слово suspend во внутренний @Queryметод, он действительно не компилируется. Похоже, что это умная штука под капотом для приостановки и столкновения комнат (как вы упомянули в своем другом вопросе, скомпилированная версия приостановки возвращает продолжение, с которым Room не может справиться).
AjahnCharles
Имеет большой смысл. Вместо этого я собираюсь вызвать его с помощью функций сопрограмм.
Робин
1
@Robin - К вашему сведению, они добавили поддержку приостановки в Комнате 2.1 :)
AjahnCharles
Видимо нет launchключевых слов больше, запускают с областью, например,GlobalScope.launch
нащ
48

Для всех RxJava или RxAndroid или RxKotlin любителей там ,

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }
Сэмюэл Роберт
источник
3
Если я помещу этот код в метод, как вернуть результат работы базы данных?
Eggakin Baconwalker
@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())
noloman
Это не сработает для IntentService. Потому что IntentService будет выполнен после завершения потока.
Уманг Котари
1
@UmangKothari. Вы не получите исключения, если находитесь в системе, IntentService#onHandleIntentпотому что этот метод выполняется в рабочем потоке, поэтому вам не понадобится какой-либо механизм потоковой передачи для выполнения операции с базой данных Room,
Сэмюэл Роберт
@SamuelRobert, да, согласен, плохо. Это вылетело из головы.
Уманг Котари
27

Вы не можете запустить его в основном потоке, вместо этого используйте обработчики, асинхронные или рабочие потоки. Пример кода доступен здесь, а статью о библиотеке комнат можно прочитать здесь: Библиотека комнат 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()

Ризван
источник
Если я использую этот метод для получения данных (в данном случае только getAllUsers ()), как вернуть данные из этого метода? Если я поставлю слово «возврат» внутри «выполнить», появится ошибка.
Eggakin Baconwalker
1
создайте где-нибудь интерфейсный метод и добавьте анонимный класс для получения данных отсюда.
Rizvan
1
Это самое простое решение для прошивки / обновления.
Beer Me
12

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

 AsyncTask.execute(() -> //run your query here );
Афджалур Рахман Рана
источник
2
Это удобно, спасибо. Кстати, с Котлином еще проще: AsyncTask.execute {}
алексрнов
1
но как получить результат с помощью этого метода?
leeCoder
11

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

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

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

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

Арсала Бангаш
источник
7

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

Thread {
   //Do your database´s operations here
}.start()
Анхель Энрике Эррера Креспо
источник
6

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

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}
Фил
источник
как вернуть результат?
leeCoder
5

Следует использовать элегантное решение RxJava / Kotlin Completable.fromCallable, которое даст вам 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(...)
dcr24
источник
5

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

Вот в чем причина.

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

Нилеш Ратхор
источник
4

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

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()своим кодом;

Мостафа Ростами
источник
4

Поскольку 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обратного вызова.

Саске Учиха
источник
3

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

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

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

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

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

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

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

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

Если вам удобнее использовать задачу Async :

  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();
Хитеш Саху
источник
2

Обновление: я также получил это сообщение, когда пытался создать запрос с использованием @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()));

        });
    }

Или...

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

 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.

живи любя
источник
1

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

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);
        }
    }
Вихан Верма
источник
1

Вы можете использовать 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();
}
начинающий
источник
Вот почему мы используем Callable / Future, потому что Android не позволяет запускать запросы в основном потоке. Как задано в qus выше
новичок
1
Я имею в виду, что хотя код из вашего ответа делает запрос в фоновом потоке, основной поток заблокирован и ожидает завершения запроса. Так что в итоге это не намного лучше allowMainThreadQueries(). Основная тема по-прежнему заблокирована в обоих случаях
eugeneek
0

На мой взгляд, правильное решение - делегировать запрос потоку ввода-вывода с помощью 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()
        );
Ротем Барак
источник