Estou testando uma amostra com a Room Persistence Library . Eu criei uma Entidade:
@Entity
public class Agent {
@PrimaryKey
public String guid;
public String name;
public String email;
public String password;
public String phone;
public String licence;
}
Criou uma 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);
}
Criou a classe Database:
@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AgentDao agentDao();
}
Banco de dados exposto usando a subclasse abaixo em 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 abaixo a função em minha atividade:
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();
}
}
Infelizmente, na execução do método acima, ele trava com o rastreamento de pilha abaixo:
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 o problema está relacionado à execução da operação db no thread principal. No entanto, o código de teste de amostra fornecido no link acima não é executado em uma thread separada:
@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));
}
Estou perdendo alguma coisa aqui? Como posso fazê-lo executar sem travar? Por favor sugira.
android
crash
kotlin
android-studio-3.0
android-room
Devarshi
fonte
fonte
Respostas:
O acesso ao banco de dados no thread principal que bloqueia a IU é o erro, como disse Dale.
Crie uma classe aninhada estática (para evitar vazamento de memória) em sua Activity estendendo AsyncTask.
Ou você pode criar uma classe final em seu próprio arquivo.
Em seguida, execute-o no método signUpAction (View view):
Em alguns casos, você também pode desejar manter uma referência à AgentAsyncTask em sua atividade para que possa cancelá-la quando a Activity for destruída. Mas você mesmo teria que interromper qualquer transação.
Além disso, sua pergunta sobre o exemplo de teste do Google ... Eles afirmam nessa página da web:
Sem atividade, sem IU.
--EDITAR--
Para quem está se perguntando ... Você tem outras opções. Recomendo dar uma olhada nos novos componentes ViewModel e LiveData. O LiveData funciona muito bem com o Room. https://developer.android.com/topic/libraries/architecture/livedata.html
Outra opção é o RxJava / RxAndroid. Mais poderoso, mas mais complexo do que LiveData. https://github.com/ReactiveX/RxJava
--EDIT 2--
Já que muitas pessoas podem se deparar com essa resposta ... A melhor opção hoje em dia, de modo geral, são os Kotlin Coroutines. A Room agora oferece suporte direto (atualmente em beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01
fonte
Não é recomendado, mas você pode acessar o banco de dados no thread principal com
allowMainThreadQueries()
fonte
allowMainThreadQueries()
o construtor, pois pode bloquear a IU por um longo período de tempo. As consultas assíncronas (consultas que retornamLiveData
ou RxJavaFlowable
) estão isentas desta regra, uma vez que executam de forma assíncrona a consulta em um thread de segundo plano quando necessário.Corrotinas Kotlin (claras e concisas)
AsyncTask é realmente desajeitado. Corrotinas são uma alternativa mais limpa (apenas polvilhe algumas palavras-chave e seu código de sincronização se torna assíncrono).
Dependências (adiciona escopos de co-rotina para componentes de arco):
- Atualizações:
08 de maio de 2019: Room 2.1 agora oferece suporte a
suspend
13 de setembro de 2019: Atualizado para usar o escopo de componentes de arquitetura
fonte
@Query abstract suspend fun count()
palavra-chave usando suspender? Você pode gentilmente analisar esta questão semelhante: stackoverflow.com/questions/48694449/…@Query
função não suspensa protegida . Quando eu adiciono a palavra-chave suspend também ao@Query
método interno , ele realmente falha ao compilar. Parece que é o material mais inteligente para suspender e confronto de Room (como você mencionou em sua outra pergunta, a versão compilada de suspend está retornando uma continuação que o Room não pode controlar).launch
mais nenhuma palavra-chave, você inicia com um escopo, comoGlobalScope.launch
Para todos os amantes de RxJava ou RxAndroid ou RxKotlin lá fora
fonte
override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }
ondeapplySchedulers()
acabei de fazerfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
IntentService#onHandleIntent
porque este método é executado no thread de trabalho, então você não precisaria de nenhum mecanismo de threading para fazer a operação do banco de dados RoomVocê não pode executá-lo no thread principal em vez de usar manipuladores, threads assíncronos ou de trabalho. Um código de amostra está disponível aqui e leia o artigo sobre a biblioteca de salas aqui: Biblioteca de salas do Android
Se você deseja executá-lo no thread principal, que não é o caminho preferido.
Você pode usar este método para obter no tópico principal
Room.inMemoryDatabaseBuilder()
fonte
Com lambda é fácil de executar com AsyncTask
fonte
Com a biblioteca Jetbrains Anko, você pode usar o método doAsync {..} para executar chamadas de banco de dados automaticamente. Isso resolve o problema de verbosidade que você parecia estar tendo com a resposta de mcastro.
Exemplo de uso:
Eu uso isso com freqüência para inserções e atualizações, no entanto, para consultas selecionadas, recomendo usando o fluxo de trabalho RX.
fonte
Basta fazer as operações do banco de dados em um Thread separado. Assim (Kotlin):
fonte
Você deve executar a solicitação em segundo plano. Uma maneira simples poderia ser usando um Executores :
fonte
Uma solução RxJava / Kotlin elegante deve ser usada
Completable.fromCallable
, que lhe dará um Observable que não retorna um valor, mas pode ser observado e inscrito em um thread diferente.Ou em Kotlin:
Você pode observar e se inscrever como faria normalmente:
fonte
Você pode permitir o acesso ao banco de dados no thread principal, mas apenas para fins de depuração, você não deve fazer isso na produção.
Aqui está o motivo.
Observação: o Room não oferece suporte para acesso ao banco de dados no thread principal, a menos que você tenha chamado allowMainThreadQueries () no construtor, pois isso pode bloquear a IU por um longo período de tempo. As consultas assíncronas - consultas que retornam instâncias de LiveData ou Flowable - estão isentas dessa regra porque executam a consulta de forma assíncrona em um thread de segundo plano quando necessário.
fonte
Simplesmente você pode usar este código para resolvê-lo:
Ou em lambda você pode usar este código:
Você pode substituir
appDb.daoAccess().someJobes()
por seu próprio código;fonte
Como asyncTask está obsoleto, podemos usar o serviço do executor. OU você também pode usar ViewModel com LiveData conforme explicado em outras respostas.
Para usar o serviço do executor, você pode usar algo como abaixo.
O Looper principal é usado para que você possa acessar o elemento da IU a partir do
onFetchDataSuccess
retorno de chamada.fonte
A mensagem de erro,
É bastante descritivo e preciso. A questão é como você deve evitar acessar o banco de dados no thread principal. Esse é um tópico enorme, mas para começar, leia sobre AsyncTask (clique aqui)
-----EDITAR----------
Vejo que você está tendo problemas ao executar um teste de unidade. Você tem algumas opções para corrigir isso:
Execute o teste diretamente na máquina de desenvolvimento em vez de em um dispositivo Android (ou emulador). Isso funciona para testes que são centrados em banco de dados e não se importam se estão sendo executados em um dispositivo.
Use a anotação
@RunWith(AndroidJUnit4.class)
para executar o teste no dispositivo Android, mas não em uma atividade com IU. Mais detalhes sobre isso podem ser encontrados neste tutorialfonte
Se você se sentir mais confortável com a tarefa Async :
fonte
Atualização: também recebi esta mensagem quando estava tentando construir uma consulta usando @RawQuery e SupportSQLiteQuery dentro do DAO.
Solução: construa a consulta dentro do ViewModel e passe para o DAO.
Ou...
Você não deve acessar o banco de dados diretamente no thread principal, por exemplo:
Você deve usar AsyncTask para atualizar, adicionar e excluir operações.
Exemplo:
Se você usa LiveData para selecionar operações, não precisa de AsyncTask.
fonte
Para consultas rápidas, você pode permitir espaço para executá-lo no thread de interface do usuário.
No meu caso tive que descobrir se o usuário clicado na lista existe no banco de dados ou não. Caso contrário, crie o usuário e inicie outra atividade
fonte
Você pode usar Future e Callable. Portanto, você não seria obrigado a escrever uma longa asynctask e pode realizar suas consultas sem adicionar allowMainThreadQueries ().
Minha consulta dao: -
Meu método de repositório: -
fonte
allowMainThreadQueries()
. Tópico principal ainda bloqueado em ambos os casosNa minha opinião, a coisa certa a fazer é delegar a consulta a um thread de IO usando RxJava.
Tenho um exemplo de solução para um problema equivalente que acabei de encontrar.
E se quisermos generalizar a solução:
fonte