Stanza Android - query di selezione semplice - Impossibile accedere al database sul thread principale


126

Sto provando un campione con Room Persistence Library . Ho creato un'entità:

@Entity
public class Agent {
    @PrimaryKey
    public String guid;
    public String name;
    public String email;
    public String password;
    public String phone;
    public String licence;
}

Creata una 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);
}

Creata la classe Database:

@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract AgentDao agentDao();
}

Database esposto utilizzando la sottoclasse sottostante in 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()
    }
}

Funzione di seguito implementata nella mia attività:

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

Sfortunatamente durante l'esecuzione del metodo precedente si blocca con la traccia dello stack sottostante:

    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) 

Sembra che il problema sia correlato all'esecuzione dell'operazione db sul thread principale. Tuttavia, il codice di prova di esempio fornito nel collegamento precedente non viene eseguito su un thread separato:

@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));
    }

Mi manca qualcosa qui? Come posso farlo eseguire senza crash? Per favore suggerisci.


1
Sebbene scritto per Kotlin, questo articolo spiega molto bene il problema sottostante!
Peter Lehnhardt


Guarda questa risposta. Questo lavoro risposta per me stackoverflow.com/a/51720501/7655085~~V~~singular~~3rd
Somen Tushir

Risposte:


59

L'accesso al database sul thread principale che blocca l'interfaccia utente è l'errore, come ha detto Dale.

Crea una classe annidata statica (per evitare perdite di memoria) nella tua attività che estende 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();
        }
    }
}

Oppure puoi creare una classe finale sul proprio file.

Quindi eseguilo nel metodo signUpAction (View view):

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

In alcuni casi potresti anche voler mantenere un riferimento ad AgentAsyncTask nella tua attività in modo da poterlo annullare quando l'attività viene distrutta. Ma dovresti interrompere tu stesso qualsiasi transazione.

Inoltre, la tua domanda sull'esempio di test di Google ... In quella pagina web si afferma:

L'approccio consigliato per testare l'implementazione del database è scrivere un test JUnit che viene eseguito su un dispositivo Android. Poiché questi test non richiedono la creazione di un'attività, dovrebbero essere più veloci da eseguire rispetto ai test dell'interfaccia utente.

Nessuna attività, nessuna interfaccia utente.

--MODIFICARE--

Per le persone che si chiedono ... Hai altre opzioni. Consiglio di dare un'occhiata ai nuovi componenti ViewModel e LiveData. LiveData funziona alla grande con Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Un'altra opzione è RxJava / RxAndroid. Più potente ma più complesso di LiveData. https://github.com/ReactiveX/RxJava

--EDIT 2--

Dal momento che molte persone potrebbero trovare questa risposta ... L'opzione migliore al giorno d'oggi, in generale, è Kotlin Coroutines. Room ora lo supporta direttamente (attualmente in beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01


31
Dovrei fare questa enorme attività asincrona (che hai proposto nell'esempio di codice) ogni volta che ho bisogno di accedere al database? Sono circa una dozzina di righe di codice invece di una per ottenere alcuni dati da db. Hai proposto anche di creare una nuova classe, ma significa che devo creare una nuova classe AsyncTask per ogni chiamata di inserimento / selezione del database?
Piotrek

7
Hai altre opzioni, sì. Potresti voler dare un'occhiata ai nuovi componenti ViewModel e LiveData. Quando si utilizza LiveData non è necessario AsyncTask, l'oggetto verrà avvisato ogni volta che qualcosa cambia. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… C'è anche AndroidRx (sebbene faccia più o meno quello che fa LiveData) e Promises. Quando si utilizza AsyncTask è possibile eseguire l'architettura in modo tale da poter includere più operazioni in un AsyncTask o separarle ciascuna.
mcastro

@Piotrek - Kotlin ora ha l'asincronizzazione (sebbene sia contrassegnata come sperimentale). Vedi la mia risposta che è relativamente banale. La risposta di Samuel Robert copre Rx. Non vedo una risposta LiveData qui, ma potrebbe essere la scelta migliore se vuoi un osservabile.
AjahnCharles

L'uso regolare di AsyncTask non sembra nemmeno funzionare ora, ottenendo ancora l'eccezione dello stato illegale
Peterstev Uremgba

@ Piotrek mi stai dicendo che sei abituato a eseguire l'accesso al database sul thread principale per tutta la tua esperienza?
mr5

142

Non è consigliato ma puoi accedere al database sul thread principale con allowMainThreadQueries()

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

10
Room non consente l'accesso al database sul thread principale a meno che tu non abbia chiamato allowMainThreadQueries()il builder perché potrebbe potenzialmente bloccare l'interfaccia utente per un lungo periodo di tempo. Le query asincrone (query che restituiscono LiveDatao RxJava Flowable) sono esentate da questa regola poiché eseguono in modo asincrono la query su un thread in background quando necessario.
PRANNO

4
Grazie, questo è molto utile per la migrazione, poiché voglio che la stanza di prova funzioni come previsto prima della conversione da
Loader

5
@JideGuruTheProgrammer No, non dovrebbe. In alcuni casi potrebbe rallentare molto la tua app. Le operazioni dovrebbero essere eseguite in modo asincrono.
Alex

@Alex Non c'è mai il caso di fare una query sul thread principale?
Justin Meiners

2
@JustinMeiners è solo una cattiva pratica, starai bene finché il database rimarrà piccolo.
lasec0203

54

Kotlin Coroutines (chiaro e conciso)

AsyncTask è davvero goffo. Le coroutine sono un'alternativa più pulita (basta cospargere un paio di parole chiave e il codice di sincronizzazione diventa asincrono).

// 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
    }
}

Dipendenze (aggiunge gli ambiti coroutine per i componenti dell'arco):

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

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

- Aggiornamenti: 8
maggio 2019: la stanza 2.1 ora supporta il suspend
13 settembre 2019: aggiornato per utilizzare l' ambito dei componenti dell'architettura


1
Hai qualche errore di compilazione con l' @Query abstract suspend fun count()utilizzo della parola chiave suspend? Puoi gentilmente esaminare questa domanda simile: stackoverflow.com/questions/48694449/…
Robin

@Robin - Sì, lo faccio. Errore mio; Stavo usando la sospensione su un metodo DAO pubblico (non annotato) che chiamava una @Queryfunzione di non sospensione protetta . Quando aggiungo anche la parola chiave suspend al @Querymetodo interno , la compilazione non riesce effettivamente. Sembra la roba intelligente sotto il cofano per suspend & Room clash (come hai menzionato nella tua altra domanda, la versione compilata di suspend sta restituendo una continuazione che Room non può gestire).
AjahnCharles

Ha molto senso. Lo chiamerò invece con le funzioni coroutine.
Robin

1
@Robin - Cordiali saluti, hanno aggiunto il supporto per la sospensione nella stanza 2.1 :)
AjahnCharles

Apparentemente non ci sono più launchparole chiave, si avvia con uno scopo, comeGlobalScope.launch
nasch

48

Per tutti gli amanti di RxJava o RxAndroid o RxKotlin là fuori

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

3
Se inserisco questo codice all'interno di un metodo, come restituire il risultato dall'operazione sul database?
Eggakin Baconwalker

@EggakinBaconwalker Ho override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }dove applySchedulers()ho appena fattofun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman

Questo non funzionerà per IntentService. Perché IntentService verrà eseguito una volta completato il thread.
Umang Kothari

1
@UmangKothari Non ottieni l'eccezione se sei attivo IntentService#onHandleIntentperché questo metodo viene eseguito sul thread di lavoro, quindi non avrai bisogno di alcun meccanismo di threading per eseguire l'operazione del database Room
Samuel Robert

@SamuelRobert, Sì, d'accordo sul mio male. L'ho dimenticato.
Umang Kothari

27

Non è possibile eseguirlo sul thread principale invece utilizzare gestori, thread asincroni o di lavoro. Un codice di esempio è disponibile qui e leggi l'articolo sulla libreria della stanza qui: Libreria della stanza di 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 vuoi eseguirlo sul thread principale che non è il modo preferito.

È possibile utilizzare questo metodo per ottenere risultati sul thread principale Room.inMemoryDatabaseBuilder()


Se utilizzo questo metodo per ottenere i dati (solo getAllUsers () in questo caso), come restituire i dati da questo metodo? Sembra un errore, se metto la parola "ritorno" all'interno della "corsa".
Eggakin Baconwalker

1
crea un metodo di interfaccia da qualche parte e aggiungi una classe anonima per ottenere dati da qui.
Rizvan

1
Questa è la soluzione più semplice per l'inserimento / aggiornamento.
Beer Me

12

Con lambda è facile da eseguire con AsyncTask

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

2
Questo è comodo, grazie. A proposito, Kotlin è ancora più semplice: AsyncTask.execute {}
alexrnov

1
ma come si ottiene il risultato usando questo metodo?
leeCoder

11

Con la libreria Jetbrains Anko, puoi usare il metodo doAsync {..} per eseguire automaticamente le chiamate al database. Questo risolve il problema di verbosità che sembrava avessi avuto con la risposta di mcastro.

Utilizzo di esempio:

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

Lo uso frequentemente per inserimenti e aggiornamenti, tuttavia per determinate query consiglio di utilizzare il flusso di lavoro RX.


7

Basta eseguire le operazioni sul database in un thread separato. In questo modo (Kotlin):

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

6

Devi eseguire la richiesta in background. Un modo semplice potrebbe essere usare un esecutore :

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

come restituisci il risultato?
leeCoder

5

È da utilizzare un'elegante soluzione RxJava / Kotlin Completable.fromCallable, che ti darà un Observable che non restituisce un valore, ma può essere osservato e sottoscritto su un thread diverso.

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

O a Kotlin:

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

Puoi osservare e iscriverti come faresti di solito:

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

5

Puoi consentire l'accesso al database sul thread principale ma solo a scopo di debug, non dovresti farlo in produzione.

Ecco il motivo.

Nota: Room non supporta l'accesso al database sul thread principale a meno che tu non abbia chiamato allowMainThreadQueries () nel builder perché potrebbe bloccare l'interfaccia utente per un lungo periodo di tempo. Le query asincrone, ovvero le query che restituiscono istanze di LiveData o Flowable, sono esentate da questa regola perché eseguono la query in modo asincrono su un thread in background quando necessario.


4

Semplicemente puoi usare questo codice per risolverlo:

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

Oppure in lambda puoi usare questo codice:

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

Puoi sostituire appDb.daoAccess().someJobes()con il tuo codice;


4

Poiché asyncTask è deprecato, potremmo utilizzare il servizio executor. OPPURE puoi anche usare ViewModel con LiveData come spiegato in altre risposte.

Per utilizzare il servizio esecutore, puoi usare qualcosa come di seguito.

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);
                });
        });
    }
}

Viene utilizzato il Looper principale, in modo da poter accedere all'elemento dell'interfaccia utente dalla onFetchDataSuccessrichiamata.


3

Il messaggio di errore,

Impossibile accedere al database sul thread principale poiché potrebbe potenzialmente bloccare l'interfaccia utente per lunghi periodi di tempo.

È abbastanza descrittivo e accurato. La domanda è come evitare di accedere al database sul thread principale. Questo è un argomento enorme, ma per iniziare, leggi AsyncTask (clicca qui)

-----MODIFICARE----------

Vedo che hai problemi quando esegui uno unit test. Hai un paio di scelte per risolvere questo problema:

  1. Esegui il test direttamente sulla macchina di sviluppo anziché su un dispositivo Android (o emulatore). Questo funziona per i test che sono incentrati sul database e non si preoccupano davvero se sono in esecuzione su un dispositivo.

  2. Utilizza l'annotazione @RunWith(AndroidJUnit4.class) per eseguire il test sul dispositivo Android, ma non in un'attività con un'interfaccia utente. Maggiori dettagli su questo possono essere trovati in questo tutorial


Capisco il tuo punto, la mia ipotesi è che lo stesso punto sia valido quando provi a testare qualsiasi operazione db tramite JUnit. Tuttavia in developer.android.com/topic/libraries/architecture/room.html il metodo di test di esempio writeUserAndReadInList non richiama la query di inserimento sul thread in background. Mi manca qualcosa qui? Per favore suggerisci.
Devarshi

Scusa, mi sono perso il fatto che questo era il test che ha avuto problemi. Modificherò la mia risposta per aggiungere ulteriori informazioni.
Dale Wilson

3

Se sei più a tuo agio con l' attività 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

Aggiornamento: ho ricevuto questo messaggio anche quando stavo cercando di creare una query utilizzando @RawQuery e SupportSQLiteQuery all'interno di 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

Soluzione: creare la query all'interno di ViewModel e passarla a DAO.

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

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

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

        });
    }

O...

Non dovresti accedere al database direttamente sul thread principale, ad esempio:

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

È necessario utilizzare AsyncTask per le operazioni di aggiornamento, aggiunta ed eliminazione.

Esempio:

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 usi LiveData per selezionare le operazioni, non hai bisogno di AsyncTask.


1

Per query rapide puoi lasciare spazio per eseguirlo sul thread dell'interfaccia utente.

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

Nel mio caso ho dovuto capire se l'utente cliccato nell'elenco esiste o meno nel database. In caso contrario, creare l'utente e avviare un'altra attività

       @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

Puoi usare Future e Callable. Quindi non ti verrà richiesto di scrivere una lunga asynctask e puoi eseguire le tue query senza aggiungere allowMainThreadQueries ().

La mia query dao: -

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

Il mio metodo di repository: -

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

Questo è il motivo per cui utilizziamo Callable / Future perché Android non consente l'esecuzione di query sul thread principale. Come chiesto nel qus sopra
principiante

1
Voglio dire, sebbene il codice della tua risposta esegua una query nel thread in background, il thread principale è bloccato e in attesa al termine della query. Quindi alla fine non è molto meglio di allowMainThreadQueries(). Il thread principale è ancora bloccato in entrambi i casi
eugeneek

0

A mio parere, la cosa giusta da fare è delegare la query a un thread IO utilizzando RxJava.

Ho un esempio di soluzione a un problema equivalente che ho appena riscontrato.

((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 vogliamo generalizzare la soluzione:

((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()
        );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.