Как я могу заставить некоторый класс, работающий в Основном потоке, одновременно ожидать, пока другой класс достигнет определенного состояния? - PullRequest
0 голосов
/ 21 сентября 2018

У меня возникают серьезные трудности с пониманием того, как я могу заставить некоторых дочерних элементов AsyncTask, объявленных и созданных в основном потоке, ожидать, пока экземпляр дочернего объекта Service достигнет определенного состояния.

В качестве примеров кода здесьсоответствующая часть для обслуживания;этот код выполняет то, что ожидалось: получает ответ JSON и удерживает его.

public class MyService extends Service {
    private boolean received = false;
    private string url = "http://someserver.mine/get-data-in-json-format";

    // [...]
    @Override
    public void onCreate() {
        doHttpJsonQuery();
    }

    public boolean responseReceived() {
        return this.received;
    }

    public List<MyModel> getResponseAsObject() {
        if (!this.received) return new ArrayList<MyModel>;

        // Many code lines that convert the data into a list.
        // [...]

        return the_list;
    }

    // [...]
    private void doHttpJsonQuery() {

        OkHttpClient client = new OkHttpClient.Builder()
                .build();

        Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String myResponse = response.body().string();
                //...and some code to hold data as JSONArray
                //[...]
            }
        });
        this.received = true;
    }
}

Эта служба работает;хорошо.Затем из другого класса (целью которого будет обрабатывать постоянство, вставляя полученные данные в локальную базу данных комнат), я пытаюсь сделать следующее (вот где мой ум поражен):

public class DataRepository {
    private MyRoomDatabase db;
    private MyModelDao mModelDao;
    // I'm skipping irrelevant code on purpose
    // [...]

    public DataRepository(Application application) {
        db = MyRoomDatabase.getDatabase(application);
        mModelDao = db.myModelDao();
        // [...]

        //  Here I instance a subclass of ContextWrapper(i named it RemoteDataSource) which
        // responsability will be handling different Services for making HTTP operations
        mRemoteDataSource = new RemoteDataSource(application.getApplicationContext());
        // It holds a reference to MyService. It has some public methods, like this one, to
        // control the referenced Service from outside with some encaspsulation
        mRemoteDataSource.startMyService();

        // Instantiating a private nested class...
        PopulateDbAsync mPopulateDbAsync = new PopulateDbAsync(db);
        mPopulateDbAsync.doInBackground();
    }
    // [...]

    // Here is the failing code
    private class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
        PopulateDbAsync(MyRoomDatabase db) {}

        @Override
        protected Void doInBackground(final Void... params) {
            MyService mService = mRemoteDataSource.getMyService();
            if (mService == null) {
                // This doesn't happen at all right now...
                Log.e("MY_ERROR","DataRepository.PopulateDbAsync --> MyService from RemoteDataSource is NULL!!!!");
            }
            List<MyModel> the_list = mService.getResponseAsObject();
            if (the_list == null) {
                // HERE! I obtain the NullReferenceException here.
                // I am confused about how would I avoid this flaw in my code
                Log.e("MY_ERROR", "DataRepository.PopulateDbAsync --> error: response isn't ready yet.");
            }
            for (MyModel i_model : the_list) {
                Log.d("MY_LOG", "DataRepository.PopulateDbAsync --> Inserting data in local DB...");
                mModelDao.insert(i_model);
            }
            return null;
        }
    }
}

Подводя итог:моя проблема в том, что я всегда получу NullReferenceException в этой строке:

for (MyModel i_model : the_list) {

Я не знаком с многопоточностью, асинхронными операциями и параллельным выполнением.В течение двух недель я читал много разных документов в Интернете, как с официального сайта Android, так и с других сайтов, пытаясь выяснить это ... «AsyncTask не подходит для выполнения таких операций» ..Итак, какую инфраструктуру я должен реализовать, мне было интересно ... я должен использовать обработчики, потоки, мессенджеры или что?Чем больше я читаю, тем больше путаюсь.Как будто у меня проблема Analysis Paralysis ...

Большинство примеров, которые я там обнаружил, содержат слишком подробные примеры кода о том, как реализовать многопоточность и параллельное выполнение;пока я читаю их и пытаюсь понять, как реализовать эти структуры в моем коде, я просто застреваю;Кроме того, с таким количеством классов, которые я могу выбрать, я еще больше запутался ...

Из-за того, что HTTP-вызов должен выполняться асинхронно (и время ответа не всегда будет одинаковым), я пытаюсьвыяснить, как заставить фрагмент кода, который вызывает NullReferenceException «ожидание», чтобы MyService завершил свою работу перед началом выполнения;Циклы while не будут работать, потому что это нарушит жизненный цикл основного потока.«Знать», завершила ли Служба свою задачу или нет, было бы так же просто, как использовать логический метод responseReceived.Основная идея заключается в том, что каждый раз, когда новые данные получают через HTTP, обновляя им RoomDatabase, а MainActivity будет отображать текущие локальные данные (если они есть) или пустой список, если ничего еще нет).

Итак, когда я получу его, я пойму, как правильно провести рефакторинг всей структуры кода, чтобы начать добавлять больше Service дочерних экземпляров в мой класс RemoteDataSource, который я создал с идеей иметь все дочерние элементы Service, которые будут использоватьOkHttp для выполнения HTTP-коммуникаций, объединенных в один класс для лучшей организации.

Каков правильный способ добиться того, что я ищу по этому поводу?Может ли кто-нибудь предоставить какой-нибудь короткий пример, объясняющий структуру кода, которая мне понадобится для чего-то подобного?Примеры с пустыми блоками, содержащими комментарии, такие как «код, который нужно выполнить, когда будет готов», были бы хорошими, поэтому я могу разобраться с этим.

Вопрос, затронутый здесь, связан с тем же проектом, который заставил меня опубликовать этот другойвопрос несколько недель назад;Я читал здесь и там, выполняя некоторые методики проб и ошибок и исправляя некоторые проблемы с кодом тут и там;однако я задаю здесь другой вопрос;поиск ответа на этот вопрос, вероятно, был бы первым шагом к выяснению ответа на другой вопрос.

URL Ссылки на документацию, которую я читал

Часть документации, на которую я обращалсячтение (но не ограничиваясь):

Ответы [ 2 ]

0 голосов
/ 25 сентября 2018

Я нашел решение: создание пользовательских слушателей .

Шаги по созданию пользовательского слушателя

1.Определите интерфейс как контракт события с методами, которые определяют события и аргументы, которые являются релевантными данными события.

2.Установите переменную-член слушателя и установщик в дочернем объекте , которому можно назначить реализацию интерфейса.

3.Владелец передает слушателю , который реализует интерфейс и обрабатывает события от дочернего объекта.

4.Инициировать события на определенном слушателе , когда объект хочет сообщить о событиях своему владельцу


Я получил NullReferenceException, потому что MyService еще не закончил свою работу.Итак, сначала я создаю структуру слушателя в классе MyService следующим образом (шаги 1 и 2):

private MyServiceListener listener;

public interface MyServiceListener {
    public void onDataDownloaded();
}

public void setMyServiceListener(MyServiceListener listener) {
    this.listener = listener;
}

И в обратном вызове HTTP-запроса (шаг 4):

@Override
public void onResponse(Call call, Response response) throws IOException {
    final String myResponse = response.body().string();
    //...and some code to hold data as JSONArray
    //[...]

    // XXX Trigger the custom event
    if (listener != null) {
        listener.onDataDownloaded();
    }
}

Теперь я могу просто обернуть код, который вызвал NullReferenceException в пользовательском слушателе, следующим образом (шаг 3):

// Within DataRepository class
mService.setMyServiceListener(new MyService.MyServiceListener) {
    @Override
    public void onDataDownloaded() {
        List<MyModel> the_list = mService.getResponseAsObject();
        if (the_list == null) {
            // HERE! I obtainED the NullReferenceException here.
            Log.e("MY_ERROR", "DataRepository.PopulateDbAsync --> error: response isn't ready yet.");
        }
        for (MyModel i_model : the_list) {
            Log.d("MY_LOG", "DataRepository.PopulateDbAsync --> Inserting data in local DB...");
            mModelDao.insert(i_model);
        }
        return null;
    }
}

На самом деле, реальная реализация, необходимая для вложения этого примера кода в другой пользовательскийслушатель выполняет аналогичные шаги;но это сработало для меня.

0 голосов
/ 21 сентября 2018

Что ж, проблема заключается в логике вашего приложения следующим образом:

Если вы используете AsyncTask, это, очевидно, отдельный поток от основного потока.Но синхронизация с вашей базой данных после извлечения данных через HTTP-вызов - это процесс, имеющий последовательность (Вызов через HTTP и получение -> затем сохранение в базе данных), он не может выполняться асинхронно.Поэтому, когда вы вызываете,

List<MyModel> the_list = mService.getResponseAsObject();

, этот вызов происходит в определенном потоке, а поток программы находится в другом потоке.Поскольку это асинхронные задачи, они работают асинхронно.Это означает, что вы никогда не будете знать, какой из них будет выполняться первым, а какой - следующим.Но, согласно вашей логике,

if (the_list == null) {

эта часть, по сути, должна быть the_list инициализирована для запуска.Но проблема в том, что служебный поток не закончил свою работу для выполнения следующей логики.так что его очевидное нарушение.

Лучше, если вы сможете изменить дизайн так, чтобы дождаться завершения HTTP-запроса и затем сохранить его в базе данных.Предположим, что если ваш HTTP-запрос завершается первым, но все равно он возвращает вам null или любой другой нежелательный результат.Так что в этом случае вам нужно обрабатывать это в своей логике.

Хорошо, так что позвольте мне сказать вам быстрый обходной путь.Позволяет использовать только один поток вместо разных потоков.Поэтому рассмотрите возможность изменения следующей строки

private class PopulateDbAsync extends AsyncTask<Void, Void, Void> {

на

private class PopulateDbAsync

, тогда вы получите ошибку с

@Override
protected Void doInBackground(final Void... params) {

, так как мы больше не расширяем класс AsyncTask.

поэтому измените его следующим образом, удалив @Override

public Void doInBackground(final Void... params) {

Это должно исправить указанную проблему здесь.

...