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.
android
crash
kotlin
android-studio-3.0
android-room
Devarshi
źródło
źródło
Odpowiedzi:
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.
Lub możesz utworzyć ostateczną klasę w swoim własnym pliku.
Następnie wykonaj go w metodzie signUpAction (Widok widoku):
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:
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
źródło
Nie jest to zalecane, ale możesz uzyskać dostęp do bazy danych w głównym wątku za pomocą
allowMainThreadQueries()
źródło
allowMainThreadQueries()
konstruktora, ponieważ może to potencjalnie zablokować interfejs użytkownika na długi czas. Zapytania asynchroniczne (zapytania, które zwracająLiveData
lub RxJavaFlowable
) są wyłączone z tej reguły, ponieważ w razie potrzeby asynchronicznie uruchamiają zapytanie w wątku w tle.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).
Zależności (dodaje coroutine zakresy dla komponentów arch):
- 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
źródło
@Query abstract suspend fun count()
użyciem słowa kluczowego suspend? Czy możesz uprzejmie przyjrzeć się temu podobnemu pytaniu: stackoverflow.com/questions/48694449/…@Query
. Kiedy dodam słowo kluczowe suspend również do@Query
metody 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ć).launch
już słowa kluczowego, uruchamiasz z zakresem, na przykładGlobalScope.launch
Dla wszystkich RxJava lub RxAndroid lub RxKotlin miłośników tam
źródło
override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }
gdzieapplySchedulers()
po prostu zrobićfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
IntentService#onHandleIntent
ponieważ ta metoda jest wykonywana w wątku roboczym, więc nie potrzebujesz tam żadnego mechanizmu wątkowania, aby wykonać operację bazy danych pokojuNie 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
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()
źródło
Dzięki lambdzie jest łatwy do uruchomienia z AsyncTask
źródło
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:
Używam tego często do wstawiania i aktualizacji, jednak w przypadku wybranych zapytań zalecam korzystanie z przepływu pracy RX.
źródło
Po prostu wykonaj operacje na bazie danych w osobnym wątku. W ten sposób (Kotlin):
źródło
Musisz wykonać żądanie w tle. Prostym sposobem może być użycie Executorów :
źródło
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.Lub w Kotlinie:
Możesz obserwować i subskrybować tak, jak zwykle:
źródło
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.
źródło
Po prostu możesz użyć tego kodu, aby go rozwiązać:
Lub w lambdzie możesz użyć tego kodu:
Możesz zastąpić
appDb.daoAccess().someJobes()
własnym kodem;źródło
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.
Używany jest Main Looper, dzięki czemu można uzyskać dostęp do elementu UI z
onFetchDataSuccess
wywołania zwrotnego.źródło
Komunikat o błędzie,
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:
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.
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źródło
Jeśli wolisz zadanie Async :
źródło
Aktualizacja: otrzymałem również tę wiadomość, gdy próbowałem zbudować zapytanie przy użyciu @RawQuery i SupportSQLiteQuery wewnątrz DAO.
Rozwiązanie: utwórz zapytanie wewnątrz ViewModel i przekaż je do DAO.
Lub...
Nie powinieneś uzyskiwać dostępu do bazy danych bezpośrednio w głównym wątku, na przykład:
Należy używać AsyncTask do operacji aktualizacji, dodawania i usuwania.
Przykład:
Jeśli używasz LiveData do wybranych operacji, nie potrzebujesz AsyncTask.
źródło
W przypadku szybkich zapytań możesz zezwolić na wykonanie go w wątku interfejsu użytkownika.
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ść
źródło
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: -
Moja metoda repozytorium: -
źródło
allowMainThreadQueries()
. Główny wątek nadal zablokowany w obu przypadkachMoim 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.
A jeśli chcemy uogólnić rozwiązanie:
źródło