Android Room - kueri pemilihan sederhana - Tidak dapat mengakses database di utas utama


126

Saya mencoba sampel dengan Room Persistence Library . Saya membuat Entitas:

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

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

Membuat kelas Database:

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

Database yang terpapar menggunakan subclass di bawah ini di 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()
    }
}

Diimplementasikan di bawah fungsi dalam aktivitas saya:

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

Sayangnya pada eksekusi metode di atas itu crash dengan pelacakan tumpukan di bawah ini:

    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) 

Sepertinya masalah itu terkait dengan eksekusi operasi db pada utas utama. Namun contoh kode uji yang diberikan di tautan di atas tidak berjalan di utas terpisah:

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

Apakah saya melewatkan sesuatu di sini? Bagaimana saya bisa membuatnya dieksekusi tanpa crash? Mohon saran.


1
Meskipun ditulis untuk Kotlin, artikel ini menjelaskan masalah yang mendasarinya dengan sangat baik!
Peter Lehnhardt


Lihat jawaban ini. Jawaban ini bekerja untuk saya stackoverflow.com/a/51720501/7655085
Somen Tushir

Jawaban:


59

Akses database pada utas utama yang mengunci UI adalah kesalahan, seperti kata Dale.

Buat kelas bertingkat statis (untuk mencegah kebocoran memori) dalam Aktivitas Anda yang memperluas 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();
        }
    }
}

Atau Anda dapat membuat kelas akhir pada filenya sendiri.

Kemudian jalankan dalam metode signUpAction (View view):

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

Dalam beberapa kasus, Anda mungkin juga ingin menyimpan referensi ke AgentAsyncTask dalam aktivitas Anda sehingga Anda bisa membatalkannya saat Aktivitas dimusnahkan. Tetapi Anda sendiri harus menghentikan transaksi apa pun.

Juga, pertanyaan Anda tentang contoh pengujian Google ... Mereka menyatakan di halaman web itu:

Pendekatan yang direkomendasikan untuk menguji implementasi database Anda adalah menulis pengujian JUnit yang dijalankan di perangkat Android. Karena pengujian ini tidak memerlukan pembuatan aktivitas, pengujian tersebut harus lebih cepat dijalankan daripada pengujian UI Anda.

Tanpa Aktivitas, Tanpa UI.

--EDIT--

Bagi orang yang bertanya-tanya ... Anda punya pilihan lain. Saya sarankan untuk melihat komponen ViewModel dan LiveData yang baru. LiveData berfungsi baik dengan Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Pilihan lainnya adalah RxJava / RxAndroid. Lebih kuat tetapi lebih kompleks daripada LiveData. https://github.com/ReactiveX/RxJava

--EDIT 2--

Karena banyak orang mungkin menemukan jawaban ini ... Pilihan terbaik saat ini, secara umum, adalah Kotlin Coroutines. Room sekarang mendukungnya secara langsung (saat ini dalam versi beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01


31
Apakah saya harus membuat tugas asinkron besar ini (yang telah Anda usulkan dalam contoh kode) setiap kali saya perlu mengakses database? Ini adalah selusin baris kode, bukan satu untuk mendapatkan beberapa data dari db. Anda juga mengusulkan untuk membuat kelas baru, tetapi apakah itu berarti saya perlu membuat kelas AsyncTask baru untuk setiap panggilan database insert / select?
Piotrek

7
Anda punya beberapa pilihan lain, ya. Anda mungkin ingin melihat komponen ViewModel dan LiveData baru. Saat menggunakan LiveData, Anda tidak memerlukan AsyncTask, objek akan diberi tahu setiap kali ada perubahan. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… Ada juga AndroidRx (meskipun ia melakukan hampir semua yang dilakukan LiveData), dan Promises. Saat menggunakan AsyncTask, Anda dapat membuat arsitektur sedemikian rupa sehingga Anda dapat menyertakan beberapa operasi dalam satu AsyncTask atau memisahkan masing-masing.
mcastro

@Piotrek - Kotlin sekarang memiliki asinkronisasi (meskipun ditandai sebagai eksperimental). Lihat jawaban saya yang relatif sepele. Jawaban Samuel Robert mencakup Rx. Saya tidak melihat jawaban LiveData di sini, tetapi itu mungkin pilihan yang lebih baik jika Anda menginginkan yang dapat diamati.
AjahnCharles

Menggunakan AsyncTask biasa bahkan tampaknya tidak berfungsi sekarang, masih mendapatkan pengecualian negara ilegal
Peterstev Uremgba

@Piotrek Anda memberi tahu saya bahwa Anda terbiasa menjalankan akses Database di utas utama di seluruh pengalaman Anda?
mr5

142

Ini tidak disarankan tetapi Anda dapat mengakses database di utas utama dengan allowMainThreadQueries()

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

10
Room tidak mengizinkan akses database pada thread utama kecuali Anda memanggil allowMainThreadQueries()pembuat karena berpotensi mengunci UI untuk jangka waktu yang lama. Kueri asinkron (kueri yang mengembalikan LiveDataatau RxJava Flowable) dikecualikan dari aturan ini karena kueri tersebut menjalankan kueri secara asinkron di thread latar belakang saat diperlukan.
pRaNaY

4
Terima kasih, ini sangat berguna untuk migrasi, karena saya ingin menguji Room berfungsi seperti yang diharapkan sebelum mengonversi dari Loader ke Live Data
SammyT

5
@JideGuruTheProgrammer Tidak, seharusnya tidak. Dalam beberapa kasus, ini dapat sangat memperlambat aplikasi Anda. Operasi harus dilakukan secara asinkron.
Alex

@Alex Tidak pernah ada kasus untuk membuat kueri di utas utama?
Justin Meiners

2
@JustinMeiners itu hanya praktik yang buruk, Anda akan baik-baik saja melakukannya selama database tetap kecil.
lasec0203

54

Kotlin Coroutines (Jelas & Ringkas)

AsyncTask sangat kikuk. Coroutine adalah alternatif yang lebih bersih (cukup taburkan beberapa kata kunci dan kode sinkronisasi Anda menjadi asinkron).

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

Dependensi (menambahkan cakupan coroutine untuk komponen arch):

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

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

- Pembaruan:
08-Mei-2019: Room 2.1 sekarang mendukung suspend
13-Sep-2019: Diperbarui untuk menggunakan cakupan komponen Arsitektur


1
Apakah Anda memiliki kesalahan kompilasi dengan @Query abstract suspend fun count()menggunakan kata kunci suspend? Bisakah Anda dengan ramah melihat pertanyaan serupa ini: stackoverflow.com/questions/48694449/…
Robin

@Robin - Ya saya lakukan. Kesalahanku; Saya menggunakan penangguhan pada metode DAO publik (tanpa pemberitahuan) yang disebut @Queryfungsi non-penangguhan yang dilindungi . Ketika saya menambahkan kata kunci suspend ke @Querymetode internal juga memang gagal untuk dikompilasi. Sepertinya orang pintar di balik terpal untuk menangguhkan & Room bentrok (seperti yang Anda sebutkan di pertanyaan Anda yang lain, versi penangguhan yang dikompilasi mengembalikan kelanjutan yang tidak bisa ditangani Room).
AjahnCharles

Sangat masuk akal. Saya akan menyebutnya dengan fungsi coroutine sebagai gantinya.
Robin

1
@Robin - FYI mereka menambahkan dukungan untuk penangguhan di Kamar 2.1 :)
AjahnCharles

Ternyata sudah tidak ada launchkata kunci lagi, Anda meluncurkan dengan cakupan, sepertiGlobalScope.launch
nasch

48

Untuk semua pecinta RxJava atau RxAndroid atau RxKotlin di luar sana

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

3
Jika saya meletakkan kode ini di dalam metode, bagaimana mengembalikan hasil dari operasi database?
Eggakin Baconwalker

@EggakinBaconwalker Saya memiliki override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }tempat yang applySchedulers()baru saja saya lakukanfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman

Ini tidak akan berfungsi untuk IntentService. Karena IntentService akan selesai setelah utasnya selesai.
Umang Kothari

1
@UmangKothari Anda tidak mendapatkan pengecualian jika Anda aktif IntentService#onHandleIntentkarena metode ini dijalankan pada thread pekerja sehingga Anda tidak memerlukan mekanisme threading di sana untuk melakukan operasi database Room
Samuel Robert

@SamuelRobert, Ya setuju saya buruk. Itu menyelipkan pikiran saya.
Umang Kothari

27

Anda tidak dapat menjalankannya di utas utama sebagai gantinya menggunakan penangan, asinkron atau utas kerja. Kode sampel tersedia di sini dan baca artikel melalui perpustakaan ruangan di sini: Perpustakaan Ruangan 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();
    }
});

Jika Anda ingin menjalankannya di utas utama yang bukan cara yang disukai.

Anda dapat menggunakan metode ini untuk mencapai thread utama Room.inMemoryDatabaseBuilder()


Jika saya menggunakan metode ini untuk mendapatkan data (hanya getAllUsers () dalam kasus ini), bagaimana mengembalikan data dari metode ini? Muncul kesalahan, jika saya meletakkan kata "kembali" di dalam "jalankan".
Eggakin Baconwalker

1
membuat metode antarmuka di suatu tempat dan menambahkan kelas anonim untuk mendapatkan data dari sini.
Rizvan

1
Ini adalah solusi paling sederhana untuk memasukkan / memperbarui.
Beer Me

12

Dengan lambda, mudah untuk dijalankan dengan AsyncTask

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

2
Ini nyaman, terima kasih. Ngomong-ngomong, Kotlin bahkan lebih mudah: AsyncTask.execute {}
alexrnov

1
tetapi bagaimana Anda mendapatkan hasil menggunakan metode ini?
leeCoder

11

Dengan pustaka Jetbrains Anko, Anda bisa menggunakan metode doAsync {..} untuk menjalankan panggilan database secara otomatis. Ini menangani masalah verbositas yang sepertinya Anda alami dengan jawaban mcastro.

Contoh penggunaan:

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

Saya sering menggunakan ini untuk penyisipan dan pembaruan, namun untuk kueri tertentu saya merekomendasikan menggunakan alur kerja RX.



6

Anda harus menjalankan permintaan di latar belakang. Cara sederhana bisa menggunakan Pelaksana :

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

bagaimana Anda mengembalikan hasil?
leeCoder

5

Solusi RxJava / Kotlin yang elegan akan digunakan Completable.fromCallable, yang akan memberi Anda Observable yang tidak mengembalikan nilai, tetapi dapat diamati dan berlangganan pada thread yang berbeda.

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

Atau di Kotlin:

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

Anda dapat mengamati dan berlangganan seperti biasanya:

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

5

Anda dapat mengizinkan akses database di thread utama tetapi hanya untuk tujuan debugging, Anda tidak boleh melakukan ini pada produksi.

Inilah alasannya.

Catatan: Room tidak mendukung akses database di utas utama kecuali Anda telah memanggil allowMainThreadQueries () di pembuat karena mungkin mengunci UI untuk jangka waktu yang lama. Kueri asinkron — kueri yang mengembalikan instance LiveData atau Flowable — dikecualikan dari aturan ini karena kueri tersebut menjalankan kueri secara asinkron di thread latar belakang saat diperlukan.


4

Cukup Anda dapat menggunakan kode ini untuk menyelesaikannya:

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

Atau di lambda Anda dapat menggunakan kode ini:

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

Anda dapat mengganti appDb.daoAccess().someJobes()dengan kode Anda sendiri;


4

Karena asyncTask sudah tidak digunakan lagi, kami dapat menggunakan layanan eksekutor. ATAU Anda juga dapat menggunakan ViewModel dengan LiveData seperti yang dijelaskan di jawaban lain.

Untuk menggunakan layanan eksekutor, Anda dapat menggunakan sesuatu seperti di bawah ini.

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

Looper Utama digunakan, sehingga Anda dapat mengakses elemen UI dari onFetchDataSuccesscallback.


3

Pesan kesalahan,

Tidak dapat mengakses database di thread utama karena berpotensi mengunci UI untuk jangka waktu yang lama.

Cukup deskriptif dan akurat. Pertanyaannya adalah bagaimana Anda harus menghindari mengakses database di thread utama. Itu adalah topik yang sangat besar, tetapi untuk memulai, baca tentang AsyncTask (klik di sini)

----- EDIT ----------

Saya melihat Anda mengalami masalah saat menjalankan pengujian unit. Anda memiliki beberapa pilihan untuk memperbaikinya:

  1. Jalankan pengujian secara langsung di mesin pengembangan, bukan di perangkat Android (atau emulator). Ini berfungsi untuk pengujian yang berpusat pada database dan tidak terlalu peduli apakah pengujian tersebut berjalan di perangkat.

  2. Gunakan anotasi @RunWith(AndroidJUnit4.class) untuk menjalankan pengujian pada perangkat android, tetapi tidak dalam aktivitas dengan UI. Detail lebih lanjut tentang ini dapat ditemukan di tutorial ini


Saya memahami maksud Anda, asumsi saya adalah bahwa poin yang sama berlaku ketika Anda mencoba menguji operasi db apa pun melalui JUnit. Namun di developer.android.com/topic/libraries/architecture/room.html , metode pengujian sampel writeUserAndReadInList tidak memanggil kueri sisipkan di utas latar belakang. Apakah saya melewatkan sesuatu di sini? Mohon saran.
Devarshi

Maaf saya melewatkan fakta bahwa ini adalah tes yang bermasalah. Saya akan mengedit jawaban saya untuk menambahkan beberapa informasi lagi.
Dale Wilson

3

Jika Anda lebih nyaman dengan tugas 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

Pembaruan: Saya juga mendapat pesan ini ketika saya mencoba membuat kueri menggunakan @RawQuery dan SupportSQLiteQuery di dalam 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

Solusi: buat kueri di dalam ViewModel dan teruskan ke DAO.

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

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

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

        });
    }

Atau...

Anda tidak boleh mengakses database secara langsung di thread utama, misalnya:

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

Anda harus menggunakan AsyncTask untuk memperbarui, menambah, dan menghapus operasi.

Contoh:

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

    }
}

Jika Anda menggunakan LiveData untuk operasi tertentu, Anda tidak memerlukan AsyncTask.


1

Untuk kueri cepat, Anda dapat mengizinkan ruang untuk mengeksekusinya di thread UI.

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

Dalam kasus saya, saya harus mencari tahu pengguna yang diklik dalam daftar ada di database atau tidak. Jika tidak, buat pengguna dan mulai aktivitas lain

       @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

Anda dapat menggunakan Future dan Callable. Jadi, Anda tidak perlu menulis asynctask yang panjang dan dapat melakukan kueri Anda tanpa menambahkan allowMainThreadQueries ().

Kueri dao saya: -

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

Metode repositori saya: -

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

Inilah mengapa kami menggunakan Callable / Future karena android tidak mengizinkan kueri untuk dijalankan di utas utama. Seperti yang ditanyakan dalam qus di atas
pemula

1
Maksud saya, meskipun kode dari jawaban Anda membuat kueri di utas latar belakang, utas utama diblokir dan menunggu saat kueri selesai. Jadi pada akhirnya tidak lebih baik dari allowMainThreadQueries(). Utas utama masih diblokir dalam kedua kasus
eugeneek

0

Menurut pendapat saya, hal yang benar untuk dilakukan adalah mendelegasikan kueri ke utas IO menggunakan RxJava.

Saya memiliki contoh solusi untuk masalah yang setara yang baru saja saya temui.

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

Dan jika kita ingin menggeneralisasi solusinya:

((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()
        );
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.