Android Room - einfache Auswahlabfrage - Zugriff auf Datenbank im Hauptthread nicht möglich

124

Ich versuche ein Beispiel mit der Room Persistence Library . Ich habe eine Entität erstellt:

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

Erstellt eine DAO-Klasse:

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

Erstellt die Datenbankklasse:

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

Exponierte Datenbank unter Verwendung der folgenden Unterklasse in Kotlin:

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

Die folgende Funktion wurde in meiner Aktivität implementiert:

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

Leider stürzt es bei Ausführung der obigen Methode mit der folgenden Stapelverfolgung ab:

    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) 

Dieses Problem hängt anscheinend mit der Ausführung der Datenbankoperation auf dem Hauptthread zusammen. Der im obigen Link angegebene Beispieltestcode wird jedoch nicht in einem separaten Thread ausgeführt:

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

Vermisse ich hier etwas? Wie kann ich es ohne Absturz ausführen lassen? Bitte vorschlagen.

Devarshi
quelle
1
Obwohl für Kotlin geschrieben, erklärt dieser Artikel das zugrunde liegende Problem sehr gut!
Peter Lehnhardt
Werfen
nnyerges
Schau dir diese Antwort an. Diese Antwort funktioniert für mich stackoverflow.com/a/51720501/7655085
Somen Tushir

Antworten:

60

Der Datenbankzugriff auf den Hauptthread, der die Benutzeroberfläche sperrt, ist der Fehler, wie Dale sagte.

Erstellen Sie eine statisch verschachtelte Klasse (um Speicherverluste zu verhindern) in Ihrer Aktivität, die AsyncTask erweitert.

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

Oder Sie können eine endgültige Klasse in einer eigenen Datei erstellen.

Führen Sie es dann in der Methode signUpAction (Ansichtsansicht) aus:

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

In einigen Fällen möchten Sie möglicherweise auch einen Verweis auf die AgentAsyncTask in Ihrer Aktivität enthalten, damit Sie ihn abbrechen können, wenn die Aktivität zerstört wird. Sie müssten jedoch alle Transaktionen selbst unterbrechen.

Auch Ihre Frage zum Testbeispiel von Google ... Sie geben auf dieser Webseite Folgendes an:

Der empfohlene Ansatz zum Testen Ihrer Datenbankimplementierung besteht darin, einen JUnit-Test zu schreiben, der auf einem Android-Gerät ausgeführt wird. Da für diese Tests keine Aktivität erstellt werden muss, sollten sie schneller ausgeführt werden können als Ihre UI-Tests.

Keine Aktivität, keine Benutzeroberfläche.

--BEARBEITEN--

Für Leute, die sich fragen ... Sie haben andere Möglichkeiten. Ich empfehle, einen Blick in die neuen ViewModel- und LiveData-Komponenten zu werfen. LiveData funktioniert hervorragend mit Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Eine weitere Option ist RxJava / RxAndroid. Leistungsstärker, aber komplexer als LiveData. https://github.com/ReactiveX/RxJava

--EDIT 2--

Da viele Leute auf diese Antwort stoßen können ... Die beste Option heutzutage ist im Allgemeinen Kotlin Coroutines. Room unterstützt es jetzt direkt (derzeit in der Beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

mcastro
quelle
28
Soll ich diese riesige asynchrone Aufgabe (die Sie im Codebeispiel vorgeschlagen haben) jedes Mal ausführen, wenn ich auf die Datenbank zugreifen muss? Es sind ungefähr ein Dutzend Codezeilen anstelle einer, um Daten von db abzurufen. Sie haben vorgeschlagen, auch eine neue Klasse zu erstellen. Bedeutet dies jedoch, dass ich für jeden Datenbankaufruf zum Einfügen / Auswählen eine neue AsyncTask-Klasse erstellen muss?
Piotrek
7
Sie haben einige andere Möglichkeiten, ja. Vielleicht möchten Sie einen Blick auf die neuen ViewModel- und LiveData-Komponenten werfen. Wenn Sie die LiveData verwenden, benötigen Sie die AsyncTask nicht. Das Objekt wird benachrichtigt, wenn sich etwas ändert. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… Es gibt auch AndroidRx (obwohl es ziemlich genau das tut, was LiveData tut) und Promises. Wenn Sie die AsyncTask verwenden, können Sie die Architektur so gestalten, dass Sie mehrere Vorgänge in eine AsyncTask aufnehmen oder jeweils trennen können.
Mcastro
@Piotrek - Kotlin hat jetzt Async eingebrannt (obwohl es als experimentell markiert ist). Siehe meine Antwort, die vergleichsweise trivial ist. Samuel Roberts Antwort bezieht sich auf Rx. Ich sehe hier keine LiveData-Antwort, aber das ist möglicherweise die bessere Wahl, wenn Sie eine beobachtbare Antwort wünschen.
AjahnCharles
Die Verwendung von regulärem AsyncTask scheint jetzt noch nicht einmal zu funktionieren und es wird immer noch die illegale Ausnahme vom Staat
angezeigt
@Piotrek Sie sagen mir, dass Sie es gewohnt sind, während Ihrer gesamten Erfahrung den Datenbankzugriff auf den Hauptthread auszuführen?
Mr5 5.
142

Es wird nicht empfohlen, aber Sie können mit auf die Datenbank im Hauptthread zugreifen allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
mpolat
quelle
9
Room erlaubt keinen Zugriff auf die Datenbank im Hauptthread, es sei denn, Sie haben allowMainThreadQueries()den Builder aufgerufen, da dies möglicherweise die Benutzeroberfläche für einen langen Zeitraum sperrt. Asynchrone Abfragen (Abfragen, die zurückgeben LiveDataoder RxJava Flowable) sind von dieser Regel ausgenommen, da sie die Abfrage bei Bedarf asynchron in einem Hintergrundthread ausführen.
PRANAY
4
Vielen Dank, dies ist sehr nützlich für die Migration, da ich testen möchte,
ob
5
@JideGuruTheProgrammer Nein, sollte es nicht. In einigen Fällen kann dies Ihre App erheblich verlangsamen. Die Operationen sollten asynchron ausgeführt werden.
Alex
@Alex Es gibt nie einen Grund für eine Abfrage im Hauptthread?
Justin Meiners
1
@JustinMeiners es ist nur eine schlechte Praxis, du wirst es in Ordnung machen, solange die Datenbank klein bleibt.
lasec0203
51

Kotlin Coroutines (klar und prägnant)

AsyncTask ist wirklich klobig. Coroutinen sind eine sauberere Alternative (streuen Sie einfach ein paar Schlüsselwörter und Ihr Synchronisierungscode wird asynchron).

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

Abhängigkeiten (fügt Coroutine-Bereiche für Bogenkomponenten hinzu):

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

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

- Aktualisierungen:
08. Mai 2019: Raum 2.1 unterstützt jetzt den suspend
13. September 2019: Aktualisiert, um den Umfang der Architekturkomponenten zu verwenden

AjahnCharles
quelle
1
Haben Sie einen Kompilierungsfehler mit dem @Query abstract suspend fun count()Schlüsselwort suspend mit? Können Sie bitte diese ähnliche Frage untersuchen: stackoverflow.com/questions/48694449/…
Robin
@ Robin - Ja, das tue ich. Mein Fehler; Ich habe suspend für eine öffentliche (nicht kommentierte) DAO-Methode verwendet, die eine geschützte Non-Suspend- @QueryFunktion aufgerufen hat . Wenn ich das Schlüsselwort suspend auch zur internen @QueryMethode hinzufüge, kann es tatsächlich nicht kompiliert werden. Es sieht aus wie das clevere Zeug unter der Haube für Suspend & Room Clash (wie Sie in Ihrer anderen Frage erwähnen, gibt die kompilierte Version von Suspend eine Fortsetzung zurück, die Room nicht verarbeiten kann).
AjahnCharles
Macht sehr viel Sinn. Ich werde es stattdessen mit Coroutine-Funktionen aufrufen.
Robin
1
@Robin - FYI sie hinzugefügt Unterstützung für Suspend in Raum 2.1 :)
AjahnCharles
Anscheinend gibt es kein launchSchlüsselwort mehr, Sie starten mit einem Bereich wieGlobalScope.launch
nasch
48

Für alle RxJava- oder RxAndroid- oder RxKotlin- Liebhaber da draußen

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }
Samuel Robert
quelle
3
Wenn ich diesen Code in eine Methode einfüge, wie kann ich das Ergebnis der Datenbankoperation zurückgeben?
Eggakin Baconwalker
@ EgakinBaconwalker Ich habe, override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }wo applySchedulers()ich gerade machefun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
Noloman
Dies funktioniert bei IntentService nicht. Weil IntentService ausgeführt wird, sobald der Thread abgeschlossen ist.
Umang Kothari
1
@UmangKothari Sie erhalten keine Ausnahme, wenn Sie aktiviert sind, IntentService#onHandleIntentda diese Methode im Worker-Thread ausgeführt wird, sodass Sie dort keinen Threading-Mechanismus benötigen, um die Room-Datenbankoperation auszuführen
Samuel Robert,
@ SamuelRobert, ja stimme mir schlecht zu. Ich habe es vergessen.
Umang Kothari
27

Sie können es nicht auf dem Hauptthread ausführen, sondern verwenden Handler, asynchrone oder funktionierende Threads. Ein Beispielcode ist hier verfügbar und Sie können den Artikel über die Raumbibliothek hier lesen: Android's Room Library

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

Wenn Sie es auf einem Hauptthread ausführen möchten, der nicht bevorzugt wird.

Sie können diese Methode verwenden, um auf Haupt-Thread zu erreichen Room.inMemoryDatabaseBuilder()

Rizvan
quelle
Wie kann ich die Daten von dieser Methode zurückgeben, wenn ich diese Methode zum Abrufen der Daten verwende (in diesem Fall nur getAllUsers ())? Es erscheint ein Fehler, wenn ich das Wort "return" in den "run" setze.
Eggakin Baconwalker
1
Erstellen Sie irgendwo eine Schnittstellenmethode und fügen Sie eine anonyme Klasse hinzu, um Daten von hier abzurufen.
Rizvan
1
Dies ist die einfachste Lösung zum Einfügen / Aktualisieren.
Beer Me
12

Mit Lambda ist es einfach, mit AsyncTask zu arbeiten

 AsyncTask.execute(() -> //run your query here );
Afjalur Rahman Rana
quelle
2
Das ist praktisch, danke. Kotlin ist übrigens noch einfacher: AsyncTask.execute {}
alexrnov
1
Aber wie kommt man mit dieser Methode zum Ergebnis?
LeeCoder
11

Mit der Jetbrains Anko-Bibliothek können Sie die doAsync {..} -Methode verwenden, um Datenbankaufrufe automatisch auszuführen. Dies behebt das Ausführlichkeitsproblem, das Sie anscheinend mit der Antwort von mcastro hatten.

Anwendungsbeispiel:

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

Ich verwende dies häufig für Einfügungen und Aktualisierungen, jedoch für ausgewählte Abfragen, die ich mithilfe des RX-Workflows empfehle.

Arsala Bangash
quelle
7

Führen Sie einfach die Datenbankoperationen in einem separaten Thread aus. So (Kotlin):

Thread {
   //Do your database´s operations here
}.start()
Engel Enrique Herrera Crespo
quelle
6

Sie müssen die Anforderung im Hintergrund ausführen. Ein einfacher Weg könnte die Verwendung eines Executors sein :

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}
Phil
quelle
Wie geben Sie das Ergebnis zurück?
LeeCoder
5

Es wird eine elegante RxJava / Kotlin-Lösung verwendet Completable.fromCallable, mit der Sie ein Observable erhalten, das keinen Wert zurückgibt , aber in einem anderen Thread beobachtet und abonniert werden kann.

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

Oder in Kotlin:

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

Sie können das beobachten und abonnieren wie gewohnt:

dataManager.insert(event)
    .subscribeOn(scheduler)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...)
dcr24
quelle
5

Sie können den Datenbankzugriff auf den Hauptthread zulassen, aber nur zu Debugging-Zwecken. Sie sollten dies nicht in der Produktion tun.

Hier ist der Grund.

Hinweis: Room unterstützt den Datenbankzugriff auf den Hauptthread nur, wenn Sie im Builder allowMainThreadQueries () aufgerufen haben, da die Benutzeroberfläche möglicherweise für einen längeren Zeitraum gesperrt wird. Asynchrone Abfragen - Abfragen, die Instanzen von LiveData oder Flowable zurückgeben - sind von dieser Regel ausgenommen, da sie die Abfrage bei Bedarf asynchron in einem Hintergrundthread ausführen.

Nilesh Rathore
quelle
4

Sie können diesen Code einfach verwenden, um ihn zu lösen:

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

Oder in Lambda können Sie diesen Code verwenden:

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

Sie können durch appDb.daoAccess().someJobes()Ihren eigenen Code ersetzen .

Mostafa Rostami
quelle
4

Da asyncTask veraltet sind, können wir den Executor-Service verwenden. ODER Sie können ViewModel auch mit LiveData verwenden wie in anderen Antworten erläutert.

Für die Verwendung des Executor-Dienstes können Sie Folgendes verwenden.

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

Main Looper wird verwendet, damit Sie über einen onFetchDataSuccessRückruf auf das UI-Element zugreifen können .

Sasuke Uchiha
quelle
3

Die Fehlermeldung,

Der Zugriff auf die Datenbank im Hauptthread ist nicht möglich, da die Benutzeroberfläche möglicherweise für längere Zeit gesperrt wird.

Ist sehr beschreibend und genau. Die Frage ist, wie Sie den Zugriff auf die Datenbank im Hauptthread vermeiden sollten. Das ist ein großes Thema, aber um loszulegen, lesen Sie darüber AsyncTask (hier klicken).

-----BEARBEITEN----------

Ich sehe, dass Sie Probleme haben, wenn Sie einen Komponententest durchführen. Sie haben mehrere Möglichkeiten, um dies zu beheben:

  1. Führen Sie den Test direkt auf dem Entwicklungscomputer und nicht auf einem Android-Gerät (oder Emulator) aus. Dies funktioniert für Tests, die datenbankzentriert sind und sich nicht wirklich darum kümmern, ob sie auf einem Gerät ausgeführt werden.

  2. Verwenden Sie die Anmerkung @RunWith(AndroidJUnit4.class) , um den Test auf dem Android-Gerät auszuführen, jedoch nicht in einer Aktivität mit einer Benutzeroberfläche. Weitere Details hierzu finden Sie in diesem Tutorial

Dale Wilson
quelle
Ich verstehe Ihren Punkt, meine Annahme ist, dass der gleiche Punkt gültig ist, wenn Sie versuchen, eine Datenbankoperation über JUnit zu testen. Jedoch in developer.android.com/topic/libraries/architecture/room.html die Probentestmethode writeUserAndReadInList nicht invoke dem Einsatz Abfrage auf Hintergrund - Thread. Vermisse ich hier etwas? Bitte vorschlagen.
Devarshi
Entschuldigung, ich habe die Tatsache verpasst, dass dies der Test war, der Probleme hatte. Ich werde meine Antwort bearbeiten, um weitere Informationen hinzuzufügen.
Dale Wilson
3

Wenn Sie mit der Async-Aufgabe besser vertraut sind :

  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
quelle
2

Update: Diese Meldung wurde auch angezeigt, als ich versuchte, eine Abfrage mit @RawQuery und SupportSQLiteQuery im DAO zu erstellen.

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

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

Lösung: Erstellen Sie die Abfrage im ViewModel und übergeben Sie sie an das DAO.

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

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

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

        });
    }

Oder...

Sie sollten nicht direkt im Hauptthread auf die Datenbank zugreifen, zum Beispiel:

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

Sie sollten AsyncTask zum Aktualisieren, Hinzufügen und Löschen von Vorgängen verwenden.

Beispiel:

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

    }
}

Wenn Sie LiveData für ausgewählte Vorgänge verwenden, benötigen Sie AsyncTask nicht.

Live-Liebe
quelle
1

Für schnelle Abfragen können Sie Platz lassen, um es auf dem UI-Thread auszuführen.

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

In meinem Fall musste ich herausfinden, ob der angeklickte Benutzer in der Liste in der Datenbank vorhanden ist oder nicht. Wenn nicht, erstellen Sie den Benutzer und starten Sie eine andere Aktivität

       @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
quelle
1

Sie können Future und Callable verwenden. Sie müssten also keine lange Asynctask schreiben und können Ihre Abfragen ausführen, ohne allowMainThreadQueries () hinzuzufügen.

Meine Dao-Anfrage: -

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

Meine Repository-Methode: -

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();
}
Anfänger
quelle
Aus diesem Grund verwenden wir Callable / Future, da Android nicht zulässt, dass Abfragen im Hauptthread ausgeführt werden. Wie im Qus oben gefragt
Anfänger
1
Ich meine, obwohl Code aus Ihrer Antwort eine Abfrage im Hintergrund-Thread macht, ist der Haupt-Thread blockiert und wartet, wenn die Abfrage beendet ist. Am Ende ist es also nicht viel besser als allowMainThreadQueries(). Haupt-Thread in beiden Fällen noch blockiert
Eugenek
0

Meiner Meinung nach ist es richtig, die Abfrage mit RxJava an einen E / A-Thread zu delegieren.

Ich habe ein Beispiel für eine Lösung für ein gleichwertiges Problem, auf das ich gerade gestoßen bin.

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

Und wenn wir die Lösung verallgemeinern wollen:

((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
quelle