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

Я щось тут пропускаю? Як я можу змусити його виконувати без збоїв? Будь ласка, підкажіть.


1
Хоча написана для Котліна, ця стаття дуже добре пояснює основну проблему!
Пітер Ленхардт


Подивіться на цю відповідь. Відповідь на це питання для мене робота stackoverflow.com/a/51720501/7655085
Сомен Tushir

Відповіді:


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 (Перегляд подання):

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

У деяких випадках ви також можете захотіти посилання на AgentAsyncTask у своїй діяльності, щоб ви могли скасувати його, коли діяльність знищена. Але вам доведеться самостійно переривати будь-які операції.

Також ваше запитання щодо тестового прикладу Google ... Вони вказані на цій веб-сторінці:

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

Ні активності, ні інтерфейсу користувача.

--EDIT--

Для людей, які цікавляться ... У вас є інші варіанти. Рекомендую ознайомитися з новими компонентами ViewModel і LiveData. LiveData чудово працює із Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Інший варіант - RxJava / RxAndroid. Більш потужний, але складніший за LiveData. https://github.com/ReactiveX/RxJava

--EDIT 2--

Оскільки багато людей можуть зіткнутися з цією відповіддю ... Найкращий варіант на сьогоднішній день, загалом кажучи, - це Котролін Котлін. Кімната тепер підтримує його безпосередньо (зараз у бета-версії). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01


31
Чи повинен я робити цю величезну задачу асинхронізації (яку ви запропонували в зразку коду) кожен раз, коли мені потрібно отримати доступ до бази даних? Це десяток або близько рядків коду замість одного, щоб отримати деякі дані з db. Ви запропонували також створити новий клас, але чи означає це, що мені потрібно створити новий клас AsyncTask для кожного виклику бази даних "вставити / вибрати"?
Піотрек

7
У вас є інші варіанти, так. Ви можете заглянути в нові компоненти ViewModel та LiveData. Під час використання LiveData вам не потрібен AsyncTask, об’єкт буде повідомлено щоразу, коли щось зміниться. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… Є також AndroidRx (хоча він робить майже все, що робить LiveData), і обіцяє. Використовуючи AsyncTask, ви можете архітектуру таким чином, щоб ви могли включати кілька операцій в одну AsyncTask або розділяти кожну.
mcastro

@Piotrek - Котлін зараз запустив асинхронізацію (хоча це позначено експериментально). Дивіться мою відповідь, яка порівняно тривіальна. Відповідь Семюеля Роберта охоплює Rx. Тут я не бачу відповіді LiveData, але це може бути кращим вибором, якщо ви хочете спостерігати.
AjahnCharles

Використання звичайного AsyncTask зараз навіть не працює, але все ще отримує виключення з незаконного стану
Петерстев Уремгба

@Piotrek, ти мені кажеш, що ти використовуєш доступ до бази даних у головному потоці протягом усього свого досвіду?
mr5

142

Це не рекомендується, але ви можете отримати доступ до бази даних на головному потоці за допомогою allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()

10
Кімната не дозволяє отримати доступ до бази даних в основному потоці, якщо ви не зателефонували allowMainThreadQueries()на конструктора, оскільки це може потенційно заблокувати інтерфейс користувача на тривалий період часу. Асинхронні запити (запити, що повертаються LiveDataабо RxJava Flowable) виключаються з цього правила, оскільки вони асинхронно виконують запит на фоновому потоці, коли це необхідно.
pRaNaY

4
Дякую, це дуже корисно для міграції, оскільки я хочу тестувати, що Кімната працює так, як очікувалося, перш ніж переходити з навантажувачів до даних Live
SammyT

5
@JideGuruTheProgrammer Ні, це не повинно. У деяких випадках це може значно уповільнити вашу програму. Операції слід робити асинхронно.
Олексій

@Alex Ніколи не буває справи для запиту на основну нитку?
Джастін Майнерс

2
@JustinMeiners це просто погана практика, ви будете добре робити це, поки база даних залишається маленькою.
lasec0203

53

Котролі Котлін (чіткі та стислі)

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: Оновлений використовувати Архітектура компонентів сфери


1
Чи є у вас помилка компіляції з @Query abstract suspend fun count()використанням ключового слова призупинити? Чи можете ви люб'язно розглянути це подібне запитання: stackoverflow.com/questions/48694449/…
Робін

@Robin - Так. Моя помилка; Я використовував призупинення на загальнодоступному (неозначеному) методі DAO, який викликав захищену @Queryфункцію неприпинення . Коли я також додаю ключове слово призупинення до внутрішнього @Queryметоду, воно справді не може зібрати. Схоже, розумний під капотом матеріал для призупинення та зіткнення кімнати (як ви згадуєте в іншому питанні, складена версія призупинення повертає продовження, яке Кімната не може впоратися).
AjahnCharles

Має багато сенсу. Я замість цього буду називати це функціями coroutine.
Робін

1
@Robin - FYI вони додали підтримку для призупинення роботи в кімнаті 2.1 :)
AjahnCharles

Мабуть, більше немає launchключового слова, ви запускаєте з такою сферою, якGlobalScope.launch
nasch

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тому що цей метод виконується на робочій нитці, тому вам не знадобиться жоден механізм нанизування для виконання операцій із базою даних кімнати
Семюель Роберт

@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
зробіть десь метод інтерфейсу та додайте анонімний клас, щоб отримати дані звідси.
Різван

1
Це найпростіше рішення для вставки / оновлення.
Пиво мені

12

З лямбдаю легко працювати з AsyncTask

 AsyncTask.execute(() -> //run your query here );

2
Це зручно, дякую. До речі, Котлін ще простіше: AsyncTask.execute {}
alexrnov

1
але як ви отримуєте результат за допомогою цього методу?
leeCoder

11

За допомогою бібліотеки Jetbrains Anko ви можете використовувати метод doAsync {..} для автоматичного виконання дзвінків до бази даних. Це вирішує проблему багатослівності, яку ви, здавалося, мали у відповіді mcastro.

Приклад використання:

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

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



6

Ви повинні виконати запит у фоновому режимі. Простим способом може бути використання Виконавців :

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}

як ви повертаєте результат?
leeCoder

5

Елегантне рішення RxJava / Kotlin - це використання Completable.fromCallable, яке дасть вам спостережливість, яка не повертає значення, але може спостерігати та підписатись на іншому потоці.

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(...)

5

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

Ось причина.

Примітка: Кімната не підтримує доступ до бази даних в основному потоці, якщо ви не викликали в дозволі будівельника enableMainThreadQueries (), оскільки він може блокувати користувальницький інтерфейс на тривалий період часу. Асинхронні запити - запити, що повертають екземпляри 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);
                });
        });
    }
}

Основний Looper використовується, щоб ви могли отримати доступ до елемента інтерфейсу з 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. Таким чином, вам не потрібно буде писати довгий асинтакт, і ви можете виконувати свої запити, не додаючи дозволуMainThreadQueries ().

Мій запит дао: -

@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

На мою думку, правильне зробити - це делегувати запит до IO-потоку за допомогою 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()
        );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.