Pull to refresh

Comments 58

Очень рад видеть, что RxJava используется в реальном проекте. Это очень мощная библиотека, которую, ИМХО, должны использовать все Java-программисты, сталкивающиеся в своих проектах с реактивностью.
Отказался от использования RxJava, сильно портит читабельность и лаконичность кода.
Android разработка, 6 версия.
Ну да точно, там лямбд нету. Поэтому и не лаконично. Я конечно же говорил про Java 8.
У нас в мире Android большие проблемы с многопоточностью и обработкой результатов. Конечно есть кучу библиотек для этого, но все равно пока нет ничего лаконичного и красивого, чтобы код душу радовал.
Да, с красотой действительно беда, об этом я говорил. Но вот по эффективности RxJava очень хороша. Особенно в связке с Retrofit, если говорить про REST.
Согласен, по эффективности все хорошо, но простота кода не менее важный фактор.
А почему дайлер так долго стартует, до сих пор? При старте раскручивается данный аппарат, и эта задача полностью загружает девайс, что UI потоку не хватает «места» отрендерить клавиатурку и лоадинг истории вызовов? Спасибо.
Возможно ребята увлеклись многопоточностью. В свое время сравнивал что лучше — 4 тяжелых потока одновременно или последовательно. Результат удивил — по скорости многопоточность выиграла, но не в 4 раза быстрее, а процентов на 20-30. С другой стороны 4 одновременных потока очень сильно нагрели смартфон. Хотя сейчас это уже не так существенно. Сам стартую по 3 потока за раз, тем более что с android query это выглядит не менее изящно чем с RxJava:
        aq.progress(this).ajax(getCallBack(this, "user.get"));
        aq.progress(this).ajax(getCallBack(this, "user.getCategories"));
        aq.progress(this).ajax(getCallBack(this, "user.getChannelsWithTags&type=channels"));
ах, да еще и кеширование из коробки^^ обработка вьюх как в груви, работа с картинками и никаких жалких аутофмемори
public static AjaxCallback getCallBack(Object o,String method) {
        String url = SurfingServiceImpl.API_URL+"?method="+method;
        AjaxCallback<String> cb = new AjaxCallback<String>();
        cb.url(url).type(String.class).weakHandler(o, "onRefresh").fileCache(true).expire(1000 * 15);
        cb.header("Authorization", "Bearer " + SurfingbirdApplication.getInstance().getSettings().getLoginToken());
        return cb;
    }

Нет уж ребят, лучше Вы к нам
UFO just landed and posted this here
Цитата нужна? Поясню. Я искал узкое место в коде и возможности оптимизации. Выяснилось что 4 http запроса в 4 потока не будут быстрее в 4 раза 4 последовательных запросов. Но буквально раскаляли телефон (это был Нексус 3 по моему). По всей видимости это связано с тем что основные накладные расходы связаны с инициализацией http клиента, установкой соединения и так далее. Вобщем должно быть очевидно что реальные задачи сильно расходятся с синтетическими тестами. Кстати узкое место я тогда нашел, и им оказался дефолтный JSON парсер. Переход на SAX парсер дал прирост раз в 100 и облегчил работу GC.

Хотя в данном конретном случае — не думаю что дело в потоках вообще. Скорее всего инициализация/работа RxJava занимает много времени. За все надо платить. Нравятся Скала, RxJava — используйте. Но за Ваш комфорт программиста заплатят пользователи своим временем. И кстати в андроидквери вы заплатите увеличением программы килобайт на 200 и не потеряете в производительности. А получите то же самое. Вобщем я останусь ретроградом и продолжу кодить на яве.
Выяснилось что 4 http запроса в 4 потока не будут быстрее в 4 раза 4 последовательных запросов.


в Android SDK до сих пор не завезли честные асинхронные сетевые запросы, обязательно использовать треды?
UFO just landed and posted this here
Мы сейчас в википедии? о_О Я рассказал о своем исследовании и своих выводах, вот и все. Если Вам оно в чем то непонятно или Вы с ним не согласны — проведите свое.
UFO just landed and posted this here
Нотариально Заверенный Скриншот подойдет?
Алексей, учитывая вашу профессиональную личность, складывается ощущение, что вы издеваетесь над человеком. Он ведь даже не понимает, что в результатах его исследования отсутствует объект исследования.
Там не виснет UI, просто зеленый экран и ничего не происходит. Почему так? Потому что в дайлере все еще есть архитектурные части, которые требуют инициализации при старте приложения. Правильным решением является устранение этой необходимости, а не ProgressBar перед историей вызовов. Они никак не относятся к RxJava. Какая вообще может быть инициализация у RxJava? Ее нет. Разве что создание тред пулов и все вот это, но оно никак не трогает старт приложения.
Давно уже не использую AsyncTask, т.к. в аднроиде появились Loaders.
Возникает вопрос: как эта библиотека работает в случаях смены конфигурации экрана, например?
Давно уже не использую Loaders, т.к. в android «появились» сервисы
RxJava прекрасно работает в случаях смены конфигурации экрана. Ее это просто не трогает. Observable не привязан к Context' у активити, если вы все правильно делаете. И дальше вам решать, что и как делать.
Можно поподробнее отсюда? Если RxJava так же, как и AsyncTask работают внутри фрагментов, то при смене ориентации дисплее они будут запущены по новой.
или у вас все фрагменты setRetainInstance(true)?
Нет, конкретно у нас просто только портрет ориентация поддерживается, вот и все. А на деле все работает хорошо, потому что внутри себя Observable все-таки никак не связан с контекстом активити, только с контекстом самого Application. А при перевороте он сохраняется. А подписываться и отписываться (для того, чтобы обрабатывать и менять UI) можно хоть-сколько раз.
Писать приложение в одной ориентации конечно намного проще)
Приятно видеть что люди стали открывать для себя событийные языки. Но пока чтоRxJava не идет ни в какое сравнение ни с SystemC, ни с VHDL, ни c Verilog.
Мы у себя тоже используем RxJava, очень довольны.
> (были бы лямбды — был бы еще и красивым)
Это решается с помощью retrolambda:

buildscript {
repositories {
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:0.11.+'
classpath 'me.tatarka:gradle-retrolambda:1.3.+'
}
}

apply plugin: 'retrolambda'

retrolambda {
jdk System.getenv(«JAVA8_HOME»)
}

dependencies {
retrolambdaConfig 'net.orfjackal.retrolambda:retrolambda:1.+'

compile 'com.netflix.rxjava:rxjava-android:0.19.+'
compile 'com.netflix.rxjava:rxjava-async-util:0.19.+'
}

P.S. извиняюсь, почему то не работает :(
все работает. lambdas, rxjava, retrolambda, AS 0.8.1

вот тут есть работающий пример, из которого я брал конфиг
github.com/fs/android-base
Не возникало ли проблем с дебагом такого кода? Или при анализе стектрейсов, если что-то внутри Observable упало?

PS Спасибо за интересную статью.
Нет, никаких проблем. Android Studio спокойно заходит во все тела всех Observable и все хорошо. Единственное, что затрудняет дебаг, так это пошаговая отладка, потому что на пути от onNext() до, например, flatMap, если произошел retry() происходит много внутренних преобразований.

Со стектрейсами все ок, читабельно, ее умные ребята делали.
«Подобный код встречается во многих проектах, он понятен, а миллионы леммингов не могут ошибаться. Но давайте копнём чуть глубже:
Что делать, если где-то во время выполнения выпал Exception?
doInBackground(Void...) выполняется в отдельном потоке, как нам сказать пользователю об ошибке в UI? Заводить поля для Exception?
А что возвращать, если не прошел запрос? null?
А если json не валидный?
Что стоит делать, если не удалось кэшировать объект?
»

Возвращать из doInBackground не JSONObject, а экземпляр своего класса, у которого, скажем, будет два поля: status, msg, object. Если в doInBackground ошибка/исключение, то возвращаем из него свой объект с соответствующим статусом и сообщением. Потом в onPostExecute проверяем сие поле и уведомляем UI (так как doInBackground имеет доступ к UI). Так что проблема, по-моему, выдуманная )
fix: конечно же, onPostExecute имеет доступ к UI, а не doInBackground
UFO just landed and posted this here
Обработка исключений — это костыли?
UFO just landed and posted this here
robospice + retrofit, наверное, самое лучшее что можно использовать для rest запросов. Минисуют, видимо, те кто до сих пор использует AsyncTask
Если во время работы doInBackground возникает исключительная ситуация, выполнение асинк-таска можно закончить методом cancel(). В таком случае вместо onPostExecute вызовется onCancelled() в аргументах которого вы можете передать и причину и объект, если зохочется.
А если я не хочу cancel(), а хочу еще раз попробовать сделать запрос? Понятно, что ASyncTask вполне самодостаточная вещь в каокм-то смысле. Но ее функционал все же ограничен, стоит признать.
это был комментарий господину Suvitruf, с примером того, как реагировать на ошибки в таске, а не к статье в целом.
А при том, что у нас в 2GisDialer используется RxJava. А 2GisDialer — это приложение, написанное под Android OS. Кроме того, в статье приведено сравнение подходов к многопоточности в Android с использованием ASyncTask и Observable. Также замечу, что класс ASyncTask является частью Android SDK и его нет в Java.
Против самого примера ничего не имею, я о другом. Если не знать что такое RxJava и читать по диагонали, то можно упустить важный факт, что RxJava — это не только «под Android». Т.е. значительно лучше было бы акцентировать внимание на том, что это для «Java в общем, и для Android в частности». Я, например, уверен, что многие люди уже из-за одного названия статью просто не открыли.
Проблемы, с которыми мы столкнулись

Вам не кажеться что одна проблема вытекает из другой и количество потоков тут не причем (так как приходилось и с большим количеством работать, которые обрабатывали видео/аудио).

Вы кешируете каждый пчих в lru cache, который храниться в памяти (±32Мб — размер кеша). Вот и получаеться что чем больше потоков тем больше записи в lru cache, тем больше вызова GC после вытеснения, а вот GC умеет останавливает UI Thread и вам кажеться что это потоки тупят систему, а на самом деле вызовы GC в лучшем случае линейно зависят от количчества потоков.

Учитывая что вставка идет JSON, то это подразумевает Network операции, это тоже память, а если где то паралельно грузяться картинки (упаси в оригинальном размере), то не удевительно что у вас просто тупит приложение из за частого вызова GC. А ограничение в 4-е потока это просто приемлемый хак, но не решение проблемы.
Для кеширования ответов с сервера лучше DiskLruCache использовать (если конечно он и не используеться), учитывая что в сетевые операция изначально заложены задержки.
final Subscription subscription =
          Observable.create(new Observable.OnSubscribe<String>() {


Разве не так?

final Observable observable =
         Observable.create(new Observable.OnSubscribe<String>() {


Как раз решил разобраться с Java RX. Пример не пошел
Да, разумеется, это описка. Спасибо.
Единственный метод, который возвращает не новый Observable, а Subscription — это метод subscribe()
Там наверное в конце пропущено }).subscribe();
Именно. Убрал за ненадобностью в примере, а тип ссылки не поменял.
Попробовал на фиде конверторе
github.com/app-z/CurrencyConverter2

Тогда еще вопрос задам, специалусту в RX Java
final Subscription subscription =…
Не надо делать unSubscribe в onDestroy? Т.е. я имею ввиду выносить наружу subscription из метода или из onCreate и делать член класса
Я к тому что сабскрайбер сам разберется когда прервать работу если активити уничтожается?
> сабскрайбер сам разберется когда прервать работу если активити уничтожается

unsubscribe надо делать вручную. RxJava ничего не знает про жизненный цикл Activity или каких-либо других сущностей Android. Немного подробнее можно прочитать здесь в разделе «Fragment and Activity life-cycle».
Теоретически CachedThreadPool тоже может положить приложение, если, к примеру, одновременно в пул на выполнение отправляются штук 20 немаленьких задач, таким образом создается штук 20 потоков.
В AsyncTask, например, создается ThreadPoolExecutor c параметрами CORE_POOL_SIZE = CPU_COUNT + 1 и MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1, где CPU_COUNT — количество процессоров на устройстве. Таким образом, если на устройстве 2 процессора, то количество потоков не будет превышать 5. По идее данный вариант самый безопасный. Но и с вашим вероятность падения очень маленькая.
А так, спасибо за статью, весьма полезная)
Полностью согласен с вами. Добавлю так же, что лучше создавать несколько тредпулов с меньшим количеством тредов.

Например, тредпул с CPU_COUNT/2 для работы с сетью, такой же для выполнения рутинных задач и FixedThreadPoolExecutor на 2-3 треда для выполнения очень приоритетных задач (коих должно быть не много по задумке).

Так проще регулировать работы обсерваблов, если их становится много, а так же позволяет приоритезировать задачи. Обычно это работает неплохо.
Было бы классно, если бы Вы добавили в статью решение еще одного примера.
Есть у нас метод запроса в сеть с параметрами offset, limit. Назовем метод request(int offset, int limit). Нам нужно получить весь массив данных. То есть скорее всего придется вызвать несколько раз request с разными параметрами offset и limit, и полученные массивы соединить в один.
Один облегчающий фактор в том, что метод request возвращает Observable (то есть работаем через RetroFit).
Собственно как должна выглядеть вся портянка итогового Observable, чтобы при подписке к нему, мы сразу получали весь массив данных?
Честно говоря, сколько гуглил, так и не смог найти понятного примера. Может вы с этим уже сталкивались?)
Sign up to leave a comment.