Phòng Android - truy vấn chọn đơn giản - Không thể truy cập cơ sở dữ liệu trên chuỗi chính


126

Tôi đang thử một mẫu với Thư viện Kiên trì Phòng . Tôi đã tạo một Thực thể:

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

Đã tạo một lớp 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);
}

Đã tạo lớp Cơ sở dữ liệu:

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

Cơ sở dữ liệu tiếp xúc bằng cách sử dụng lớp con bên dưới trong 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()
    }
}

Đã triển khai chức năng dưới đây trong hoạt động của tôi:

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

Thật không may khi thực hiện phương pháp trên, nó gặp sự cố với dấu vết ngăn xếp bên dưới:

    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) 

Có vẻ như vấn đề đó liên quan đến việc thực thi thao tác db trên luồng chính. Tuy nhiên, mã thử nghiệm mẫu được cung cấp trong liên kết trên không chạy trên một chuỗi riêng biệt:

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

Tôi có thiếu gì ở đây không? Làm thế nào tôi có thể làm cho nó thực thi mà không gặp sự cố? Hãy đề nghị.


1
Mặc dù được viết cho Kotlin, bài viết này giải thích vấn đề cơ bản rất tốt!
Peter Lehnhardt


Nhìn vào câu trả lời này. Câu trả lời này phù hợp với tôi stackoverflow.com/a/51720501/7655085
Somen Tushir

Câu trả lời:


59

Việc truy cập cơ sở dữ liệu trên luồng chính khóa giao diện người dùng là lỗi, như Dale đã nói.

Tạo một lớp lồng nhau tĩnh (để tránh rò rỉ bộ nhớ) trong Hoạt động mở rộng AsyncTask của bạn.

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

Hoặc bạn có thể tạo một lớp cuối cùng trên tệp của chính nó.

Sau đó thực thi nó trong phương thức signUpAction (View view):

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

Trong một số trường hợp, bạn cũng có thể muốn giữ một tham chiếu đến AgentAsyncTask trong hoạt động của mình để bạn có thể hủy nó khi Hoạt động bị hủy. Nhưng bạn sẽ phải tự mình làm gián đoạn bất kỳ giao dịch nào.

Ngoài ra, câu hỏi của bạn về ví dụ thử nghiệm của Google ... Họ nêu rõ trong trang web đó:

Phương pháp được đề xuất để kiểm tra việc triển khai cơ sở dữ liệu của bạn là viết một bài kiểm tra JUnit chạy trên thiết bị Android. Bởi vì các thử nghiệm này không yêu cầu tạo một hoạt động, chúng sẽ thực thi nhanh hơn các thử nghiệm giao diện người dùng của bạn.

Không có hoạt động, không có giao diện người dùng.

--BIÊN TẬP--

Đối với những người tự hỏi ... Bạn có các lựa chọn khác. Tôi khuyên bạn nên xem xét các thành phần ViewModel và LiveData mới. LiveData hoạt động hiệu quả với Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Một tùy chọn khác là RxJava / RxAndroid. Mạnh hơn nhưng phức tạp hơn LiveData. https://github.com/ReactiveX/RxJava

--PHẦN 2--

Vì nhiều người có thể bắt gặp câu trả lời này ... Nói chung, lựa chọn tốt nhất hiện nay là Kotlin Coroutines. Room hiện hỗ trợ trực tiếp (hiện đang trong giai đoạn thử nghiệm). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01


31
Tôi có phải thực hiện nhiệm vụ không đồng bộ khổng lồ này (mà bạn đã đề xuất trong mẫu mã) mỗi khi tôi cần truy cập cơ sở dữ liệu không? Đó là hàng tá dòng mã thay vì một dòng để lấy một số dữ liệu từ db. Bạn cũng đã đề xuất tạo lớp mới, nhưng có nghĩa là tôi cần tạo lớp AsyncTask mới cho mỗi lệnh gọi cơ sở dữ liệu insert / select?
Piotrek,

7
Bạn có một số tùy chọn khác, có. Bạn có thể muốn xem xét các thành phần ViewModel và LiveData mới. Khi sử dụng LiveData, bạn không cần AsyncTask, đối tượng sẽ được thông báo bất cứ khi nào có điều gì đó thay đổi. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… Ngoài ra còn có AndroidRx (mặc dù nó thực hiện khá nhiều những gì LiveData làm) và Hứa hẹn. Khi sử dụng AsyncTask, bạn có thể cấu trúc theo cách mà bạn có thể bao gồm nhiều thao tác trong một AsyncTask hoặc tách biệt từng thao tác.
mcastro

@Piotrek - Kotlin hiện đã cài đặt không đồng bộ (mặc dù nó là thử nghiệm được gắn cờ). Hãy xem câu trả lời của tôi tương đối tầm thường. Câu trả lời của Samuel Robert bao gồm Rx. Tôi không thấy câu trả lời LiveData ở đây, nhưng đó có thể là lựa chọn tốt hơn nếu bạn muốn quan sát được.
AjahnCharles

Sử dụng AsyncTask thường xuyên thậm chí không dường như làm việc bây giờ, vẫn nhận được ngoại trừ tình trạng bất hợp pháp
Peterstev Uremgba

@Piotrek bạn đang nói với tôi rằng bạn được sử dụng để thực thi quyền truy cập Cơ sở dữ liệu trên chuỗi chính trên toàn bộ trải nghiệm của bạn?
mr5,

142

Nó không được khuyến khích nhưng bạn có thể truy cập vào cơ sở dữ liệu trên chuỗi chính với allowMainThreadQueries()

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

9
Room không cho phép truy cập cơ sở dữ liệu trên chuỗi chính trừ khi bạn gọi allowMainThreadQueries()trình tạo vì nó có thể khóa giao diện người dùng trong một khoảng thời gian dài. Các truy vấn không đồng bộ (truy vấn trả về LiveDatahoặc RxJava Flowable) được miễn quy tắc này vì chúng chạy không đồng bộ truy vấn trên một chuỗi nền khi cần.
pRaNaY

4
Nhờ điều này rất hữu ích cho việc di chuyển, như tôi muốn kiểm tra phòng đang làm việc như mong đợi trước khi chuyển đổi từ Máy bốc hàng để Live Data
SammyT

5
@JideGuruTheProgrammer Không, không nên. Trong một số trường hợp, nó có thể làm chậm ứng dụng của bạn rất nhiều. Các hoạt động phải được thực hiện không đồng bộ.
Alex

@Alex Không bao giờ có trường hợp thực hiện truy vấn trên chuỗi chính?
Justin Meiners

2
@JustinMeiners đó chỉ là một thực tiễn tồi, bạn sẽ ổn khi làm điều đó miễn là cơ sở dữ liệu vẫn nhỏ.
lasec0203 14/09/19

53

Kotlin Coroutines (Rõ ràng và súc tích)

AsyncTask thực sự phức tạp. Coroutines là một giải pháp thay thế rõ ràng hơn (chỉ cần rắc một vài từ khóa và mã đồng bộ của bạn trở nên không đồng bộ).

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

Sự phụ thuộc (thêm phạm vi điều chỉnh cho các thành phần vòm):

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

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

- Cập nhật:
08-05-2019: Room 2.1 hiện hỗ trợ suspend
13-09-2019: Cập nhật để sử dụng phạm vi thành phần Kiến trúc


1
Bạn có bất kỳ lỗi biên dịch nào với @Query abstract suspend fun count()từ khóa bằng cách sử dụng đình chỉ không? Bạn có thể vui lòng xem câu hỏi tương tự này không: stackoverflow.com/questions/48694449/…
Robin

@Robin - Đúng vậy. Lỗi của tôi; Tôi đang sử dụng phương pháp tạm ngưng trên một phương pháp DAO công khai (không có chú thích) được gọi là @Querychức năng không tạm ngưng được bảo vệ . Khi tôi thêm từ khóa pause vào @Queryphương thức nội bộ, nó thực sự không biên dịch được. Có vẻ như công cụ thông minh để tạm ngừng và xung đột trong phòng (như bạn đã đề cập trong câu hỏi khác của mình, phiên bản tạm ngừng đã biên dịch đang trả lại một sự tiếp diễn mà Phòng không thể xử lý).
AjahnCharles

Hãy làm cho nó thêm ý nghĩa hơn. Thay vào đó, tôi sẽ gọi nó bằng các hàm coroutine.
Robin

1
@Robin - FYI họ đã thêm hỗ trợ tạm ngưng trong Phòng 2.1 :)
AjahnCharles

Rõ ràng là không có launchtừ khóa nào nữa, bạn khởi chạy với một phạm vi, chẳng hạn nhưGlobalScope.launch
nasch

48

Dành cho tất cả những người yêu thích RxJava hoặc RxAndroid hoặc RxKotlin ngoài kia

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

3
Nếu tôi đặt mã này bên trong một phương thức, làm thế nào để trả về kết quả từ hoạt động cơ sở dữ liệu?
Eggakin Baconwalker

@EggakinBaconwalker Tôi có override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }nơi applySchedulers()tôi chỉ làmfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman

Điều này sẽ không hoạt động cho IntentService. Bởi vì IntentService sẽ được thực hiện sau khi luồng của nó hoàn thành.
Umang Kothari

1
@UmangKothari Bạn không nhận được ngoại lệ nếu bạn đang ở trên IntentService#onHandleIntentvì phương pháp này thực thi trên thread công nhân, do đó bạn sẽ không cần bất kỳ cơ chế luồng đó để làm các hoạt động cơ sở dữ liệu Phòng
Samuel Robert

@SamuelRobert, Vâng, đồng ý là xấu của tôi. Quên mất rồi.
Umang Kothari

27

Bạn không thể chạy nó trên luồng chính thay vào đó hãy sử dụng trình xử lý, không đồng bộ hoặc luồng đang làm việc. Mã mẫu có sẵn tại đây và đọc bài viết trên thư viện phòng tại đây: Thư viện phòng của 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();
    }
});

Nếu bạn muốn chạy nó trên luồng chính mà không phải là cách ưa thích.

Bạn có thể sử dụng phương pháp này để đạt được trên chuỗi chính Room.inMemoryDatabaseBuilder()


Nếu tôi sử dụng phương thức này để lấy dữ liệu (chỉ getAllUsers () trong trường hợp này), thì làm cách nào để trả lại dữ liệu từ phương thức này? Nó xuất hiện một lỗi, nếu tôi đặt từ "trở lại" bên trong "chạy".
Eggakin Baconwalker

1
tạo một phương thức giao diện ở đâu đó và thêm lớp ẩn danh để lấy dữ liệu từ đây.
Rizvan

1
Đây là giải pháp đơn giản nhất để chèn / cập nhật.
Bia cho tôi

12

Với lambda, nó dễ dàng chạy với AsyncTask

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

2
Điều này là thuận tiện, cảm ơn. Nhân tiện, Kotlin thậm chí còn dễ dàng hơn: AsyncTask.execute {}
alexrnov

1
nhưng làm thế nào để bạn nhận được kết quả bằng cách sử dụng phương pháp này?
leeCoder

11

Với thư viện Jetbrains Anko, bạn có thể sử dụng phương thức doAsync {..} để tự động thực hiện các lệnh gọi cơ sở dữ liệu. Điều này giải quyết vấn đề chi tiết mà bạn dường như đã gặp phải với câu trả lời của mcastro.

Ví dụ sử dụng:

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

Tôi sử dụng điều này thường xuyên để chèn và cập nhật, tuy nhiên đối với các truy vấn chọn lọc, tôi khuyên bạn nên sử dụng quy trình làm việc RX.


7

Chỉ cần thực hiện các hoạt động cơ sở dữ liệu trong một Thread riêng biệt. Như thế này (Kotlin):

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

6

Bạn phải thực hiện yêu cầu trong nền. Một cách đơn giản có thể là sử dụng Executor :

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

làm thế nào để bạn trả lại kết quả?
leeCoder

5

Một giải pháp RxJava / Kotlin thanh lịch sẽ được sử dụng Completable.fromCallable, giải pháp này sẽ cung cấp cho bạn một Observable không trả về giá trị, nhưng có thể quan sát và đăng ký trên một chuỗi khác.

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

Hoặc trong Kotlin:

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

Bạn có thể quan sát và đăng ký như bạn thường làm:

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

5

Bạn có thể cho phép truy cập cơ sở dữ liệu trên luồng chính nhưng chỉ cho mục đích gỡ lỗi, bạn không nên làm điều này trên sản xuất.

Đây là lý do.

Lưu ý: Room không hỗ trợ truy cập cơ sở dữ liệu trên chuỗi chính trừ khi bạn đã gọi allowMainThreadQueries () trên trình tạo vì nó có thể khóa giao diện người dùng trong một thời gian dài. Các truy vấn không đồng bộ — các truy vấn trả về các bản sao của LiveData hoặc Flowable — được miễn quy tắc này vì chúng chạy không đồng bộ truy vấn trên một chuỗi nền khi cần.


4

Đơn giản là bạn có thể sử dụng mã này để giải quyết nó:

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

Hoặc trong lambda, bạn có thể sử dụng mã này:

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

Bạn có thể thay thế appDb.daoAccess().someJobes()bằng mã của riêng bạn;


4

Vì asyncTask không được dùng nữa, chúng tôi có thể sử dụng dịch vụ trình thực thi. HOẶC bạn cũng có thể sử dụng ViewModel với LiveData như được giải thích trong các câu trả lời khác.

Để sử dụng dịch vụ thực thi, bạn có thể sử dụng một cái gì đó như bên dưới.

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

Main Looper được sử dụng để bạn có thể truy cập phần tử UI từ onFetchDataSuccesscallback.


3

Thông báo lỗi,

Không thể truy cập cơ sở dữ liệu trên chuỗi chính vì nó có thể khóa giao diện người dùng trong một thời gian dài.

Là khá mô tả và chính xác. Câu hỏi đặt ra là bạn nên làm thế nào để tránh truy cập vào cơ sở dữ liệu trên chuỗi chính. Đó là một chủ đề rất lớn, nhưng để bắt đầu, hãy đọc về AsyncTask (nhấp vào đây)

-----BIÊN TẬP----------

Tôi thấy bạn đang gặp sự cố khi chạy thử nghiệm đơn vị. Bạn có một số lựa chọn để khắc phục điều này:

  1. Chạy thử nghiệm trực tiếp trên máy phát triển chứ không phải trên thiết bị Android (hoặc trình giả lập). Điều này hoạt động đối với các bài kiểm tra tập trung vào cơ sở dữ liệu và không thực sự quan tâm liệu chúng có đang chạy trên thiết bị hay không.

  2. Sử dụng chú thích @RunWith(AndroidJUnit4.class) để chạy thử nghiệm trên thiết bị Android, nhưng không chạy trong hoạt động có giao diện người dùng. Có thể tìm thấy thêm chi tiết về điều này trong hướng dẫn này


Tôi hiểu quan điểm của bạn, giả định của tôi là cùng một điểm hợp lệ khi bạn cố gắng kiểm tra bất kỳ thao tác db nào thông qua JUnit. Tuy nhiên, trong developer.android.com/topic/libraries/architecture/room.html , phương pháp kiểm tra mẫu writeUserAndReadInList không gọi truy vấn chèn trên chuỗi nền. Tôi có thiếu gì ở đây không? Hãy đề nghị.
Devarshi

Xin lỗi, tôi đã bỏ lỡ thực tế rằng đây là bài kiểm tra có vấn đề. Tôi sẽ chỉnh sửa câu trả lời của mình để thêm một số thông tin.
Dale Wilson

3

Nếu bạn cảm thấy thoải mái hơn với tác vụ 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

Cập nhật: Tôi cũng nhận được thông báo này khi tôi đang cố gắng tạo truy vấn bằng @RawQuery và SupportSQLiteQuery bên trong 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

Giải pháp: xây dựng truy vấn bên trong ViewModel và chuyển nó đến DAO.

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

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

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

        });
    }

Hoặc là...

Bạn không nên truy cập cơ sở dữ liệu trực tiếp trên chuỗi chính, ví dụ:

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

Bạn nên sử dụng AsyncTask cho các thao tác cập nhật, thêm và xóa.

Thí dụ:

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

    }
}

Nếu bạn sử dụng LiveData cho các hoạt động được chọn, bạn không cần AsyncTask.


1

Đối với các truy vấn nhanh, bạn có thể cho phép khoảng trống để thực thi nó trên chuỗi giao diện người dùng.

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

Trong trường hợp của tôi, tôi phải tìm ra người dùng được nhấp trong danh sách có tồn tại trong cơ sở dữ liệu hay không. Nếu không, hãy tạo người dùng và bắt đầu hoạt động khác

       @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

Bạn có thể sử dụng Future và Callable. Vì vậy, bạn sẽ không bắt buộc phải viết một asynctask dài và có thể thực hiện các truy vấn của mình mà không cần thêm allowMainThreadQueries ().

My dao truy vấn: -

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

Phương thức lưu trữ của tôi: -

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

Đây là lý do tại sao chúng tôi đang sử dụng Callable / Future vì android không cho phép các truy vấn chạy trên chuỗi chính. Như đã hỏi trong qus ở trên
người mới bắt đầu

1
Ý tôi là, mặc dù mã từ câu trả lời của bạn thực hiện truy vấn trong chuỗi nền, chuỗi chính bị chặn và chờ khi truy vấn kết thúc. Vì vậy, cuối cùng nó không phải là tốt hơn nhiều allowMainThreadQueries(). Chủ đề chính vẫn bị chặn trong cả hai trường hợp
eugeneek

0

Theo tôi, điều đúng đắn cần làm là ủy quyền truy vấn cho một chuỗi IO bằng RxJava.

Tôi có một ví dụ về giải pháp cho một vấn đề tương đương mà tôi vừa gặp phải.

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

Và nếu chúng ta muốn tổng quát hóa giải pháp:

((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()
        );
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.