Estoy probando una muestra con Room Persistence Library . Creé una entidad:
@Entity
public class Agent {
@PrimaryKey
public String guid;
public String name;
public String email;
public String password;
public String phone;
public String licence;
}
Creó una clase 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);
}
Creó la clase de base de datos:
@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AgentDao agentDao();
}
Base de datos expuesta usando la siguiente subclase en 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()
}
}
Implementado a continuación la función en mi actividad:
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();
}
}
Desafortunadamente, al ejecutar el método anterior, se bloquea con el seguimiento de la pila inferior:
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)
Parece que ese problema está relacionado con la ejecución de la operación db en el hilo principal. Sin embargo, el código de prueba de muestra proporcionado en el enlace anterior no se ejecuta en un hilo separado:
@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));
}
¿Me estoy perdiendo algo por aquí? ¿Cómo puedo hacer que se ejecute sin fallar? Por favor recomiende.
android
crash
kotlin
android-studio-3.0
android-room
Devarshi
fuente
fuente
Respuestas:
El acceso a la base de datos en el hilo principal que bloquea la interfaz de usuario es el error, como dijo Dale.
Cree una clase anidada estática (para evitar la pérdida de memoria) en su actividad extendiendo AsyncTask.
O puede crear una clase final en su propio archivo.
Luego ejecútelo en el método signUpAction (View view):
En algunos casos, es posible que también desee mantener una referencia a AgentAsyncTask en su actividad para poder cancelarla cuando se destruya la actividad. Pero tendría que interrumpir las transacciones usted mismo.
Además, tu pregunta sobre el ejemplo de prueba de Google ... Dicen en esa página web:
Sin actividad, sin interfaz de usuario.
--EDITAR--
Para las personas que se preguntan ... Tienes otras opciones. Recomiendo echar un vistazo a los nuevos componentes ViewModel y LiveData. LiveData funciona muy bien con Room. https://developer.android.com/topic/libraries/architecture/livedata.html
Otra opción es RxJava / RxAndroid. Más potente pero más complejo que LiveData. https://github.com/ReactiveX/RxJava
--EDIT 2--
Dado que muchas personas pueden encontrar esta respuesta ... La mejor opción hoy en día, en términos generales, es Kotlin Coroutines. Room ahora lo admite directamente (actualmente en versión beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01
fuente
No se recomienda, pero puede acceder a la base de datos en el hilo principal con
allowMainThreadQueries()
fuente
allowMainThreadQueries()
al constructor porque podría bloquear la interfaz de usuario durante un largo período de tiempo. Las consultas asincrónicas (consultas que devuelvenLiveData
o RxJavaFlowable
) están exentas de esta regla, ya que ejecutan de forma asincrónica la consulta en un hilo en segundo plano cuando es necesario.Corutinas de Kotlin (claras y concisas)
AsyncTask es realmente torpe. Las corrutinas son una alternativa más limpia (solo agregue un par de palabras clave y su código de sincronización se volverá asíncrono).
Dependencias (agrega alcances de corrutina para componentes de arco):
- Actualizaciones:
08-May-2019: Room 2.1 ahora es compatible con
suspend
13-Sep-2019: actualizado para usar el alcance de los componentes de Arquitectura
fuente
@Query abstract suspend fun count()
uso de la palabra clave suspend? ¿Puede consultar esta pregunta similar ?: stackoverflow.com/questions/48694449/…@Query
. Cuando agrego la palabra clave suspend al@Query
método interno también, de hecho, no se compila. Parece lo inteligente bajo el capó para suspender y choque de sala (como mencionaste en tu otra pregunta, la versión compilada de suspender devuelve una continuación que Room no puede manejar).launch
palabra clave, seGlobalScope.launch
Para todos los RxJava o RxAndroid o RxKotlin amantes hacia fuera allí
fuente
override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }
dondeapplySchedulers()
acabo de hacerfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
IntentService#onHandleIntent
porque este método se ejecuta en el hilo de trabajo, por lo que no necesitaría ningún mecanismo de subprocesamiento allí para realizar la operación de la base de datos de la salaNo puede ejecutarlo en el subproceso principal, en su lugar, use controladores, subprocesos asíncronos o de trabajo. Un código de muestra está disponible aquí y lea el artículo sobre la biblioteca de salas aquí: Biblioteca de salas de Android
Si desea ejecutarlo en el hilo principal, que no es la forma preferida.
Puede utilizar este método para lograr en el hilo principal
Room.inMemoryDatabaseBuilder()
fuente
Con lambda es fácil de ejecutar con AsyncTask
fuente
Con la biblioteca Jetbrains Anko, puede usar el método doAsync {..} para ejecutar automáticamente llamadas a la base de datos. Esto soluciona el problema de verbosidad que parecía haber tenido con la respuesta de mcastro.
Uso de ejemplo:
Utilizo esto con frecuencia para inserciones y actualizaciones, sin embargo, para consultas seleccionadas, recomiendo usar el flujo de trabajo RX.
fuente
Simplemente realice las operaciones de la base de datos en un hilo separado. Así (Kotlin):
fuente
Tienes que ejecutar la solicitud en segundo plano. Una forma sencilla podría ser utilizar Ejecutores :
fuente
Se debe utilizar una elegante solución RxJava / Kotlin
Completable.fromCallable
, que le dará un Observable que no devuelve un valor, pero que puede ser observado y suscrito en un hilo diferente.O en Kotlin:
Puede observar y suscribirse como lo haría normalmente:
fuente
Puede permitir el acceso a la base de datos en el hilo principal, pero solo con fines de depuración, no debe hacer esto en producción.
He aquí la razón.
Nota: Room no admite el acceso a la base de datos en el subproceso principal a menos que haya llamado allowMainThreadQueries () en el constructor porque podría bloquear la interfaz de usuario durante un período de tiempo prolongado. Las consultas asincrónicas (consultas que devuelven instancias de LiveData o Flowable) están exentas de esta regla porque ejecutan de forma asincrónica la consulta en un subproceso en segundo plano cuando es necesario.
fuente
Simplemente puedes usar este código para resolverlo:
O en lambda puede usar este código:
Puede reemplazarlo
appDb.daoAccess().someJobes()
con su propio código;fuente
Como asyncTask está en desuso, podemos usar el servicio ejecutor. O también puede usar ViewModel con LiveData como se explica en otras respuestas.
Para usar el servicio ejecutor, puede usar algo como a continuación.
Se utiliza Main Looper para que pueda acceder al elemento de la interfaz de usuario desde la
onFetchDataSuccess
devolución de llamada.fuente
El mensaje de error
Es bastante descriptivo y preciso. La pregunta es cómo evitar el acceso a la base de datos en el hilo principal. Ese es un tema enorme, pero para comenzar, lea sobre AsyncTask (haga clic aquí)
-----EDITAR----------
Veo que tiene problemas cuando ejecuta una prueba unitaria. Tiene un par de opciones para solucionar este problema:
Ejecute la prueba directamente en la máquina de desarrollo en lugar de en un dispositivo Android (o emulador). Esto funciona para pruebas que se centran en bases de datos y no les importa si se están ejecutando en un dispositivo.
Use la anotación
@RunWith(AndroidJUnit4.class)
para ejecutar la prueba en el dispositivo Android, pero no en una actividad con una interfaz de usuario. Se pueden encontrar más detalles sobre esto en este tutorial.fuente
Si se siente más cómodo con la tarea Async :
fuente
Actualización: también recibí este mensaje cuando intentaba crear una consulta usando @RawQuery y SupportSQLiteQuery dentro del DAO.
Solución: cree la consulta dentro de ViewModel y páselo al DAO.
O...
No debe acceder a la base de datos directamente en el hilo principal, por ejemplo:
Debe usar AsyncTask para actualizar, agregar y eliminar operaciones.
Ejemplo:
Si usa LiveData para operaciones seleccionadas, no necesita AsyncTask.
fuente
Para consultas rápidas, puede dejar espacio para ejecutarlo en el hilo de la interfaz de usuario.
En mi caso, tuve que averiguar que el usuario seleccionado en la lista existe en la base de datos o no. Si no es así, cree el usuario y comience otra actividad.
fuente
Puede utilizar Future y Callable. Por lo tanto, no se le pedirá que escriba una asynctask larga y puede realizar sus consultas sin agregar allowMainThreadQueries ().
Mi consulta dao: -
Mi método de repositorio: -
fuente
allowMainThreadQueries()
. El hilo principal todavía está bloqueado en ambos casosEn mi opinión, lo correcto es delegar la consulta a un hilo IO usando RxJava.
Tengo un ejemplo de una solución a un problema equivalente que acabo de encontrar.
Y si queremos generalizar la solución:
fuente