J'essaye un échantillon avec la bibliothèque de persistance de pièce . J'ai créé une entité:
@Entity
public class Agent {
@PrimaryKey
public String guid;
public String name;
public String email;
public String password;
public String phone;
public String licence;
}
Création d'une classe 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);
}
Création de la classe Database:
@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AgentDao agentDao();
}
Base de données exposée en utilisant la sous-classe ci-dessous dans 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()
}
}
Fonction implémentée ci-dessous dans mon activité:
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();
}
}
Malheureusement, lors de l'exécution de la méthode ci-dessus, il se bloque avec la trace de pile ci-dessous:
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)
Il semble que ce problème soit lié à l'exécution de l'opération db sur le thread principal. Cependant, l'exemple de code de test fourni dans le lien ci-dessus ne s'exécute pas sur un thread distinct:
@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));
}
Est-ce que je manque quelque chose ici? Comment puis-je l'exécuter sans planter? Veuillez suggérer.
Réponses:
L'accès à la base de données sur le thread principal verrouillant l'interface utilisateur est l'erreur, comme l'a dit Dale.
Créez une classe imbriquée statique (pour éviter les fuites de mémoire) dans votre activité en étendant AsyncTask.
Ou vous pouvez créer une classe finale sur son propre fichier.
Puis exécutez-le dans la méthode signUpAction (View view):
Dans certains cas, vous souhaiterez peut-être également conserver une référence à AgentAsyncTask dans votre activité afin de pouvoir l'annuler lorsque l'activité est détruite. Mais vous devrez interrompre vous-même toutes les transactions.
Aussi, votre question sur l'exemple de test de Google ... Ils indiquent dans cette page Web:
Aucune activité, aucune interface utilisateur.
--ÉDITER--
Pour les gens qui se demandent ... Vous avez d'autres options. Je recommande de jeter un œil aux nouveaux composants ViewModel et LiveData. LiveData fonctionne parfaitement avec Room. https://developer.android.com/topic/libraries/architecture/livedata.html
Une autre option est le RxJava / RxAndroid. Plus puissant mais plus complexe que LiveData. https://github.com/ReactiveX/RxJava
--EDIT 2--
Puisque beaucoup de gens peuvent trouver cette réponse ... La meilleure option de nos jours, en général, est Kotlin Coroutines. Room le prend désormais en charge directement (actuellement en version bêta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01
la source
Ce n'est pas recommandé mais vous pouvez accéder à la base de données sur le thread principal avec
allowMainThreadQueries()
la source
allowMainThreadQueries()
le générateur, car il pourrait potentiellement verrouiller l'interface utilisateur pendant une longue période. Les requêtes asynchrones (requêtes qui retournentLiveData
ou RxJavaFlowable
) sont exemptées de cette règle car elles exécutent la requête de manière asynchrone sur un thread d'arrière-plan lorsque cela est nécessaire.Kotlin Coroutines (clair et concis)
AsyncTask est vraiment maladroit. Les coroutines sont une alternative plus propre (il suffit de saupoudrer quelques mots-clés et votre code de synchronisation devient asynchrone).
Dépendances (ajoute des portées coroutines pour les composants de l'arche):
- Mises à jour:
8 mai 2019: la salle 2.1 prend désormais en charge
suspend
13 septembre 2019: mise à jour pour utiliser la portée des composants d'architecture
la source
@Query abstract suspend fun count()
en utilisant le mot clé suspend? Pouvez-vous s'il vous plaît examiner cette question similaire: stackoverflow.com/questions/48694449/…@Query
. Lorsque j'ajoute le mot-clé suspend à la@Query
méthode interne, la compilation échoue en effet. Cela ressemble à l'intelligent sous le capot pour suspendre et clash de salle (comme vous le mentionnez dans votre autre question, la version compilée de suspension renvoie une suite que Room ne peut pas gérer).launch
plus de mot-clé, vous lancez avec une portée, telle queGlobalScope.launch
Pour tous les RxJava ou RxAndroid ou RxKotlin amateurs là - bas
la source
override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }
là oùapplySchedulers()
je viens de fairefun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
IntentService#onHandleIntent
parce que cette méthode s'exécute sur le thread de travail, vous n'avez donc pas besoin d'un mécanisme de thread pour effectuer l'opération de base de données de la salleVous ne pouvez pas l'exécuter sur le thread principal à la place, utilisez des gestionnaires, des threads asynchrones ou de travail. Un exemple de code est disponible ici et lisez l'article sur la bibliothèque de la salle ici: Bibliothèque de la salle d'Android
Si vous souhaitez l'exécuter sur le thread principal, ce n'est pas la manière préférée.
Vous pouvez utiliser cette méthode pour réaliser sur le fil principal
Room.inMemoryDatabaseBuilder()
la source
Avec lambda, il est facile à exécuter avec AsyncTask
la source
Avec la bibliothèque Jetbrains Anko, vous pouvez utiliser la méthode doAsync {..} pour exécuter automatiquement des appels de base de données. Cela résout le problème de verbosité que vous sembliez avoir avec la réponse de mcastro.
Exemple d'utilisation:
Je l'utilise fréquemment pour les insertions et les mises à jour, mais pour certaines requêtes, je recommande d'utiliser le flux de travail RX.
la source
Effectuez simplement les opérations de base de données dans un thread séparé. Comme ça (Kotlin):
la source
Vous devez exécuter la demande en arrière-plan. Un moyen simple pourrait être d'utiliser un exécuteur :
la source
Une solution RxJava / Kotlin élégante est à utiliser
Completable.fromCallable
, qui vous donnera un Observable qui ne retourne pas de valeur, mais peut être observé et abonné sur un thread différent.Ou à Kotlin:
Vous pouvez l'observer et vous abonner comme vous le feriez habituellement:
la source
Vous pouvez autoriser l'accès à la base de données sur le thread principal, mais uniquement à des fins de débogage, vous ne devriez pas le faire en production.
Voici la raison.
Remarque: Room ne prend pas en charge l'accès à la base de données sur le thread principal, sauf si vous avez appelé allowMainThreadQueries () sur le générateur, car il peut verrouiller l'interface utilisateur pendant une longue période. Les requêtes asynchrones (requêtes qui renvoient des instances de LiveData ou Flowable) sont exemptées de cette règle car elles exécutent la requête de manière asynchrone sur un thread d'arrière-plan lorsque cela est nécessaire.
la source
Vous pouvez simplement utiliser ce code pour le résoudre:
Ou en lambda, vous pouvez utiliser ce code:
Vous pouvez remplacer
appDb.daoAccess().someJobes()
par votre propre code;la source
Comme asyncTask est obsolète, nous pouvons utiliser le service exécuteur. OU vous pouvez également utiliser ViewModel avec LiveData comme expliqué dans d'autres réponses.
Pour utiliser le service exécuteur, vous pouvez utiliser quelque chose comme ci-dessous.
Main Looper est utilisé, de sorte que vous pouvez accéder à l'élément d'interface utilisateur à partir du
onFetchDataSuccess
rappel.la source
Le message d'erreur,
Est assez descriptif et précis. La question est de savoir comment éviter d'accéder à la base de données sur le thread principal. C'est un sujet énorme, mais pour commencer, lisez AsyncTask (cliquez ici)
-----ÉDITER----------
Je vois que vous rencontrez des problèmes lorsque vous exécutez un test unitaire. Vous avez plusieurs choix pour résoudre ce problème:
Exécutez le test directement sur la machine de développement plutôt que sur un appareil Android (ou un émulateur). Cela fonctionne pour les tests centrés sur la base de données et ne se soucient pas vraiment de savoir s'ils s'exécutent sur un appareil.
Utilisez l'annotation
@RunWith(AndroidJUnit4.class)
pour exécuter le test sur l'appareil Android, mais pas dans une activité avec une interface utilisateur. Plus de détails à ce sujet peuvent être trouvés dans ce tutoriella source
Si vous êtes plus à l'aise avec la tâche Async :
la source
Mise à jour: J'ai également reçu ce message lorsque j'essayais de créer une requête en utilisant @RawQuery et SupportSQLiteQuery dans le DAO.
Solution: créez la requête à l'intérieur du ViewModel et transmettez-la au DAO.
Ou...
Vous ne devez pas accéder à la base de données directement sur le thread principal, par exemple:
Vous devez utiliser AsyncTask pour les opérations de mise à jour, d'ajout et de suppression.
Exemple:
Si vous utilisez LiveData pour sélectionner des opérations, vous n'avez pas besoin d'AsyncTask.
la source
Pour les requêtes rapides, vous pouvez autoriser la salle à l'exécuter sur le fil de l'interface utilisateur.
Dans mon cas, j'ai dû comprendre que l'utilisateur cliqué dans la liste existe ou non dans la base de données. Sinon, créez l'utilisateur et démarrez une autre activité
la source
Vous pouvez utiliser Future et Callable. Ainsi, vous ne seriez pas obligé d'écrire une longue asynctask et pouvez effectuer vos requêtes sans ajouter allowMainThreadQueries ().
Ma requête dao: -
Ma méthode de dépôt: -
la source
allowMainThreadQueries()
. Fil principal toujours bloqué dans les deux casÀ mon avis, la bonne chose à faire est de déléguer la requête à un thread IO en utilisant RxJava.
J'ai un exemple de solution à un problème équivalent que je viens de rencontrer.
Et si nous voulons généraliser la solution:
la source