Махинации Realm и Looper

У меня проблемы с Loopers и Realm.

У меня есть Activity, который создает экземпляр Presenter в onCreate(), а затем вызывает один из своих общедоступных методов initFirstLaunch():

RealmChangeListener<CourseDetailed> listener = new RealmChangeListener<CourseDetailed>() {
    @Override
    public void onChange(CourseDetailed element) {
        Log.i("renaud", "courseDetailed.addChangeListener onChange");
        computeTableOfContent(element);
        Log.i("renaud", "1");
        playerViewContract.initWithCourseDetails(element);
        Log.i("renaud", "2");
    }
};

public void initFirstLaunch() {

    courseDetailed = realm.where(CourseDetailed.class).contains("_id", courseId).findFirst();

    if (courseDetailed == null) {
        courseDetailed = realm.createObject(CourseDetailed.class, courseId);
    }

    courseDetailed.addChangeListener(listener);

    Api.getInstance().backend.getCourse(courseId).enqueue(new CustomRetrofitCallBack<CourseDetailed>(playerViewContract) {
        @Override
        public void onResponseReceived(final CourseDetailed response) {

            Realm.getDefaultInstance().executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    realm.copyToRealmOrUpdate(response);
                }
            });
        }
    });
}

обратите внимание, что playerViewContract - это моя деятельность в этом контексте.

Моя проблема в том, что onChange() иногда вызывается, и когда это происходит, он блокирует мой поток пользовательского интерфейса (и в конечном итоге провоцирует OutOfMemoryError). Я предполагаю, что я не был в потоке петлителя, но когда я вызываю Looper.prepare() в любом месте, он вылетает, говоря, что я уже в нем.

Что творится ?

Спасибо


Изменить: добавление кода initWithCourseDetailed

@Override
public void initWithCourseDetails(final CourseDetailed detailed) {

    Log.i("renaud", "initWithCourseDetails");

    mDrawer.post(new Runnable() {
        @Override
        public void run() {

            String title = detailed.getName();
            String subtitle = detailed.getCompany().getName();

            if (detailed.getThumbnail() != null) {

                String picUrl = AppConstants.SERVER_URL + "/api/" + detailed.getThumbnail();

                final LinearLayout l = (LinearLayout) mNavigationView.findViewById(R.id.layout);
                Picasso.with(ModulePlayerActivity.this)
                        .load(picUrl)
                        .config(Bitmap.Config.RGB_565)
                        .into(new Target() {
                            @Override
                            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                                l.setBackground(new BitmapDrawable(getResources(), bitmap));
                            }

                            @Override
                            public void onBitmapFailed(Drawable errorDrawable) {
                            }

                            @Override
                            public void onPrepareLoad(Drawable placeHolderDrawable) {

                            }
                        });
            }

            TextView nameTv = (TextView) mNavigationView.findViewById(R.id.drawer_header_name);
            nameTv.setText(title);

            TextView jobTv = (TextView) mNavigationView.findViewById(R.id.drawer_header_job);
            jobTv.setText(subtitle);

            supportInvalidateOptionsMenu();

        }
    });
}

Изменить: исправление

Api.getInstance().backend.getCourse(courseId).enqueue(new CustomRetrofitCallBack<CourseDetailed>(playerViewContract) {
            @Override
            public void onResponseReceived(final CourseDetailed response) {

                //TEST
                Realm realm = null;
                try {
                    realm = Realm.getDefaultInstance();
                    realm.executeTransactionAsync(new Realm.Transaction() {
                        @Override
                        public void execute(Realm realm) {
                            realm.copyToRealmOrUpdate(response);
                        }
                    });
                } finally {
                    if (realm != null) {
                        realm.close();
                    }
                    realm = null;
                }

            }
        });

person Renaud Favier    schedule 01.09.2016    source источник


Ответы (1)


Поток пользовательского интерфейса является потоком Looper (у него есть цикл Looper.getMainLooper()), что означает, что он имеет автообновление. Однако автоматическое обновление означает, что ChangeListener, который вы добавляете к RealmObject, будет вызываться при каждом изменении базовой таблицы, а не только один раз.

(И я также должен отметить, что RealmChangeListener сохраняются со слабой ссылкой Context Realm, как и RealmObject, поэтому чтобы автообновление и прослушиватель изменений оставались нетронутыми даже через сборщик мусора, он должен храниться как ссылка на поле.

Хотя также стоит отметить, что findFirst() может возвращать null, если элемент не найден по ID, в противном случае сразу возвращает элемент.)

Поэтому мне кажется, что что бы ни делал playerViewContract.initWithCourseDetails(element);, он делает это снова и снова и в конце концов вылетает.

Надеюсь, я ответил на ваш вопрос?

person EpicPandaForce    schedule 01.09.2016
comment
Мне пока что не ясно. Я регистрирую вызов initWithCourseDetails, и в большинстве случаев он не появляется, иногда я вижу 1 журнал. Я также регистрирую вызов onChange, который всегда вызывается один раз. Я понимаю, что он будет вызываться каждый раз, когда мой RealmModel обновляется, это поведение, которое я ищу. Я не понимаю, почему вызов initWithCourseDetails заставляет задуматься об изменении моей модели, это метод чистого представления. (добавляю код initWithCourseDetails) - person Renaud Favier; 01.09.2016
comment
Кстати, я только что получил ваш балл RealmChangeListener, я изменил его, чтобы он стал полем моего докладчика. Не могли бы вы просто объяснить мне еще раз, почему вы думаете, что onChange называется бесконечным временем? И я понимаю, что не сказал спасибо за ответ, что очень ценно, уверяю вас. - person Renaud Favier; 01.09.2016
comment
Ну, технически, если вы изменяете таблицу только один раз, то она должна вызываться только один раз. Однако, если он вызывается только один раз, ваш код не должен падать, если только ваши computeTableOfContent(element); или initWithCourseDetails каким-то образом не застревают в цикле... createObject() не должен работать, если вы не находитесь в транзакции, так что я немного сомневаюсь об этом коде. Могу ли я порекомендовать вам прочитать мой статья об использовании Realm? - person EpicPandaForce; 01.09.2016
comment
Realm в первую очередь предназначен для загрузки данных в фоновом потоке, сохранения их в фоновом потоке и автоматического отображения всех результатов в режиме автоматического обновления (в моем примере случае RecyclerView). Реактивные взгляды, по сути. Мне, вероятно, придется увидеть еще больше кода, чтобы дать правильный совет. - person EpicPandaForce; 01.09.2016
comment
Хорошо, я вижу, что в основе проблемы с концепцией, я внимательно прочитаю вашу статью, попробую сделать то, что хочу, и тогда вернусь сюда. Большое спасибо за ваше время. - person Renaud Favier; 01.09.2016
comment
Очень хорошая статья, я изменил все, чтобы соответствовать лучшим практикам, которые вы описали. Ошибка все еще была, но что-то появилось: это courseDetailed.addChangeListener(listener);, который замораживает мой поток пользовательского интерфейса! Даже если этот слушатель абсолютно ничего не делает (пустой onChange()). Я больше не знаю, где искать в моем коде - person Renaud Favier; 01.09.2016
comment
Вы случайно не используете realm.beginTransaction() где-нибудь в своем коде без фиксации или отмены после этого? - person EpicPandaForce; 01.09.2016
comment
Также стоит отметить, что если больше ничего не работает, отправьте свой код в help[at]realm.io, и они прочитают его и выяснят причину зависания. Вероятно, вы застряли в какой-то петле. - person EpicPandaForce; 01.09.2016
comment
Хорошо, я следил за вашей статьей, заменил все случаи использования области вашим способом ее реализации (я уже использовал область в небольшом модуле). Теперь работает нормально! Я не совсем понял, в чем проблема, подумал - person Renaud Favier; 02.09.2016
comment
Упс, поторопился.. Жук вернулся, похоже, у него есть собственное сознание - person Renaud Favier; 02.09.2016
comment
Это частный проект? Мне пришлось бы прочитать код, чтобы сказать вам, в чем проблема. - person EpicPandaForce; 02.09.2016
comment
Я только что сделал общедоступное репо без свойств gradle: github.com/renaudfavier/client-android-safe - person Renaud Favier; 02.09.2016
comment
Проект находится на рефакторинге, поэтому структура довольно каготична. Вы ищете com.m360.android.presentation_layer.player.presenter.OnlinePlayerPresenter и com.m360.android.presentation_layer.player.view.ModulePlayerActivity - person Renaud Favier; 02.09.2016
comment
Неато! Я загрузил его, вам, вероятно, следует удалить его, хотя теперь, учитывая, что вы, похоже, поделились своими хранилищами ключей (хотя, к счастью, пароли нет: P). Я посмотрю на это как можно скорее - person EpicPandaForce; 02.09.2016
comment
Давайте продолжим обсуждение в чате. - person Renaud Favier; 02.09.2016
comment
Извините, я отвечаю здесь, а не в чате, но я с мобильного телефона, и он не работает. Я просмотрел ваш пост, большое спасибо, центр уведомлений — это старая и отключенная часть кода, которая, как я думал, не может повлиять на эту часть, и было много советов, которые, я уверен, будут очень полезны для меня. Я буду вдали от цивилизации в течение 3 дней. Я дам вам обратную связь и обновлю вопрос во вторник. Спасибо еще раз! - person Renaud Favier; 03.09.2016
comment
А пока я частично написал эту статью для вас: medium.com/@Zhuinden/ - person EpicPandaForce; 03.09.2016