Sala Android - consulta de seleção simples - Não é possível acessar o banco de dados no thread principal


126

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.


1
Embora escrito para Kotlin, este artigo explica muito bem o problema subjacente!
Peter Lehnhardt


Veja esta resposta. Esta resposta funciona para mim stackoverflow.com/a/51720501/7655085
Somen Tushir

Respostas:


59

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.

private static class AgentAsyncTask extends AsyncTask<Void, Void, Integer> {

    //Prevent leak
    private WeakReference<Activity> weakActivity;
    private String email;
    private String phone;
    private String license;

    public AgentAsyncTask(Activity activity, String email, String phone, String license) {
        weakActivity = new WeakReference<>(activity);
        this.email = email;
        this.phone = phone;
        this.license = license;
    }

    @Override
    protected Integer doInBackground(Void... params) {
        AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
        return agentDao.agentsCount(email, phone, license);
    }

    @Override
    protected void onPostExecute(Integer agentsCount) {
        Activity activity = weakActivity.get();
        if(activity == null) {
            return;
        }

        if (agentsCount > 0) {
            //2: If it already exists then prompt user
            Toast.makeText(activity, "Agent already exists!", Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(activity, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
            activity.onBackPressed();
        }
    }
}

Ou você pode criar uma classe final em seu próprio arquivo.

Em seguida, execute-o no método signUpAction (View view):

new AgentAsyncTask(this, email, phone, license).execute();

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:

A abordagem recomendada para testar sua implementação de banco de dados é escrever um teste JUnit que é executado em um dispositivo Android. Como esses testes não exigem a criação de uma atividade, eles devem ser mais rápidos de executar do que os testes de IU.

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


31
Devo fazer essa enorme tarefa assíncrona (que você propôs no exemplo de código) toda vez que precisar acessar o banco de dados? É uma dúzia ou mais de linhas de código em vez de uma para obter alguns dados do banco de dados. Você propôs também a criação de uma nova classe, mas isso significa que preciso criar uma nova classe AsyncTask para cada inserção / seleção de chamada de banco de dados?
Piotrek

7
Você tem algumas outras opções, sim. Você pode querer dar uma olhada nos novos componentes ViewModel e LiveData. Ao usar o LiveData você não precisa do AsyncTask, o objeto será notificado sempre que algo mudar. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… Há também AndroidRx (embora faça praticamente o que o LiveData faz) e Promises. Ao usar o AsyncTask, você pode arquitetar de forma que inclua várias operações em um AsyncTask ou separe cada um deles.
mcastro

@Piotrek - Kotlin agora tem assíncrono integrado (embora seja marcado como experimental). Veja minha resposta, que é relativamente trivial. A resposta de Samuel Robert cobre Rx. Não vejo uma resposta LiveData aqui, mas essa pode ser a melhor escolha se você quiser um observável.
AjahnCharles

Usar AsyncTask regular nem parece funcionar agora, ainda recebendo a exceção de estado ilegal
Peterstev Uremgba

@Piotrek, você está me dizendo que está acostumado a executar o acesso ao banco de dados no thread principal em toda a sua experiência?
mr5

142

Não é recomendado, mas você pode acessar o banco de dados no thread principal com allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()

9
O Room não permite o acesso ao banco de dados no encadeamento principal, a menos que você chame allowMainThreadQueries()o construtor, pois pode bloquear a IU por um longo período de tempo. As consultas assíncronas (consultas que retornam LiveDataou RxJava Flowable) estão isentas desta regra, uma vez que executam de forma assíncrona a consulta em um thread de segundo plano quando necessário.
PRANAY

4
Obrigado, isso é muito útil para a migração, pois eu quero testar o Room está funcionando conforme o esperado antes de converter de Loaders para Live Data
SammyT

5
@JideGuruTheProgrammer Não, não deveria. Em alguns casos, isso pode tornar seu aplicativo muito lento. As operações devem ser feitas de forma assíncrona.
Alex

@Alex Nunca é o caso de fazer uma consulta no tópico principal?
Justin Meiners

2
@JustinMeiners é apenas uma prática ruim, você não terá problemas com isso, desde que o banco de dados permaneça pequeno.
lasec0203

53

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).

// Step 1: add `suspend` to your fun
suspend fun roomFun(...): Int
suspend fun notRoomFun(...) = withContext(Dispatchers.IO) { ... }

// Step 2: launch from coroutine scope
private fun myFun() {
    lifecycleScope.launch { // coroutine on Main
        val queryResult = roomFun(...) // coroutine on IO
        doStuff() // ...back on Main
    }
}

Dependências (adiciona escopos de co-rotina para componentes de arco):

// lifecycleScope:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha04'

// viewModelScope:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha04'

- 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


1
Você tem algum erro de compilação com a @Query abstract suspend fun count()palavra-chave usando suspender? Você pode gentilmente analisar esta questão semelhante: stackoverflow.com/questions/48694449/…
Robin

@Robin - Sim, quero. Meu erro; Eu estava usando suspender em um método DAO público (não anotado) que chamou uma @Queryfunção não suspensa protegida . Quando eu adiciono a palavra-chave suspend também ao @Querymé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).
AjahnCharles

Faz muito sentido. Em vez disso, vou chamá-lo com funções de co-rotina.
Robin

1
@Robin - Para sua informação, eles adicionaram suporte para suspender na Sala 2.1 :)
AjahnCharles

Aparentemente, não há launchmais nenhuma palavra-chave, você inicia com um escopo, comoGlobalScope.launch
nasch

48

Para todos os amantes de RxJava ou RxAndroid ou RxKotlin lá fora

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }

3
Se eu colocar esse código dentro de um método, como retornar o resultado da operação do banco de dados?
Eggakin Baconwalker de

@EggakinBaconwalker, estou override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }onde applySchedulers()acabei de fazerfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman

Isso não funcionará para IntentService. Porque IntentService será feito assim que seu thread for concluído.
Umang Kothari

1
@UmangKothari Você não obtém a exceção se estiver ligado IntentService#onHandleIntentporque 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 Room
Samuel Robert

@SamuelRobert, Sim, concordo, minha má. Escapou da minha mente.
Umang Kothari

27

Você 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

/**
 *  Insert and get data using Database Async way
 */
AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        // Insert Data
        AppDatabase.getInstance(context).userDao().insert(new User(1,"James","Mathew"));

        // Get Data
        AppDatabase.getInstance(context).userDao().getAllUsers();
    }
});

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()


Se eu usar esse método para obter os dados (apenas getAllUsers () neste caso), como retornar os dados desse método? Parece um erro, se eu colocar a palavra "return" dentro do "run".
Eggakin Baconwalker de

1
faça um método de interface em algum lugar e adicione uma classe anônima para obter dados daqui.
Rizvan

1
Esta é a solução mais simples para inserir / atualizar.
Beer Me

12

Com lambda é fácil de executar com AsyncTask

 AsyncTask.execute(() -> //run your query here );

2
Isso é conveniente, obrigado. A propósito, Kotlin é ainda mais fácil: AsyncTask.execute {}
alexrnov

1
mas como você obtém o resultado usando este método?
leeCoder

11

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:

    doAsync { 
        Application.database.myDAO().insertUser(user) 
    }

Eu uso isso com freqüência para inserções e atualizações, no entanto, para consultas selecionadas, recomendo usando o fluxo de trabalho RX.


7

Basta fazer as operações do banco de dados em um Thread separado. Assim (Kotlin):

Thread {
   //Do your database´s operations here
}.start()

6

Você deve executar a solicitação em segundo plano. Uma maneira simples poderia ser usando um Executores :

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}

como você retorna o resultado?
leeCoder

5

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.

public Completable insert(Event event) {
    return Completable.fromCallable(new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            return database.eventDao().insert(event)
        }
    }
}

Ou em Kotlin:

fun insert(event: Event) : Completable = Completable.fromCallable {
    database.eventDao().insert(event)
}

Você pode observar e se inscrever como faria normalmente:

dataManager.insert(event)
    .subscribeOn(scheduler)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...)

5

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.


4

Simplesmente você pode usar este código para resolvê-lo:

Executors.newSingleThreadExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        appDb.daoAccess().someJobes();//replace with your code
                    }
                });

Ou em lambda você pode usar este código:

Executors.newSingleThreadExecutor().execute(() -> appDb.daoAccess().someJobes());

Você pode substituir appDb.daoAccess().someJobes()por seu próprio código;


4

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.

public class DbHelper {

    private final Executor executor = Executors.newSingleThreadExecutor();

    public void fetchData(DataFetchListener dataListener){
        executor.execute(() -> {
                Object object = retrieveAgent(agentId);
                new Handler(Looper.getMainLooper()).post(() -> {
                        dataListener.onFetchDataSuccess(object);
                });
        });
    }
}

O Looper principal é usado para que você possa acessar o elemento da IU a partir do onFetchDataSuccessretorno de chamada.


3

A mensagem de erro,

Não é possível acessar o banco de dados no thread principal, pois pode bloquear potencialmente a IU por longos períodos de tempo.

É 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:

  1. 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.

  2. 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 tutorial


Eu entendo seu ponto, minha suposição é que o mesmo ponto é válido quando você tenta testar qualquer operação db por meio do JUnit. No entanto, em developer.android.com/topic/libraries/architecture/room.html, o método de teste de amostra writeUserAndReadInList não invoca a consulta de inserção no encadeamento em segundo plano. Estou perdendo alguma coisa aqui? Por favor sugira.
Devarshi

Desculpe, eu perdi o fato de que este era o teste com problemas. Vou editar minha resposta para adicionar mais algumas informações.
Dale Wilson

3

Se você se sentir mais confortável com a tarefa Async :

  new AsyncTask<Void, Void, Integer>() {
                @Override
                protected Integer doInBackground(Void... voids) {
                    return Room.databaseBuilder(getApplicationContext(),
                            AppDatabase.class, DATABASE_NAME)
                            .fallbackToDestructiveMigration()
                            .build()
                            .getRecordingDAO()
                            .getAll()
                            .size();
                }

                @Override
                protected void onPostExecute(Integer integer) {
                    super.onPostExecute(integer);
                    Toast.makeText(HomeActivity.this, "Found " + integer, Toast.LENGTH_LONG).show();
                }
            }.execute();

2

Atualização: também recebi esta mensagem quando estava tentando construir uma consulta usando @RawQuery e SupportSQLiteQuery dentro do DAO.

@Transaction
public LiveData<List<MyEntity>> getList(MySettings mySettings) {
    //return getMyList(); -->this is ok

    return getMyList(new SimpleSQLiteQuery("select * from mytable")); --> this is an error

Solução: construa a consulta dentro do ViewModel e passe para o DAO.

public MyViewModel(Application application) {
...
        list = Transformations.switchMap(searchParams, params -> {

            StringBuilder sql;
            sql = new StringBuilder("select  ... ");

            return appDatabase.rawDao().getList(new SimpleSQLiteQuery(sql.toString()));

        });
    }

Ou...

Você não deve acessar o banco de dados diretamente no thread principal, por exemplo:

 public void add(MyEntity item) {
     appDatabase.myDao().add(item); 
 }

Você deve usar AsyncTask para atualizar, adicionar e excluir operações.

Exemplo:

public class MyViewModel extends AndroidViewModel {

    private LiveData<List<MyEntity>> list;

    private AppDatabase appDatabase;

    public MyViewModel(Application application) {
        super(application);

        appDatabase = AppDatabase.getDatabase(this.getApplication());
        list = appDatabase.myDao().getItems();
    }

    public LiveData<List<MyEntity>> getItems() {
        return list;
    }

    public void delete(Obj item) {
        new deleteAsyncTask(appDatabase).execute(item);
    }

    private static class deleteAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        deleteAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().delete((params[0]));
            return null;
        }
    }

    public void add(final MyEntity item) {
        new addAsyncTask(appDatabase).execute(item);
    }

    private static class addAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        addAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().add((params[0]));
            return null;
        }

    }
}

Se você usa LiveData para selecionar operações, não precisa de AsyncTask.


1

Para consultas rápidas, você pode permitir espaço para executá-lo no thread de interface do usuário.

AppDatabase db = Room.databaseBuilder(context.getApplicationContext(),
        AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build();

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

       @Override
        public void onClick(View view) {



            int position = getAdapterPosition();

            User user = new User();
            String name = getName(position);
            user.setName(name);

            AppDatabase appDatabase = DatabaseCreator.getInstance(mContext).getDatabase();
            UserDao userDao = appDatabase.getUserDao();
            ArrayList<User> users = new ArrayList<User>();
            users.add(user);
            List<Long> ids = userDao.insertAll(users);

            Long id = ids.get(0);
            if(id == -1)
            {
                user = userDao.getUser(name);
                user.setId(user.getId());
            }
            else
            {
                user.setId(id);
            }

            Intent intent = new Intent(mContext, ChatActivity.class);
            intent.putExtra(ChatActivity.EXTRAS_USER, Parcels.wrap(user));
            mContext.startActivity(intent);
        }
    }

1

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: -

@Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();

Meu método de repositório: -

public UserData getDefaultData() throws ExecutionException, InterruptedException {

    Callable<UserData> callable = new Callable<UserData>() {
        @Override
        public UserData call() throws Exception {
            return userDao.getDefaultData();
        }
    };

    Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);

    return future.get();
}

É por isso que estamos usando Callable / Future, porque o Android não permite que as consultas sejam executadas no thread principal. Conforme perguntado nas perguntas acima
iniciante em

1
Quero dizer, embora o código de sua resposta faça uma consulta no thread de segundo plano, o thread principal está bloqueado e aguardando quando a consulta terminar. Portanto, no final, não é muito melhor do que allowMainThreadQueries(). Tópico principal ainda bloqueado em ambos os casos
eugeneek

0

Na 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.

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            //Creating view model requires DB access
            homeViewModel = new ViewModelProvider(this, factory).get(HomeViewModel.class);
        }).subscribeOn(Schedulers.io())//The DB access executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    mAdapter = new MyAdapter(homeViewModel.getExams());
                    recyclerView.setAdapter(mAdapter);
                    ((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.INVISIBLE);
                },
                error -> error.printStackTrace()
        );

E se quisermos generalizar a solução:

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            someTaskThatTakesTooMuchTime();
        }).subscribeOn(Schedulers.io())//The long task executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    taskIWantToDoOnTheMainThreadWhenTheLongTaskIsDone();
                },
                error -> error.printStackTrace()
        );
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.