Android Room - proste zapytanie wybierające - Nie można uzyskać dostępu do bazy danych w głównym wątku

126

Próbuję próbki z Room Persistence Library . Utworzyłem jednostkę:

@Entity
public class Agent {
    @PrimaryKey
    public String guid;
    public String name;
    public String email;
    public String password;
    public String phone;
    public String licence;
}

Utworzono klasę 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);
}

Utworzono klasę Database:

@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract AgentDao agentDao();
}

Odsłonięta baza danych przy użyciu poniższej podklasy w Kotlinie:

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

Zaimplementowana poniżej funkcja w mojej działalności:

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

Niestety przy wykonaniu powyższej metody następuje awaria z poniższym śladem stosu:

    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) 

Wygląda na to, że problem jest związany z wykonywaniem operacji db na głównym wątku. Jednak przykładowy kod testu podany w powyższym linku nie działa w osobnym wątku:

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

Czy coś mi tu brakuje? Jak mogę to zrobić bez awarii? Proszę zasugeruj.

Devarshi
źródło
1
Chociaż ten artykuł został napisany dla Kotlina, bardzo dobrze wyjaśnia podstawowy problem!
Peter Lehnhardt
Spójrz na tę odpowiedź. Ta odpowiedź działa dla mnie stackoverflow.com/a/51720501/7655085
Somen Tushir

Odpowiedzi:

59

Dostęp do bazy danych w głównym wątku blokującym interfejs użytkownika jest błędem, jak powiedział Dale.

Utwórz statyczną klasę zagnieżdżoną (aby zapobiec wyciekowi pamięci) w swoim działaniu rozszerzającym 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();
        }
    }
}

Lub możesz utworzyć ostateczną klasę w swoim własnym pliku.

Następnie wykonaj go w metodzie signUpAction (Widok widoku):

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

W niektórych przypadkach możesz również chcieć przechowywać odwołanie do AgentAsyncTask w swoim działaniu, aby można było je anulować, gdy działanie zostanie zniszczone. Ale musiałbyś sam przerywać wszelkie transakcje.

Również twoje pytanie dotyczące przykładu testowego Google ... Stwierdzają na tej stronie internetowej:

Zalecanym podejściem do testowania implementacji bazy danych jest napisanie testu JUnit, który działa na urządzeniu z systemem Android. Ponieważ testy te nie wymagają tworzenia aktywności, powinny być wykonywane szybciej niż testy interfejsu użytkownika.

Brak aktywności, brak interfejsu użytkownika.

--EDYTOWAĆ--

Dla ludzi, którzy się zastanawiają ... Masz inne możliwości. Polecam przyjrzeć się nowym komponentom ViewModel i LiveData. LiveData działa świetnie z Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Inną opcją jest RxJava / RxAndroid. Mocniejszy, ale bardziej złożony niż LiveData. https://github.com/ReactiveX/RxJava

--EDYCJA 2--

Ponieważ wiele osób może spotkać się z tą odpowiedzią… Ogólnie rzecz biorąc, obecnie najlepszą opcją jest Kotlin Coroutines. Room obsługuje go teraz bezpośrednio (obecnie w wersji beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

mcastro
źródło
31
Czy mam wykonać to ogromne zadanie asynchroniczne (które zaproponowałeś w przykładzie kodu) za każdym razem, gdy potrzebuję uzyskać dostęp do bazy danych? To kilkanaście wierszy kodu zamiast jednego, aby pobrać dane z bazy danych. Zaproponowałeś również utworzenie nowej klasy, ale czy to oznacza, że ​​muszę utworzyć nową klasę AsyncTask dla każdego wywołania bazy danych insert / select?
Piotrek
7
Masz inne opcje, tak. Możesz przyjrzeć się nowym składnikom ViewModel i LiveData. Podczas korzystania z LiveData nie potrzebujesz AsyncTask, obiekt zostanie powiadomiony, gdy coś się zmieni. developer.android.com/topic/libraries/architecture/ ... developer.android.com/topic/libraries/architecture/ ... Jest też AndroidRx (chociaż robi prawie to samo, co LiveData) i Promises. Korzystając z AsyncTask, możesz stworzyć architekturę w taki sposób, że możesz uwzględnić wiele operacji w jednym AsyncTask lub osobno każdą.
mcastro
@Piotrek - Kotlin ma teraz wbudowaną async (chociaż jest oznaczona jako eksperymentalna). Zobacz moją odpowiedź, która jest stosunkowo banalna. Odpowiedź Samuela Roberta obejmuje Rx. Nie widzę tutaj odpowiedzi LiveData, ale może to być lepszy wybór, jeśli chcesz obserwować.
AjahnCharles
Używanie zwykłego AsyncTask wydaje się teraz nawet nie działać, wciąż otrzymując nielegalny wyjątek stanu
Peterstev Uremgba
@Piotrek Mówisz mi, że byłeś przyzwyczajony do wykonywania dostępu do bazy danych w głównym wątku podczas całego doświadczenia?
mr5
142

Nie jest to zalecane, ale możesz uzyskać dostęp do bazy danych w głównym wątku za pomocą allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
mpolat
źródło
10
Room nie zezwala na dostęp do bazy danych w głównym wątku, chyba że wywołałeś allowMainThreadQueries()konstruktora, ponieważ może to potencjalnie zablokować interfejs użytkownika na długi czas. Zapytania asynchroniczne (zapytania, które zwracają LiveDatalub RxJava Flowable) są wyłączone z tej reguły, ponieważ w razie potrzeby asynchronicznie uruchamiają zapytanie w wątku w tle.
pRaNaY
4
Dzięki Jest to bardzo przydatne dla migracji, jak chcę przetestować pokoju działa zgodnie z oczekiwaniami przed konwersją z ładowarki do aktualnych danych
SammyT
5
@JideGuruTheProgrammer Nie, nie powinno. W niektórych przypadkach może to znacznie spowolnić Twoją aplikację. Operacje powinny być wykonywane asynchronicznie.
Alex
@Alex Nigdy nie ma powodu, aby wykonać zapytanie w głównym wątku?
Justin Meiners,
2
@JustinMeiners to po prostu zła praktyka, będziesz w porządku, dopóki baza danych pozostanie mała.
lasec0203
53

Kotlin Coroutines (jasne i zwięzłe)

AsyncTask jest naprawdę niezgrabny. Korekty są czystszą alternatywą (wystarczy posypać kilkoma słowami kluczowymi, a kod synchronizacji stanie się asynchroniczny).

// 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
    }
}

Zależności (dodaje coroutine zakresy dla komponentów arch):

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

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

- Aktualizacje:
8 maja 2019 r .: Pokój 2.1 obsługuje teraz suspend
13 września 2019 r .: Zaktualizowano w celu korzystania z zakresu komponentów architektury

AjahnCharles
źródło
1
Czy masz jakiś błąd kompilacji z @Query abstract suspend fun count()użyciem słowa kluczowego suspend? Czy możesz uprzejmie przyjrzeć się temu podobnemu pytaniu: stackoverflow.com/questions/48694449/…
Robin,
@Robin - Tak, mam. Mój błąd; Używałem zawieszenia w publicznej (nieanotowanej) metodzie DAO, która nazywała chronioną funkcję bez zawieszenia @Query. Kiedy dodam słowo kluczowe suspend również do @Querymetody wewnętrznej, kompilacja rzeczywiście kończy się niepowodzeniem. Wygląda na to, że sprytne rzeczy pod maską do zderzenia zawieszenia i pokoju (jak wspomniałeś w swoim drugim pytaniu, skompilowana wersja zawieszenia zwraca kontynuację, której Room nie może obsłużyć).
AjahnCharles
To ma dużo sensu. Zamiast tego nazwiemy to funkcjami coroutine.
Robin
1
@Robin - FYI dodali obsługę zawieszenia w pokoju 2.1 :)
AjahnCharles
Najwyraźniej nie ma launchjuż słowa kluczowego, uruchamiasz z zakresem, na przykładGlobalScope.launch
nasch
48

Dla wszystkich RxJava lub RxAndroid lub RxKotlin miłośników tam

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }
Samuel Robert
źródło
3
Jeśli umieszczę ten kod w metodzie, jak zwrócić wynik z operacji na bazie danych?
Eggakin Baconwalker,
@EggakinBaconwalker mam override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }gdzie applySchedulers()po prostu zrobićfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman
To nie zadziała w przypadku IntentService. Ponieważ IntentService zostanie wykonany po zakończeniu wątku.
Umang Kothari
1
@UmangKothari Nie otrzymujesz wyjątku, jeśli jesteś włączony, IntentService#onHandleIntentponieważ ta metoda jest wykonywana w wątku roboczym, więc nie potrzebujesz tam żadnego mechanizmu wątkowania, aby wykonać operację bazy danych pokoju
Samuel Robert
@SamuelRobert, Tak, zgadzam się z moim złem. Zapomniało mi się.
Umang Kothari
27

Nie możesz go uruchomić w wątku głównym zamiast tego użyj programów obsługi, wątków asynchronicznych lub wątków roboczych. Przykładowy kod jest dostępny tutaj i przeczytaj artykuł nad biblioteką pokojów tutaj: Biblioteka pokojów Androida

/**
 *  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();
    }
});

Jeśli chcesz uruchomić go na głównym wątku, co nie jest preferowanym sposobem.

Możesz użyć tej metody do osiągnięcia w głównym wątku Room.inMemoryDatabaseBuilder()

Rizvan
źródło
Jeśli użyję tej metody do pobrania danych (w tym przypadku tylko getAllUsers ()), jak zwrócić dane z tej metody? Wydaje się, że jest to błąd, jeśli wstawię słowo „powrót” w polu „bieg”.
Eggakin Baconwalker
1
stwórz gdzieś metodę interfejsu i dodaj anonimową klasę, aby pobrać stąd dane.
Rizvan
1
To najprostsze rozwiązanie do wstawiania / aktualizacji.
Beer Me
12

Dzięki lambdzie jest łatwy do uruchomienia z AsyncTask

 AsyncTask.execute(() -> //run your query here );
Afjalur Rahman Rana
źródło
2
To wygodne, dziękuję. Nawiasem mówiąc, Kotlin jest jeszcze łatwiejszy: AsyncTask.execute {}
alexrnov
1
ale jak uzyskać wynik za pomocą tej metody?
leeCoder
11

Dzięki bibliotece Jetbrains Anko możesz użyć metody doAsync {..} do automatycznego wykonywania wywołań bazy danych. To rozwiązuje problem z gadatliwością, który wydawał się mieć z odpowiedzią mcastro.

Przykładowe użycie:

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

Używam tego często do wstawiania i aktualizacji, jednak w przypadku wybranych zapytań zalecam korzystanie z przepływu pracy RX.

Arsala Bangash
źródło
7

Po prostu wykonaj operacje na bazie danych w osobnym wątku. W ten sposób (Kotlin):

Thread {
   //Do your database´s operations here
}.start()
Angel Enrique Herrera Crespo
źródło
6

Musisz wykonać żądanie w tle. Prostym sposobem może być użycie Executorów :

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}
Phil
źródło
jak zwracasz wynik?
leeCoder
5

Można użyć eleganckiego rozwiązania RxJava / Kotlin Completable.fromCallable, które da ci Observable, który nie zwraca wartości, ale może być obserwowany i subskrybowany w innym wątku.

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

Lub w Kotlinie:

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

Możesz obserwować i subskrybować tak, jak zwykle:

dataManager.insert(event)
    .subscribeOn(scheduler)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...)
dcr24
źródło
5

Możesz zezwolić na dostęp do bazy danych w głównym wątku, ale tylko w celu debugowania, nie powinieneś tego robić na produkcji.

Oto powód.

Uwaga: Room nie obsługuje dostępu do bazy danych w głównym wątku, chyba że wywołałeś allowMainThreadQueries () w kreatorze, ponieważ może to zablokować interfejs użytkownika na długi czas. Zapytania asynchroniczne - zapytania, które zwracają wystąpienia LiveData lub Flowable - są wyłączone z tej reguły, ponieważ w razie potrzeby asynchronicznie uruchamiają zapytanie w wątku w tle.

Nilesh Rathore
źródło
4

Po prostu możesz użyć tego kodu, aby go rozwiązać:

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

Lub w lambdzie możesz użyć tego kodu:

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

Możesz zastąpić appDb.daoAccess().someJobes()własnym kodem;

Mostafa Rostami
źródło
4

Ponieważ asyncTask są przestarzałe, możemy korzystać z usługi wykonawczej. LUB możesz również użyć ViewModel z LiveData, jak wyjaśniono w innych odpowiedziach.

Aby skorzystać z usługi executora, możesz użyć czegoś takiego jak poniżej.

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

Używany jest Main Looper, dzięki czemu można uzyskać dostęp do elementu UI z onFetchDataSuccesswywołania zwrotnego.

Sasuke Uchiha
źródło
3

Komunikat o błędzie,

Nie można uzyskać dostępu do bazy danych w głównym wątku, ponieważ może to potencjalnie blokować interfejs użytkownika na długi czas.

Jest dość opisowa i dokładna. Pytanie brzmi, jak należy unikać dostępu do bazy danych w głównym wątku. To obszerny temat, ale aby zacząć, przeczytaj o AsyncTask (kliknij tutaj)

-----EDYTOWAĆ----------

Widzę, że masz problemy podczas uruchamiania testu jednostkowego. Masz kilka możliwości rozwiązania tego problemu:

  1. Uruchom test bezpośrednio na komputerze deweloperskim, a nie na urządzeniu z systemem Android (lub emulatorze). Działa to w przypadku testów, które są skoncentrowane na bazie danych i nie ma znaczenia, czy są uruchomione na urządzeniu.

  2. Użyj adnotacji, @RunWith(AndroidJUnit4.class) aby uruchomić test na urządzeniu z systemem Android, ale nie w działaniu z interfejsem użytkownika. Więcej szczegółów na ten temat można znaleźć w tym samouczku

Dale Wilson
źródło
Rozumiem twój punkt widzenia, moje założenie jest takie, że ten sam punkt obowiązuje, gdy próbujesz przetestować dowolną operację db za pośrednictwem JUnit. Jednak w developer.android.com/topic/libraries/architecture/room.html przykładowa metoda testowa writeUserAndReadInList nie wywołuje zapytania wstawiającego w wątku w tle. Czy coś mi tu brakuje? Proszę zasugeruj.
Devarshi
Przepraszam, że przegapiłem fakt, że to był test z problemami. Zmienię odpowiedź, aby dodać więcej informacji.
Dale Wilson
3

Jeśli wolisz zadanie 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();
Hitesh Sahu
źródło
2

Aktualizacja: otrzymałem również tę wiadomość, gdy próbowałem zbudować zapytanie przy użyciu @RawQuery i SupportSQLiteQuery wewnątrz 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

Rozwiązanie: utwórz zapytanie wewnątrz ViewModel i przekaż je do DAO.

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

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

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

        });
    }

Lub...

Nie powinieneś uzyskiwać dostępu do bazy danych bezpośrednio w głównym wątku, na przykład:

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

Należy używać AsyncTask do operacji aktualizacji, dodawania i usuwania.

Przykład:

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

    }
}

Jeśli używasz LiveData do wybranych operacji, nie potrzebujesz AsyncTask.

miłość na żywo
źródło
1

W przypadku szybkich zapytań możesz zezwolić na wykonanie go w wątku interfejsu użytkownika.

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

W moim przypadku musiałem dowiedzieć się, że kliknięty użytkownik na liście istnieje w bazie danych, czy nie. Jeśli nie, utwórz użytkownika i rozpocznij inną czynność

       @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);
        }
    }
Vihaan Verma
źródło
1

Możesz użyć Future i Callable. Nie musisz więc pisać długiego asynctask i możesz wykonywać zapytania bez dodawania funkcji allowMainThreadQueries ().

Moje zapytanie dao: -

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

Moja metoda repozytorium: -

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();
}
początkujący
źródło
Dlatego używamy Callable / Future, ponieważ Android nie pozwala na uruchamianie zapytań w głównym wątku. Zgodnie z pytaniem w powyższym qus
początkujący
1
Chodzi mi o to, że chociaż kod z twojej odpowiedzi tworzy zapytanie w wątku w tle, główny wątek jest zablokowany i czeka na zakończenie zapytania. Więc ostatecznie nie jest dużo lepiej niż allowMainThreadQueries(). Główny wątek nadal zablokowany w obu przypadkach
eugeneek
0

Moim zdaniem właściwą rzeczą jest delegowanie zapytania do wątku IO za pomocą RxJava.

Mam przykład rozwiązania podobnego problemu, z którym właśnie się spotkałem.

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

A jeśli chcemy uogólnić rozwiązanie:

((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()
        );
Rotem Barak
źródło