एंड्रॉइड रूम - सरल चयन क्वेरी - मुख्य धागे पर डेटाबेस का उपयोग नहीं कर सकता


126

मैं कक्ष दृढ़ता पुस्तकालय के साथ एक नमूना की कोशिश कर रहा हूँ । मैंने एक इकाई बनाई:

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

एक डीएओ वर्ग बनाया गया:

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

डेटाबेस वर्ग बनाया गया:

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

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

मेरी गतिविधि में कार्य के नीचे लागू:

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

दुर्भाग्य से उपरोक्त विधि के निष्पादन पर यह स्टैक ट्रेस के साथ नीचे दुर्घटनाग्रस्त हो जाता है:

    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) 

ऐसा लगता है कि समस्या मुख्य धागे पर डीबी संचालन के निष्पादन से संबंधित है। हालाँकि उपरोक्त लिंक में प्रदान किया गया नमूना परीक्षण कोड अलग थ्रेड पर नहीं चलता है:

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

क्या मुझे यहाँ कुछ भी याद नहीं है? मैं दुर्घटना के बिना इसे कैसे निष्पादित कर सकता हूं? कृपया सुझाव दे।


1
हालांकि कोटलिन के लिए लिखा गया, यह लेख अंतर्निहित समस्या को बहुत अच्छा बताता है!
पीटर लेहहार्ट ने

करने के लिए एक नजर डालें stackoverflow.com/questions/58532832/...
nnyerges

इस जवाब को देखिए। मेरे लिए यह जवाब काम stackoverflow.com/a/51720501/7655085
सोम तुशीर

जवाबों:


59

यूआई को लॉक करने वाले मुख्य थ्रेड पर डेटाबेस एक्सेस त्रुटि है, जैसे डेल ने कहा।

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

या आप अपनी फ़ाइल पर एक अंतिम वर्ग बना सकते हैं।

फिर साइनअप में इसे निष्पादित करें (देखें दृश्य) विधि:

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

कुछ मामलों में आप अपनी गतिविधि में AgentAsyncTask का संदर्भ भी रखना चाह सकते हैं ताकि गतिविधि के नष्ट होने पर आप इसे रद्द कर सकें। लेकिन आपको किसी भी लेन-देन को बाधित करना होगा।

इसके अलावा, Google के परीक्षण उदाहरण के बारे में आपका प्रश्न ... वे उस वेब पेज में बताते हैं:

अपने डेटाबेस कार्यान्वयन के परीक्षण के लिए अनुशंसित दृष्टिकोण एक JUnit परीक्षण लिख रहा है जो एक Android डिवाइस पर चलता है। क्योंकि इन परीक्षणों को गतिविधि बनाने की आवश्यकता नहीं होती है, इसलिए उन्हें आपके UI परीक्षणों की तुलना में तेज़ होना चाहिए।

कोई गतिविधि नहीं, कोई UI नहीं।

--EDIT--

सोच रहे लोगों के लिए ... आपके पास अन्य विकल्प हैं। मैं नए ViewModel और LiveData घटकों पर एक नज़र डालने की सलाह देता हूं। LiveData कमरे के साथ बहुत अच्छा काम करता है। https://developer.android.com/topic/libraries/architecture/livedata.html

एक अन्य विकल्प RxJava / RxAndroid है। LiveData से अधिक शक्तिशाली लेकिन अधिक जटिल। https://github.com/ReactiveX/RxJava

--EDIT 2--

चूंकि बहुत से लोग इस जवाब पर आ सकते हैं ... आजकल सबसे अच्छा विकल्प, आमतौर पर बोल रहा है, कोटलिन कॉरटुइन है। कक्ष अब सीधे (वर्तमान में बीटा में) इसका समर्थन करता है। https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01


31
क्या मुझे हर बार डेटाबेस तक पहुँचने के लिए इस विशाल async कार्य (जो आपने कोड नमूने में प्रस्तावित किया है) करना चाहिए था? यह db से कुछ डेटा प्राप्त करने के लिए एक के बजाय एक दर्जन या कोड की लाइनें हैं। आपने नई कक्षा बनाने का भी प्रस्ताव रखा था, लेकिन क्या इसका मतलब है कि मुझे प्रत्येक सम्मिलित / चयन डेटाबेस कॉल के लिए नए AsyncTask वर्ग बनाने की आवश्यकता है?
पियोट्रेक

7
आपके पास कुछ अन्य विकल्प हैं, हाँ। आप नए ViewModel और LiveData घटकों में नज़र रखना चाहते हैं। LiveData का उपयोग करते समय आपको AsyncTask की आवश्यकता नहीं होती है, जब भी कुछ परिवर्तन होता है तो ऑब्जेक्ट को सूचित किया जाएगा। developer.android.com/topic/lbooks/altecture/… developer.android.com/topic/lbooks/Healthecture/… वहाँ भी AndroidRx है (हालांकि यह बहुत ज्यादा LiveData करता है), और वादा करता है। AsyncTask का उपयोग करते समय आप इस तरह से आर्किटेक्चर कर सकते हैं कि आप एक AsyncTask में कई ऑपरेशन शामिल कर सकते हैं या प्रत्येक को अलग कर सकते हैं।
मस्करो जूल

@Piotrek - कोटलिन अब async में बेक किया हुआ है (हालांकि इसे प्रयोगात्मक रूप से चिह्नित किया गया है)। मेरा जवाब देखें जो तुलनात्मक रूप से तुच्छ है। सैमुअल रॉबर्ट का जवाब आरएक्स को कवर करता है। मुझे यहां LiveData का उत्तर दिखाई नहीं देता है, लेकिन यदि आप एक अवलोकन चाहते हैं तो यह बेहतर विकल्प हो सकता है।
अजानचर्ल्स

नियमित रूप से AsyncTask का उपयोग करना अब भी काम नहीं करता है, फिर भी अवैध राज्य अपवाद प्राप्त कर रहा है
पीटरस्टेव उर्मगबा

@Piotrek आप मुझे बता रहे हैं कि आप अपने पूरे अनुभव पर मुख्य थ्रेड पर डेटाबेस एक्सेस को निष्पादित करने के लिए उपयोग किए जाते हैं?
mr5

142

यह अनुशंसित नहीं है, लेकिन आप मुख्य थ्रेड पर डेटाबेस तक पहुंच सकते हैं allowMainThreadQueries()

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

10
कक्ष मुख्य डेटाबेस पर डेटाबेस तक पहुँचने की अनुमति नहीं देता है जब तक कि आप allowMainThreadQueries()बिल्डर को नहीं बुलाते हैं क्योंकि यह संभवतः यूआई को लंबे समय तक लॉक कर सकता है। अतुल्यकालिक प्रश्न (वे प्रश्न जो वापस आते हैं LiveDataया RxJava Flowable) को इस नियम से छूट दी जाती है क्योंकि वे जरूरत पड़ने पर पृष्ठभूमि के धागे पर अतुल्यकालिक रूप से क्वेरी चलाते हैं।
pRaNaY

4
धन्यवाद यह प्रवास के लिए बहुत उपयोगी है, जैसा कि मैं चाहता हूं कि परीक्षण किया जाए कि कक्ष लोडर से लाइव डेटा में परिवर्तित होने से पहले अपेक्षित रूप से काम कर रहा है
SammyT

5
@JideGuruTheProgrammer नहीं, यह नहीं होना चाहिए। कुछ मामलों में यह आपके ऐप को बहुत धीमा कर सकता है। संचालन को अतुल्यकालिक रूप से किया जाना चाहिए।
एलेक्स

@ एलेक्स मुख्य धागे पर एक प्रश्न बनाने के लिए कभी कोई मामला नहीं है?
जस्टिन मीनर्स

2
@JustinMeiners यह सिर्फ बुरा अभ्यास है, जब तक डेटाबेस छोटा रहेगा तब तक आप इसे करना ठीक रहेगा।
Lasec0203

53

कोटलिन कॉरआउट (स्पष्ट और संक्षिप्त)

AsyncTask वास्तव में क्लूनी है। Coroutines एक क्लीनर विकल्प है (केवल कुछ कीवर्ड छिड़कें और आपका सिंक कोड async हो जाता है)।

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

निर्भरताएँ (आर्च घटकों के लिए कॉरटीन स्कोप जोड़ता है):

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

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

- अपडेट: ०
now-मई -२०१ ९: कमरा २.१ अब suspend
१३-सितंबर -२०१ ९ का समर्थन करता है : आर्किटेक्चर घटकों के दायरे का उपयोग करने के लिए अपडेट किया गया


1
क्या आपके पास @Query abstract suspend fun count()सस्पेंड कीवर्ड का उपयोग करके कोई संकलन त्रुटि है ? क्या आप कृपया इस तरह के प्रश्न पर विचार कर सकते हैं: stackoverflow.com/questions/48694449/…
रॉबिन

@ रोबिन - हाँ मैं करता हूँ। मेरी गलती; मैं एक सार्वजनिक (बिना बताए) DAO विधि पर निलंबित का उपयोग कर रहा था जिसे संरक्षित गैर-निलंबित @Queryफ़ंक्शन कहा जाता है । जब मैं सस्पेंड कीवर्ड को आंतरिक @Queryविधि में जोड़ता हूं तो यह वास्तव में संकलन करने में विफल रहता है। यह सस्पेंड एंड रूम क्लैश के लिए हुड सामान के नीचे चतुर जैसा दिखता है (जैसा कि आप अपने अन्य प्रश्न में उल्लेख करते हैं, सस्पेंड का संकलित संस्करण एक निरंतरता लौटा रहा है जो कमरा संभाल नहीं सकता है)।
अज़हरचर्ल्स

इसमें काफी सार्थकता है। मैं इसके बजाय coroutine फ़ंक्शन के साथ कॉल करने जा रहा हूं।
रॉबिन

1
@ रॉबिन - FYI करें उन्होंने सस्पेंड के लिए सपोर्ट को कमरा 2.1 में जोड़ा :) :)
AjahnCharles

जाहिरा तौर पर launchअब कोई कीवर्ड नहीं है, आप एक गुंजाइश के साथ लॉन्च करते हैं, जैसेGlobalScope.launch
nasch

48

सभी RxJava या RxAndroid या RxKotlin प्रेमियों के लिए वहाँ

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

3
यदि मैं इस कोड को एक विधि के अंदर रखता हूं, तो डेटाबेस ऑपरेशन से परिणाम कैसे लौटाएं?
एगाकिन बेकोनवल्कर

@EggakinBaconwalker मेरे पास override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }जहां applySchedulers()मैं बस करता हूंfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman

यह IntentService के लिए काम नहीं करेगा। क्योंकि थ्रेड पूरा हो जाने के बाद IntentService किया जाएगा।
उमंग कोठारी

1
@UmangKothari आपको अपवाद नहीं मिलता है यदि आप ऐसा कर रहे हैं IntentService#onHandleIntentक्योंकि यह विधि कार्यकर्ता थ्रेड पर निष्पादित होती है तो आपको कक्ष डेटाबेस संचालन करने के लिए किसी थ्रेडिंग तंत्र की आवश्यकता नहीं होगी
सैमुअल रॉबर्ट

@SamuelRobert, हाँ मेरी बुरी बात से सहमत हूँ। यह बात मेरे दिमाग से निकल गई।
उमंग कोठारी

27

आप इसे मुख्य थ्रेड पर चलाने के बजाय हैंडलर, एसिंक्स या वर्किंग थ्रेड का उपयोग नहीं कर सकते। एक नमूना कोड यहां उपलब्ध है और यहां कमरे की लाइब्रेरी पर लेख पढ़ा गया है: एंड्रॉइड रूम लाइब्रेरी

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

यदि आप इसे मुख्य धागे पर चलाना चाहते हैं जो कि पसंदीदा तरीका नहीं है।

आप मुख्य धागे पर प्राप्त करने के लिए इस विधि का उपयोग कर सकते हैं Room.inMemoryDatabaseBuilder()


यदि मैं इस विधि का उपयोग केवल डेटा (केवल getAllUsers () को इस मामले में) प्राप्त करने के लिए करता हूं, तो इस विधि से डेटा कैसे लौटाएं? यह एक त्रुटि प्रतीत होता है, अगर मैंने "रन" शब्द "रन" के अंदर रखा।
एगाकिन बेकनवल्कर

1
कहीं से एक इंटरफ़ेस विधि बनाएं और यहां से डेटा प्राप्त करने के लिए अनाम वर्ग जोड़ें।
रिजवान

1
यह सम्मिलित / अद्यतन करने का सबसे सरल उपाय है।
मी

12

लैम्ब्डा के साथ AsyncTask के साथ चलना आसान है

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

2
यह सुविधाजनक है, धन्यवाद। वैसे, कोटलिन और भी आसान है: AsyncTask.execute {}
एलेक्स्रनोव

1
लेकिन आपको इस विधि का उपयोग करके परिणाम कैसे मिलेगा?
leeCoder

11

Jetbrains Anko लाइब्रेरी के साथ, आप डेटाबेस कॉल को स्वचालित रूप से निष्पादित करने के लिए doAsync {..} विधि का उपयोग कर सकते हैं। यह उस वर्बोसिटी समस्या का ख्याल रखता है जो आपको लगता था कि मैक्रो के जवाब के साथ है।

उदाहरण उपयोग:

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

मैं आवेषण और अद्यतनों के लिए अक्सर इसका उपयोग करता हूं, हालांकि चुनिंदा प्रश्नों के लिए मैं आरएक्स वर्कफ़्लो का उपयोग करता हूं।



6

आपको पृष्ठभूमि में अनुरोध निष्पादित करना होगा। एक आसान तरीका एक एक्सेक्यूटर्स का उपयोग कर सकता है :

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

आप परिणाम कैसे लौटाते हैं?
leeCoder

5

एक सुरुचिपूर्ण RxJava / Kotlin समाधान का उपयोग करना है Completable.fromCallable, जो आपको एक ऑब्जर्व करने योग्य देगा जो एक मूल्य नहीं लौटाता है, लेकिन एक अलग धागे पर देखा और सदस्यता लिया जा सकता है।

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

या कोटलिन में:

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

आप आम तौर पर निरीक्षण कर सकते हैं और सदस्यता ले सकते हैं:

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

5

आप मुख्य थ्रेड पर डेटाबेस का उपयोग करने की अनुमति दे सकते हैं लेकिन केवल डिबगिंग उद्देश्य के लिए, आपको उत्पादन पर ऐसा नहीं करना चाहिए।

यहीं कारण है।

नोट: कक्ष मुख्य थ्रेड पर डेटाबेस एक्सेस का समर्थन नहीं करता है जब तक कि आपने बिल्डर पर AllowMainThreadQueries () को नहीं बुलाया है क्योंकि यह यूआई को लंबे समय तक लॉक कर सकता है। अतुल्यकालिक प्रश्न - LiveData या फ़्लोएबल के उदाहरणों को लौटाने वाले प्रश्न इस नियम से छूट वाले हैं क्योंकि वे असिंक्रोनस रूप से क्वेरी को एक पृष्ठभूमि थ्रेड पर चलाते हैं जब जरूरत होती है।


4

बस आप इसे हल करने के लिए इस कोड का उपयोग कर सकते हैं:

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

या मेमने में आप इस कोड का उपयोग कर सकते हैं:

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

आप appDb.daoAccess().someJobes()अपने खुद के कोड से बदल सकते हैं ;


4

AsyncTask को पदावनत करने के बाद हम निष्पादक सेवा का उपयोग कर सकते हैं। या आप ViewModel को LiveData के साथ अन्य उत्तरों में बताए अनुसार उपयोग कर सकते हैं ।

निष्पादक सेवा का उपयोग करने के लिए, आप नीचे कुछ का उपयोग कर सकते हैं।

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

मुख्य लूपर का उपयोग किया जाता है, ताकि आप onFetchDataSuccessकॉलबैक से UI तत्व तक पहुंच सकें ।


3

त्रुटि संदेश,

मुख्य रूप से डेटाबेस तक पहुँच नहीं कर सकता क्योंकि यह संभवतः यूआई को लंबे समय तक लॉक कर सकता है।

काफी वर्णनात्मक और सटीक है। सवाल यह है कि आपको मुख्य धागे पर डेटाबेस तक पहुंचने से कैसे बचना चाहिए। यह एक बहुत बड़ा विषय है, लेकिन आरंभ करने के लिए, AsyncTask के बारे में पढ़ें (यहां क्लिक करें)

----- संपादित ----------

जब आप एक इकाई परीक्षण चलाते हैं तो मुझे समस्या होती है। इसे ठीक करने के लिए आपके पास कुछ विकल्प हैं:

  1. एंड्रॉइड डिवाइस (या एमुलेटर) के बजाय सीधे विकास मशीन पर परीक्षण चलाएं। यह उन परीक्षणों के लिए काम करता है जो डेटाबेस-केंद्रित हैं और वास्तव में परवाह नहीं करते हैं कि वे किसी डिवाइस पर चल रहे हैं या नहीं।

  2. @RunWith(AndroidJUnit4.class) एंड्रॉइड डिवाइस पर परीक्षण चलाने के लिए एनोटेशन का उपयोग करें , लेकिन यूआई के साथ गतिविधि में नहीं। इसके बारे में अधिक विवरण इस ट्यूटोरियल में पाया जा सकता है


मैं आपकी बात समझता हूं, मेरी धारणा यह है कि जब आप JUnit के माध्यम से किसी db ऑपरेशन का परीक्षण करने का प्रयास करते हैं तो वही बिंदु मान्य होता है। हालाँकि developer.android.com/topic/lbooks/altecture/room.html नमूना परीक्षण विधि में लिखा गया है। UserAndReadInList बैकग्राउंड थ्रेड पर सम्मिलित क्वेरी को आमंत्रित नहीं करता है। क्या मुझे यहाँ कुछ भी याद नहीं है? कृपया सुझाव दे।
देवर्षि

क्षमा करें, मैंने इस तथ्य को याद किया कि यह परीक्षण समस्या थी। मैं कुछ और जानकारी जोड़ने के लिए अपने उत्तर को संपादित करूँगा।
डेल विल्सन

3

यदि आप 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

अद्यतन: मुझे यह संदेश तब भी मिला जब मैं DAO के अंदर @RawQuery और SupportSQLiteQuery का उपयोग करके एक क्वेरी बनाने की कोशिश कर रहा था।

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

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

समाधान: ViewModel के अंदर क्वेरी बनाएँ और इसे DAO को पास करें।

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

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

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

        });
    }

या ...

आपको डेटाबेस को सीधे मुख्य धागे पर नहीं पहुंचना चाहिए, उदाहरण के लिए:

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

आपको ऑपरेशनों को अपडेट करने, जोड़ने और हटाने के लिए AsyncTask का उपयोग करना चाहिए।

उदाहरण:

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

    }
}

यदि आप चुनिंदा परिचालनों के लिए LiveData का उपयोग करते हैं, तो आपको AsyncTask की आवश्यकता नहीं है।


1

त्वरित प्रश्नों के लिए आप कमरे को UI थ्रेड पर निष्पादित करने की अनुमति दे सकते हैं।

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

मेरे मामले में मुझे सूची में क्लिक किए गए उपयोगकर्ता का पता लगाना था जो डेटाबेस में मौजूद है या नहीं। यदि नहीं तो उपयोगकर्ता बनाएँ और एक और गतिविधि शुरू करें

       @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

आप Future और Callable का उपयोग कर सकते हैं। इसलिए आपको एक लंबे एसिंक्टस्क को लिखने की आवश्यकता नहीं होगी और बिना अनुमति के अपने प्रश्नों का प्रदर्शन कर सकते हैं ।MainThreadQueries ()।

मेरा दाव क्वेरी: -

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

मेरा भंडार विधि: -

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

यही कारण है कि हम Callable / Future का उपयोग कर रहे हैं क्योंकि Android मुख्य थ्रेड पर क्वेरीज़ को चलाने की अनुमति नहीं देता है। जैसा कि ऊपर क्युस में पूछा गया है
शुरुआती

1
मेरा मतलब है, हालांकि आपके उत्तर से कोड पृष्ठभूमि थ्रेड में क्वेरी करते हैं, मुख्य थ्रेड अवरुद्ध है और क्वेरी समाप्त होने पर प्रतीक्षा कर रहा है। इसलिए अंत में यह बहुत बेहतर नहीं है allowMainThreadQueries()। मुख्य धागा अभी भी दोनों मामलों में अवरुद्ध है
eugeneek

0

मेरी राय में सही काम RxJava का उपयोग करके एक IO थ्रेड को क्वेरी सौंपना है।

मेरे पास एक समस्‍या के समाधान का एक उदाहरण है जिसका मैंने अभी सामना किया है।

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

और अगर हम समाधान को सामान्य बनाना चाहते हैं:

((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()
        );
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.