У меня возникла ошибка при запуске моего проекта Android для RssReader.
Код:
URL url = new URL(urlToRssFeed); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); XMLReader xmlreader = parser.getXMLReader(); RssHandler theRSSHandler = new RssHandler(); xmlreader.setContentHandler(theRSSHandler); InputSource is = new InputSource(url.openStream()); xmlreader.parse(is); return theRSSHandler.getFeed();
И это показывает ошибку ниже:
android.os.NetworkOnMainThreadException
Как я могу исправить эту проблему?
Это исключение возникает, когда приложение пытается выполнить сетевую операцию в своем основном потоке. Запустите свой код в AsyncTask
:
class RetrieveFeedTask extends AsyncTask<String, Void, RSSFeed> { private Exception exception; protected RSSFeed doInBackground(String... urls) { try { URL url = new URL(urls[0]); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); XMLReader xmlreader = parser.getXMLReader(); RssHandler theRSSHandler = new RssHandler(); xmlreader.setContentHandler(theRSSHandler); InputSource is = new InputSource(url.openStream()); xmlreader.parse(is); return theRSSHandler.getFeed(); } catch (Exception e) { this.exception = e; return null; } finally { is.close(); } } protected void onPostExecute(RSSFeed feed) { // TODO: check this.exception // TODO: do something with the feed } }
Как выполнить задачу:
В файле MainActivity.java
вы можете добавить эту строку в свой oncreate()
new RetrieveFeedTask().execute(urlToRssFeed);
Не забудьте добавить это в файл AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET"/>
Вы должны почти всегда запускать сетевые операции в потоке или как асинхронную задачу. Но если вы знаете лучше и готовы принять последствия и должны выполнять сетевые операции в основном потоке, вы можете переопределить поведение по умолчанию:
Добавить:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);
В вашем классе,
а также
ADD это разрешение в файле android manifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
Последствия:
Ваше приложение (в зонах пятнистого интернет-соединения) перестает отвечать на запросы и блокируется, пользователь воспринимает медленность и должен убить силы, и вы рискуете, что менеджер активности убил ваше приложение и сообщил пользователю, что приложение остановлено.
У Android есть несколько хороших советов о хороших практиках программирования для разработки для реагирования: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html
Я решил эту проблему, используя новую Thread
.
Thread thread = new Thread(new Runnable() { @Override public void run() { try { //Your code goes here } catch (Exception e) { e.printStackTrace(); } } }); thread.start();
Вы не можете выполнять сетевой ввод-вывод в потоке пользовательского интерфейса на Honeycomb . Технически это возможно в более ранних версиях Android, но это действительно плохая идея, так как это заставит ваше приложение перестать отвечать на запросы и может привести к тому, что ОС будет убивать ваше приложение за плохое поведение. Вам нужно будет запустить фоновый процесс или использовать AsyncTask для выполнения сетевой транзакции в фоновом потоке.
На сайте разработчика Android есть статья о Painless Threading, которая является хорошим знакомством с этим, и она предоставит вам гораздо более глубокую глубину ответа, чем это может быть реально реализовано здесь.
Принятый ответ имеет некоторые существенные сторонники. Не рекомендуется использовать AsyncTask для взаимодействия, если вы действительно не знаете, что делаете. Некоторые из сторон включают:
executeOnExecutor
и не предоставляете альтернативный исполнитель). Код, который отлично работает при серийном запуске ICS, может прерываться при одновременном выполнении на Gingerbread, скажем, если у вас есть непреднамеренные зависимости от порядка выполнения. Если вы хотите избежать кратковременных утечек памяти, иметь четко определенные характеристики выполнения на всех платформах и иметь базу для создания действительно надежной сетевой обработки, вы можете подумать:
IntentService
этого используйте Service
или IntentService
, возможно, с помощью PendingIntent
чтобы вернуть результат с помощью метода Activity onActivityResult
. Down-сторона:
AsyncTask
, хотя и не так сильно, как вы думаете IntentService
эквивалентной реализацией Service
, возможно, как этот . Up-сторон:
onActivityResult
AsyncTask
в Activity
, но если пользовательский контекст отключит приложение для совершения телефонного звонка, система может убить приложение до завершения загрузки. С меньшей вероятностью можно убить приложение с активной Service
. IntentService
(например, связанную выше), вы можете контролировать уровень параллелизма через Executor
. Вы можете реализовать IntentService
для выполнения загрузок на одном фоновом потоке довольно легко.
Шаг 1. Создайте IntentService
для выполнения загрузки. Вы можете сказать, что загрузить через Intent
extra, и передать ему PendingIntent
чтобы использовать для возврата результата в Activity
:
import android.app.IntentService; import android.app.PendingIntent; import android.content.Intent; import android.util.Log; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; public class DownloadIntentService extends IntentService { private static final String TAG = DownloadIntentService.class.getSimpleName(); public static final String PENDING_RESULT_EXTRA = "pending_result"; public static final String URL_EXTRA = "url"; public static final String RSS_RESULT_EXTRA = "url"; public static final int RESULT_CODE = 0; public static final int INVALID_URL_CODE = 1; public static final int ERROR_CODE = 2; private IllustrativeRSSParser parser; public DownloadIntentService() { super(TAG); // make one and re-use, in the case where more than one intent is queued parser = new IllustrativeRSSParser(); } @Override protected void onHandleIntent(Intent intent) { PendingIntent reply = intent.getParcelableExtra(PENDING_RESULT_EXTRA); InputStream in = null; try { try { URL url = new URL(intent.getStringExtra(URL_EXTRA)); IllustrativeRSS rss = parser.parse(in = url.openStream()); Intent result = new Intent(); result.putExtra(RSS_RESULT_EXTRA, rss); reply.send(this, RESULT_CODE, result); } catch (MalformedURLException exc) { reply.send(INVALID_URL_CODE); } catch (Exception exc) { // could do better by treating the different sax/xml exceptions individually reply.send(ERROR_CODE); } } catch (PendingIntent.CanceledException exc) { Log.i(TAG, "reply cancelled", exc); } } }
Шаг 2. Зарегистрируйте службу в манифесте:
<service android:name=".DownloadIntentService" android:exported="false"/>
Шаг 3. Вызовите службу из Activity, передав объект PendingResult, который Служба будет использовать для возврата результата:
PendingIntent pendingResult = createPendingResult( RSS_DOWNLOAD_REQUEST_CODE, new Intent(), 0); Intent intent = new Intent(getApplicationContext(), DownloadIntentService.class); intent.putExtra(DownloadIntentService.URL_EXTRA, URL); intent.putExtra(DownloadIntentService.PENDING_RESULT_EXTRA, pendingResult); startService(intent);
Шаг 4: Обработать результат в onActivityResult:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == RSS_DOWNLOAD_REQUEST_CODE) { switch (resultCode) { case DownloadIntentService.INVALID_URL_CODE: handleInvalidURL(); break; case DownloadIntentService.ERROR_CODE: handleError(data); break; case DownloadIntentService.RESULT_CODE: handleRSS(data); break; } handleRSS(data); } super.onActivityResult(requestCode, resultCode, data); }
Здесь доступен проект github, содержащий полный рабочий проект Android-Studio / gradle.
Использовать службу или AsyncTask
См. Также вопрос переполнения стека:
android.os.NetworkOnMainThreadException отправляет электронное письмо с Android
Делать сетевые действия в другом потоке
Например:
new Thread(new Runnable(){ @Override public void run() { // Do network action in this function } }).start();
И добавьте это в AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
Вы отключите строгий режим, используя следующий код:
if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); }
Это не рекомендуется : используйте интерфейс AsyncTask
.
Полный код для обоих методов
Сетевые операции не могут выполняться в основном потоке. Вам нужно запустить все сетевые задачи на дочернем потоке или реализовать AsyncTask.
Вот как вы запускаете задачу в дочернем потоке:
new Thread(new Runnable(){ @Override public void run() { try { // Your implementation goes here } catch (Exception ex) { ex.printStackTrace(); } } }).start();
Поместите свой код внутри:
new Thread(new Runnable(){ @Override public void run() { try { // Your implementation } catch (Exception ex) { ex.printStackTrace(); } } }).start();
Или:
class DemoTask extends AsyncTask<Void, Void, Void> { protected Void doInBackground(Void... arg0) { //Your implementation } protected void onPostExecute(Void result) { // TODO: do something with the feed } }
Использование аннотаций Android – это вариант. Это позволит вам просто запустить любой метод в фоновом потоке:
// normal method private void normal() { doSomething(); // do something in background } @Background protected void doSomething() // run your networking code here }
Обратите внимание, что, хотя он обеспечивает преимущества простоты и удобочитаемости, он имеет свои недостатки.
Это происходит в Android 3.0 и выше. Начиная с Android 3.0 и выше, они ограничили использование сетевых операций (функций, которые обращаются к Интернету) из потока в потоке основного потока / пользовательского интерфейса (что возникает из ваших методов create и on resume в действии).
Это должно стимулировать использование отдельных потоков для сетевых операций. См. AsyncTask для получения более подробной информации о том, как правильно выполнять сетевые действия.
Вы не должны выполнять какую-то трудоемкую задачу в основном потоке (потоке пользовательского интерфейса), как в любой сетевой операции, операции ввода-вывода или SQLite базы данных. Поэтому для такого рода операций вы должны создать рабочий поток, но проблема в том, что вы не можете напрямую выполнять какую-либо операцию, связанную с пользовательским интерфейсом, из рабочего потока. Для этого вам нужно использовать Handler
и передать Message
.
Чтобы упростить все эти вещи, Android предоставляет различные способы, такие как AsyncTask
, AsyncTaskLoader
, CursorLoader
или IntentService
. Таким образом, вы можете использовать любой из них в соответствии с вашими требованиями.
Ошибка связана с выполнением длительных операций в основном потоке, вы можете легко исправить проблему, используя AsynTask или Thread . Вы можете проверить эту библиотеку AsyncHTTPClient для лучшей обработки.
AsyncHttpClient client = new AsyncHttpClient(); client.get("http://www.google.com", new AsyncHttpResponseHandler() { @Override public void onStart() { // Called before a request is started } @Override public void onSuccess(int statusCode, Header[] headers, byte[] response) { // Called when response HTTP status is "200 OK" } @Override public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) { // Called when response HTTP status is "4XX" (for example, 401, 403, 404) } @Override public void onRetry(int retryNo) { // Called when request is retried } });
Верхний ответ спектом работает отлично.
Если вы пишете AsyncTask
inline и не расширяетесь как класс, и, кроме того, если есть необходимость получить ответ из AsyncTask
, можно использовать метод get()
как AsyncTask
ниже.
RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();
(Из его примера.)
Это делается только для приложений, ориентированных на SDK Honeycomb или выше. Приложениям, использующим более ранние версии SDK, разрешено создавать сети в своих потоках цикла основного события.
Ошибка – предупреждение SDK!
Для меня это было так:
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="10" />
Устройство, на котором я тестировало свое приложение, было 4.1.2, которое представляет собой версию SDK версии 16!
Убедитесь, что целевая версия совпадает с вашей целевой целевой библиотекой Android. Если вы не знаете, что такое ваша целевая библиотека, щелкните правой кнопкой мыши ваш проект -> Путь сборки -> Android , и он должен быть отмечен галочкой.
Кроме того, как указывали другие, укажите правильные разрешения на доступ в Интернет:
<uses-permission android:name="android.permission.INTERNET"/>
Просто для того, чтобы четко сказать что-то:
Основной поток – это в основном поток пользовательского интерфейса.
Поэтому, говоря, что вы не можете выполнять сетевые операции в основном потоке, вы не можете выполнять сетевые операции в потоке пользовательского интерфейса, а это означает, что вы не можете выполнять сетевые операции в *runOnUiThread(new Runnable() { ... }*
в каком-то другом потоке , или.
(У меня просто был длинный головокружающий момент, пытаясь понять, почему я получаю эту ошибку где-то, кроме моего основного потока. Вот почему, этот поток помог, и, надеюсь, этот комментарий поможет кому-то другому.)
Это исключение возникает из-за любой тяжелой задачи, выполняемой в основном потоке, если эта выполняющая задача занимает слишком много времени .
Чтобы этого избежать, мы можем обрабатывать его с помощью потоков или исполнителей
Executors.newSingleThreadExecutor().submit(new Runnable() { @Override public void run() { // You can perform your task here. } });
**Use like this in Your Activity** btnsub.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub //Initialize soap request + add parameters SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME1); //Use this to add parameters request.addProperty("pincode",txtpincode.getText().toString()); request.addProperty("bg",bloodgroup.getSelectedItem().toString()); //Declare the version of the SOAP request SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11); envelope.setOutputSoapObject(request); envelope.dotNet = true; try { HttpTransportSE androidHttpTransport = new HttpTransportSE(URL); //this is the actual part that will call the webservice androidHttpTransport.call(SOAP_ACTION1, envelope); // Get the SoapResult from the envelope body. SoapObject result = (SoapObject)envelope.getResponse(); Log.e("result data", "data"+result); SoapObject root = (SoapObject) result.getProperty(0); // SoapObject s_deals = (SoapObject) root.getProperty(0); //SoapObject s_deals_1 = (SoapObject) s_deals.getProperty(0); // System.out.println("********Count : "+ root.getPropertyCount()); value=new ArrayList<Detailinfo>(); for (int i = 0; i < root.getPropertyCount(); i++) { SoapObject s_deals = (SoapObject) root.getProperty(i); Detailinfo info=new Detailinfo(); info.setFirstName( s_deals.getProperty("Firstname").toString()); info.setLastName( s_deals.getProperty("Lastname").toString()); info.setDOB( s_deals.getProperty("DOB").toString()); info.setGender( s_deals.getProperty("Gender").toString()); info.setAddress( s_deals.getProperty("Address").toString()); info.setCity( s_deals.getProperty("City").toString()); info.setState( s_deals.getProperty("State").toString()); info.setPinecode( s_deals.getProperty("Pinecode").toString()); info.setMobile( s_deals.getProperty("Mobile").toString()); info.setEmail( s_deals.getProperty("Email").toString()); info.setBloodgroup( s_deals.getProperty("Bloodgroup").toString()); info.setAdddate( s_deals.getProperty("Adddate").toString()); info.setWaight(s_deals.getProperty("waight").toString()); value.add(info); } } catch (Exception e) { e.printStackTrace(); } Intent inten=new Intent(getApplicationContext(),ComposeMail.class); //intent.putParcelableArrayListExtra("valuesList", value); startActivity(inten); } }).start(); } });
-**Use like this in Your Activity** btnsub.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub //Initialize soap request + add parameters SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME1); //Use this to add parameters request.addProperty("pincode",txtpincode.getText().toString()); request.addProperty("bg",bloodgroup.getSelectedItem().toString()); //Declare the version of the SOAP request SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11); envelope.setOutputSoapObject(request); envelope.dotNet = true; try { HttpTransportSE androidHttpTransport = new HttpTransportSE(URL); //this is the actual part that will call the webservice androidHttpTransport.call(SOAP_ACTION1, envelope); // Get the SoapResult from the envelope body. SoapObject result = (SoapObject)envelope.getResponse(); Log.e("result data", "data"+result); SoapObject root = (SoapObject) result.getProperty(0); // SoapObject s_deals = (SoapObject) root.getProperty(0); //SoapObject s_deals_1 = (SoapObject) s_deals.getProperty(0); // System.out.println("********Count : "+ root.getPropertyCount()); value=new ArrayList<Detailinfo>(); for (int i = 0; i < root.getPropertyCount(); i++) { SoapObject s_deals = (SoapObject) root.getProperty(i); Detailinfo info=new Detailinfo(); info.setFirstName( s_deals.getProperty("Firstname").toString()); info.setLastName( s_deals.getProperty("Lastname").toString()); info.setDOB( s_deals.getProperty("DOB").toString()); info.setGender( s_deals.getProperty("Gender").toString()); info.setAddress( s_deals.getProperty("Address").toString()); info.setCity( s_deals.getProperty("City").toString()); info.setState( s_deals.getProperty("State").toString()); info.setPinecode( s_deals.getProperty("Pinecode").toString()); info.setMobile( s_deals.getProperty("Mobile").toString()); info.setEmail( s_deals.getProperty("Email").toString()); info.setBloodgroup( s_deals.getProperty("Bloodgroup").toString()); info.setAdddate( s_deals.getProperty("Adddate").toString()); info.setWaight(s_deals.getProperty("waight").toString()); value.add(info); } } catch (Exception e) { e.printStackTrace(); } Intent inten=new Intent(getApplicationContext(),ComposeMail.class); //intent.putParcelableArrayListExtra("valuesList", value); startActivity(inten); } }).start(); } });
Говоря простыми словами,
НЕ ДЕЛАЙТЕ СЕТЕВЫЕ РАБОТЫ В РЕЖИМЕ UI
Например, если вы выполняете HTTP-запрос, это сетевое действие.
Решение:
Путь:
Поместите все свои работы внутри
run()
новый поток doInBackground()
класса AsyncTask. Но:
Когда вы получаете что-то из ответа сети и хотите показать его на своем представлении (например, сообщение ответа дисплея в TextView), вам нужно вернуться обратно в поток пользовательского интерфейса .
Если вы этого не сделаете, вы получите ViewRootImpl$CalledFromWrongThreadException
.
Как?
onPostExecute()
runOnUiThread()
и обновите представление внутри метода run()
. Хотя выше есть огромный пул решений, никто не упоминается com.koushikdutta.ion
: https://github.com/koush/ion
Он также асинхронен и очень прост в использовании:
Ion.with(context) .load("http://example.com/thing.json") .asJsonObject() .setCallback(new FutureCallback<JsonObject>() { @Override public void onCompleted(Exception e, JsonObject result) { // do stuff with the result or error } });
Это работает. Просто сделал ответ доктора Луиджи немного проще.
new Thread() { @Override public void run() { try { //Your code goes here } catch (Exception e) { e.printStackTrace(); } } }.start();
В Android, сетевые операции не могут выполняться в основном потоке. Вы можете использовать Thread, AsyncTask (краткосрочные задачи), Service (долгосрочные задачи) для выполнения сетевых операций.
Доступ к сетевым ресурсам из основного (UI) потока вызывает это исключение. Используйте отдельный поток или AsyncTask для доступа к сетевому ресурсу, чтобы избежать этой проблемы.
Существует еще один очень удобный способ решения этой проблемы – использовать возможности параллелизма rxJava. Вы можете выполнить любую задачу в фоновом режиме и опубликовать результаты в основной поток очень удобным способом, чтобы эти результаты были переданы цепочке обработки.
Первый проверенный ответ – использование AsynTask. Да, это решение, но сейчас оно устарело, потому что вокруг есть новые инструменты.
String getUrl() { return "SomeUrl"; } private Object makeCallParseResponse(String url) { return null; // } private void processResponse(Object o) { }
Метод getUrl предоставляет URL-адрес, и он будет выполнен в основном потоке.
makeCallParseResponse (..) – действительно ли работа
processResponse (..) – обрабатывает результат по основному потоку.
Код для асинхронного выполнения будет выглядеть так:
rx.Observable.defer(new Func0<rx.Observable<String>>() { @Override public rx.Observable<String> call() { return rx.Observable.just(getUrl()); } }) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .map(new Func1<String, Object>() { @Override public Object call(final String s) { return makeCallParseResponse(s); } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<Object>() { @Override public void call(Object o) { processResponse(o); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { // Process error here, it will be posted on // the main thread } });
По сравнению с AsyncTask этот метод позволяет переключать планировщики произвольным числом раз (скажем, извлекать данные на одном планировщике и обрабатывать эти данные на другом (скажем, Scheduler.computation ()). Вы также можете определить собственные планировщики.
Чтобы использовать эту библиотеку, включите следующие строки в файл build.gradle:
compile 'io.reactivex:rxjava:1.1.5' compile 'io.reactivex:rxandroid:1.2.0'
Последняя зависимость включает поддержку планировщика .mainThread ().
Существует отличная книга для rx-java .
Вы также можете решить эту проблему, используя Strict Mode, используя приведенный ниже код. Это также альтернатива решению этой проблемы.
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy);
Но лучше всего использовать AsyncTask.
Вам не разрешено выполнять сетевые операции в потоке пользовательского интерфейса на Android. Вам нужно будет использовать класс AsyncTask для выполнения связанных с сетью операций, таких как отправка запроса API, загрузка изображения с URL-адреса и т. Д. И использование методов обратного вызова AsyncTask, вы можете получить результат inPostExecute menthod, и вы попадете в поток пользовательского интерфейса, и вы может заполнять пользовательский интерфейс данными из веб-службы или что-то в этом роде.
Пример. Предположим, вы хотите загрузить изображение с URL-адреса: http://img.ruphp.com/android/sampleimage.jpg
Решение с использованием AsyncTask: соответственно.
public class MyDownloader extends AsyncTask<String,Void,Bitmap> { @Override protected void onPreExecute() { // Show progress dialog super.onPreExecute(); } @Override protected void onPostExecute(Bitmap bitmap) { //Populate Ui super.onPostExecute(bitmap); } @Override protected Bitmap doInBackground(String... params) { // Open URL connection read bitmaps and return form here return result; } @Override protected void onProgressUpdate(Void... values) { // Show progress update super.onProgressUpdate(values); } } }
Примечание. Не забудьте добавить разрешение Интернета в файл манифеста Android. Он будет работать как шарм. 🙂
Это исключение возникает, когда приложение пытается выполнить сетевую операцию в своем основном потоке. Если ваша задача заняла более пяти секунд, она приближается к силе.
Запустите свой код в AsyncTask
:
class RetrieveFeedTask extends AsyncTask<String, Void, Boolean> { protected RSSFeed doInBackground(String... urls) { // TODO: Connect } protected void onPostExecute(RSSFeed feed) { // TODO: Check this.exception // TODO: Do something with the feed } }
RxAndroid
– еще одна лучшая альтернатива этой проблеме, и это избавляет нас от проблем создания потоков, а затем публикует результаты в потоке Android UI. We just need to specify threads on which tasks need to be executed and everything is handled internally.
Observable<List<String>> musicShowsObservable = Observable.fromCallable(new Callable<List<String>>() { @Override public List<String> call() { return mRestClient.getFavoriteMusicShows(); } }); mMusicShowSubscription = musicShowsObservable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<List<String>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(List<String> musicShows){ listMusicShows(musicShows); } });
By specifiying (Schedulers.io())
,RxAndroid will run getFavoriteMusicShows()
on a different thread.
By using AndroidSchedulers.mainThread()
we want to observe this Observable on the UI thread, ie we want our onNext()
callback to be called on the UI thread