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.
android
crash
kotlin
android-studio-3.0
android-room
Devarshi
quelle
quelle
Antworten:
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.
Oder Sie können eine endgültige Klasse in einer eigenen Datei erstellen.
Führen Sie es dann in der Methode signUpAction (Ansichtsansicht) aus:
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:
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
quelle
Es wird nicht empfohlen, aber Sie können mit auf die Datenbank im Hauptthread zugreifen
allowMainThreadQueries()
quelle
allowMainThreadQueries()
den Builder aufgerufen, da dies möglicherweise die Benutzeroberfläche für einen langen Zeitraum sperrt. Asynchrone Abfragen (Abfragen, die zurückgebenLiveData
oder RxJavaFlowable
) sind von dieser Regel ausgenommen, da sie die Abfrage bei Bedarf asynchron in einem Hintergrundthread ausführen.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).
Abhängigkeiten (fügt Coroutine-Bereiche für Bogenkomponenten hinzu):
- Aktualisierungen:
08. Mai 2019: Raum 2.1 unterstützt jetzt den
suspend
13. September 2019: Aktualisiert, um den Umfang der Architekturkomponenten zu verwenden
quelle
@Query abstract suspend fun count()
Schlüsselwort suspend mit? Können Sie bitte diese ähnliche Frage untersuchen: stackoverflow.com/questions/48694449/…@Query
Funktion aufgerufen hat . Wenn ich das Schlüsselwort suspend auch zur internen@Query
Methode 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).launch
Schlüsselwort mehr, Sie starten mit einem Bereich wieGlobalScope.launch
Für alle RxJava- oder RxAndroid- oder RxKotlin- Liebhaber da draußen
quelle
override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }
woapplySchedulers()
ich gerade machefun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
IntentService#onHandleIntent
da diese Methode im Worker-Thread ausgeführt wird, sodass Sie dort keinen Threading-Mechanismus benötigen, um die Room-Datenbankoperation auszuführenSie 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
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()
quelle
Mit Lambda ist es einfach, mit AsyncTask zu arbeiten
quelle
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:
Ich verwende dies häufig für Einfügungen und Aktualisierungen, jedoch für ausgewählte Abfragen, die ich mithilfe des RX-Workflows empfehle.
quelle
Führen Sie einfach die Datenbankoperationen in einem separaten Thread aus. So (Kotlin):
quelle
Sie müssen die Anforderung im Hintergrund ausführen. Ein einfacher Weg könnte die Verwendung eines Executors sein :
quelle
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.Oder in Kotlin:
Sie können das beobachten und abonnieren wie gewohnt:
quelle
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.
quelle
Sie können diesen Code einfach verwenden, um ihn zu lösen:
Oder in Lambda können Sie diesen Code verwenden:
Sie können durch
appDb.daoAccess().someJobes()
Ihren eigenen Code ersetzen .quelle
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.
Main Looper wird verwendet, damit Sie über einen
onFetchDataSuccess
Rückruf auf das UI-Element zugreifen können .quelle
Die Fehlermeldung,
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:
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.
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 Tutorialquelle
Wenn Sie mit der Async-Aufgabe besser vertraut sind :
quelle
Update: Diese Meldung wurde auch angezeigt, als ich versuchte, eine Abfrage mit @RawQuery und SupportSQLiteQuery im DAO zu erstellen.
Lösung: Erstellen Sie die Abfrage im ViewModel und übergeben Sie sie an das DAO.
Oder...
Sie sollten nicht direkt im Hauptthread auf die Datenbank zugreifen, zum Beispiel:
Sie sollten AsyncTask zum Aktualisieren, Hinzufügen und Löschen von Vorgängen verwenden.
Beispiel:
Wenn Sie LiveData für ausgewählte Vorgänge verwenden, benötigen Sie AsyncTask nicht.
quelle
Für schnelle Abfragen können Sie Platz lassen, um es auf dem UI-Thread auszuführen.
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
quelle
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: -
Meine Repository-Methode: -
quelle
allowMainThreadQueries()
. Haupt-Thread in beiden Fällen noch blockiertMeiner 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.
Und wenn wir die Lösung verallgemeinern wollen:
quelle