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.
android
crash
kotlin
android-studio-3.0
android-room
Devarshi
sumber
sumber
Jawaban:
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.
Atau Anda dapat membuat kelas akhir pada filenya sendiri.
Kemudian jalankan dalam metode signUpAction (View view):
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:
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
sumber
Ini tidak disarankan tetapi Anda dapat mengakses database di utas utama dengan
allowMainThreadQueries()
sumber
allowMainThreadQueries()
pembuat karena berpotensi mengunci UI untuk jangka waktu yang lama. Kueri asinkron (kueri yang mengembalikanLiveData
atau RxJavaFlowable
) dikecualikan dari aturan ini karena kueri tersebut menjalankan kueri secara asinkron di thread latar belakang saat diperlukan.Kotlin Coroutines (Jelas & Ringkas)
AsyncTask sangat kikuk. Coroutine adalah alternatif yang lebih bersih (cukup taburkan beberapa kata kunci dan kode sinkronisasi Anda menjadi asinkron).
Dependensi (menambahkan cakupan coroutine untuk komponen arch):
- Pembaruan:
08-Mei-2019: Room 2.1 sekarang mendukung
suspend
13-Sep-2019: Diperbarui untuk menggunakan cakupan komponen Arsitektur
sumber
@Query abstract suspend fun count()
menggunakan kata kunci suspend? Bisakah Anda dengan ramah melihat pertanyaan serupa ini: stackoverflow.com/questions/48694449/…@Query
fungsi non-penangguhan yang dilindungi . Ketika saya menambahkan kata kunci suspend ke@Query
metode 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).launch
kata kunci lagi, Anda meluncurkan dengan cakupan, sepertiGlobalScope.launch
Untuk semua pecinta RxJava atau RxAndroid atau RxKotlin di luar sana
sumber
override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }
tempat yangapplySchedulers()
baru saja saya lakukanfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
IntentService#onHandleIntent
karena metode ini dijalankan pada thread pekerja sehingga Anda tidak memerlukan mekanisme threading di sana untuk melakukan operasi database RoomAnda 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
Jika Anda ingin menjalankannya di utas utama yang bukan cara yang disukai.
Anda dapat menggunakan metode ini untuk mencapai thread utama
Room.inMemoryDatabaseBuilder()
sumber
Dengan lambda, mudah untuk dijalankan dengan AsyncTask
sumber
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:
Saya sering menggunakan ini untuk penyisipan dan pembaruan, namun untuk kueri tertentu saya merekomendasikan menggunakan alur kerja RX.
sumber
Lakukan saja operasi database di Thread terpisah. Seperti ini (Kotlin):
sumber
Anda harus menjalankan permintaan di latar belakang. Cara sederhana bisa menggunakan Pelaksana :
sumber
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.Atau di Kotlin:
Anda dapat mengamati dan berlangganan seperti biasanya:
sumber
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.
sumber
Cukup Anda dapat menggunakan kode ini untuk menyelesaikannya:
Atau di lambda Anda dapat menggunakan kode ini:
Anda dapat mengganti
appDb.daoAccess().someJobes()
dengan kode Anda sendiri;sumber
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.
Looper Utama digunakan, sehingga Anda dapat mengakses elemen UI dari
onFetchDataSuccess
callback.sumber
Pesan kesalahan,
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:
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.
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 inisumber
Jika Anda lebih nyaman dengan tugas Async :
sumber
Pembaruan: Saya juga mendapat pesan ini ketika saya mencoba membuat kueri menggunakan @RawQuery dan SupportSQLiteQuery di dalam DAO.
Solusi: buat kueri di dalam ViewModel dan teruskan ke DAO.
Atau...
Anda tidak boleh mengakses database secara langsung di thread utama, misalnya:
Anda harus menggunakan AsyncTask untuk memperbarui, menambah, dan menghapus operasi.
Contoh:
Jika Anda menggunakan LiveData untuk operasi tertentu, Anda tidak memerlukan AsyncTask.
sumber
Untuk kueri cepat, Anda dapat mengizinkan ruang untuk mengeksekusinya di thread UI.
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
sumber
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: -
Metode repositori saya: -
sumber
allowMainThreadQueries()
. Utas utama masih diblokir dalam kedua kasusMenurut 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.
Dan jika kita ingin menggeneralisasi solusinya:
sumber