- android.os.NetworkOnMainThreadException crashed Android App #178
- Comments
- shenybluemix commented Feb 5, 2016
- shenybluemix commented Feb 5, 2016
- shenybluemix commented Feb 5, 2016
- VidyasagarMSC commented Feb 5, 2016
- germanattanasio commented Feb 5, 2016
- esdrasdemorais commented Sep 30, 2019 •
- Некоторые “подводные камни” разработки под Android
- NetworkOnMainThreadException #3184
- Comments
- rusmichal commented Feb 22, 2017
- swankjesse commented Feb 22, 2017
- rusmichal commented Feb 23, 2017
- DavidEdwards commented Feb 23, 2017
- rusmichal commented Feb 23, 2017
- DavidEdwards commented Feb 23, 2017 •
- rusmichal commented Feb 23, 2017
- DavidEdwards commented Feb 23, 2017
- rusmichal commented Feb 23, 2017
- swankjesse commented Mar 18, 2017
- RafalManka commented Feb 11, 2019 •
android.os.NetworkOnMainThreadException crashed Android App #178
Comments
shenybluemix commented Feb 5, 2016
Using SDK 2.6 to call Alchemy Sentiment Analysis. Running the sample code in README. The whole Android App (Android SDK 23 ) is crashed when because of android.os.NetworkOnMainThreadException ( looks like from okhttp library) when calling getSentiment() function.
The text was updated successfully, but these errors were encountered:
shenybluemix commented Feb 5, 2016
The sample code I am running in my Android App is as follows
shenybluemix commented Feb 5, 2016
B.T.W I am using SDK 2.6 because I met the issue #167
VidyasagarMSC commented Feb 5, 2016
Starting from HoneyComb (SDK 3.0), Android throws this NetworkOnMainThreadException if a long operation such as network access or database query is using it’s Main Thread aka UI Thread.
One fix which I can think of is running your code in AsyncTask.
Note: Luckily , okHTTP doesn’t use Apache HTTPClient which is deprecated effective API 23/ Android SDK 6.0
germanattanasio commented Feb 5, 2016
As @VidyasagarMSC said. You need to run the code in a different Thread.
esdrasdemorais commented Sep 30, 2019 •
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Источник
Некоторые “подводные камни” разработки под Android
Недавно наша команда завершила разработку приложения под Android. В процессе разработки и затем поддержки мы столкнулись с некоторыми проблемами технического характера. Часть из них — это наши баги, которых мы могли бы избежать, другая часть — это совсем неочевидные особенности Android, которые либо плохо описаны в документации, либо не описаны вообще.
В этой статье я бы хотел рассмотреть несколько реальных багов, которые возникли у наших пользователей и рассказать о путях их решения.
Статья не претендует на подробный анализ потенциальных проблем, это просто рассказ из жизни одно реального Android приложения.
RTFM (http://en.wikipedia.org/wiki/RTFM)
Так как при разработке под Android надо иметь в виду, что ваше приложение может работать на огромном количестве различных устройств, то надо думать о проблемах их совместимости.
Вот например одна из ошибок, которая возникала у наших пользователей:
А причина — проста, согласно документации класс android.util.Patterns доступен начиная с версии API 8 (Android 2.2.x), а у пользователя была версия 2.1. Решили мы это конечно оберткой этого кода в try/catch .
Вот еще одна подобная проблема вызванная невнимательным чтением документации:
Все дело в том, что strict mode (http://developer.android.com/reference/android/os/StrictMode.html) был включен по умолчанию в Android начиная с версии 3.0. Это значит, что ваше приложение не может обращаться к сети напрямую из основного UI потока, так как это может занимать некоторое время и при этом основной поток блокируется и не отвечает на другие события. Мы старались избегать подобного поведения, но в одном из мест все-таки остался простой сетевой вызов. Решена эта проблема была тем, что мы вынесли этот код в другой поток.
Поворот устройства — что может быть проще и привычнее для пользователя?
Казалось бы, для мобильных устройств смена ориентации экрана — это вещь настолько часто используемая и привычная, что это должно отразиться и в API. Т.е. эта ситуация должна обрабатываться очень просто. Но нет. Тут много нюансов.
Допустим, что нам необходимо сделать приложение, которое загружает список чего-либо и выводит его на экран. Т.е. при старте Activity (в методе onCreate() ) мы запускаем поток (чтобы не блокировать UI поток), который будет загружать данные. Этот поток отрабатывает некоторое время, поэтому мы будет отображать процесс загрузки в ProgressDialog . Все просто и замечательно работает.
Но, после загрузки, пользователь повернул устройство и тут мы обнаруживаем, что снова появился ProgressDialog и мы опять загружаем наши данные. Но ведь ничего не изменилось? Просто человеку удобнее смотреть на этот список повернув устройство.
А все дело в том, что метод onCreate() вызывается не только при создании Activity , но и при повороте экрана! Но мы не хотим засталять пользователя снова ждать загрузки. Все что нам надо — это снова показать уже загруженные данные.
Если мы поищем в интернете, мы найдем много ссылок на описание этой проблемы и также огромное количество примеров как решить эту проблему. И что самое плохое, что большинство таких ссылок предлагает неверное решение. Вот пример этого — http://codesex.org/articles/33-android-rotate
Не делайте обработку поворота экрана через onConfigurationChanged() ! Это неверно! Официальная документация ясно утверждает что “Using this attribute should be avoided and used only as a last-resort.“ http://developer.android.com/guide/topics/manifest/activity-element.html#config
А вот правильный подход описан здесь — http://developer.android.com/guide/topics/resources/runtime-changes.html И как показывает практика — он не прост в имплементации.
Идея в том, что перед разворотом экрана Android вызовет метод onRetainNonConfigurationInstance() вашего Activity . И вы можете вернуть данные (например список загруженных объектов) из этого метода, которые и будут сохранены между 2 вызовами метода onCreate() вашего Activity . Затем при вызове onCreate() вы можете вызвать getLastNonConfigurationInstance() который и вернет вам сохраненные данные. Т.е. при создании Activity вы вызываете getLastNonConfigurationInstance() и если он вам вернул данные — то эти данные уже были загружены и надо только отобразить их. Если же не вернул данные — то запускаете загрузку.
Но вот на практике ситуация выглядит так. У нас может быть 2 варианта когда пользователь поворачивает устройство. Первый вариант — когда идет загрузка данных (работает наш поток который загружает данные и при этом отображается ProgressDialog ) или данные уже загружены и мы сохранили их в списке для отображения. Получается, что в первом случае мы при повороте должны сохранить ссылку на работающий поток, а во втором случае ссылку на уже загруженный список. Мы так и делаем.
Но это усложняет наш код и не кажется мне простым и интуитивным. Более того, когда идет смена ориентации экрана и мы сохранили поток загрузки данных, мы потом вынуждены при onCreate() опять восстанавливать ProgressDialog ! А если добавить сюда, что в нашем приложении пользователь может загружать данные из разных мест и у нас не один поток загрузки данных, а несколько — то количество кода которое обслуживает простой поворот экрана становится просто огромным.
Честно сказать, я не понимаю почему это было сделано так сложно.
Чуть более подробнее о потоках или использование AsyncTask.
Давайте рассмотрим загрузку данных в другом потоке чуть более подробно, так как и при этом нас ждали неожиданные “сюрпризы”.
Немного теории: для облегчения создания и работы в потоке отличном от основного UI потока был сделан специальный класс — AsyncTask (http://developer.android.com/reference/android/os/AsyncTask.html)
Суть его в том, что в нем уже есть готовые методы onPreExecute() и onPostExecute(Result) , которые выполняются в основном UI потоке и которые служат для отображение чего-либо и есть метод doInBackground(Params. ) внутри которого и происходит основная работа и он запускается в отдельном потоке автоматически. Вот примерный код как это выглядит:
Все просто и красиво.
Но теперь — немного практики. Вот Вам ошибка от реального пользователя:
А эта ошибка означает, что когда мы вызываем spinner.show() в методе onPreExecute() , то этот ProgressDialog созданный со ссылкой на MyActivity уже неактивен и его нет на экране! Ладно, я еще понимаю как такое может быть при вызове onPostExecute() . Т.е. например пока мы загружали данные пользователь нажал Back и наш ProgressDialog ушел с экрана. Но как такое может быть сразу при вызове загрузки, когда этот код стартует сразу при затуске Activity — это для меня неясно.
Но в любом случае, мы должны обрабатывать такие ситуации поэтому мы решили это обеткой методов spinner.show() и spinner.dismiss() в try/catch . Решение конечно не очень красивое, но в нашем случае — вполне функциональное.
Кстати, такой же код есть и например в Facebook SDK for Android, который был разработан другими опытными разрабочиками. И там тоже у нас были ситуации когда приложение вылетало при закрытии ProgressDialog . Нам пришлось добавить обработку и в их код. Так что проблема эта — не только в нас.
Добавьте сюда еще проблему, что была описана ранее. Что при повороте устройства надо пересоздавать ProgressDialog если поворот был во время загрузки данных. Это тоже добавит сюда вспомогательного кода.
И еще стоит вспомнить что метод doInBackground() выполняется в отдельном потоке и поэтому если при загрузке данных произошла ошибка, то нельзя выдать Alert прямо оттуда, так как это не UI поток. Надо сохранить ошибку, а затем после выхода из потока загрузки в методе onPostExecute(Void result) уже можно что-то показать.
Т.е. опять много вспомогательного кода и не все так просто…
AlarmManager
А еще есть моменты которые вообще не описаны в документации. Например, мы в своем приложении используем AlarmManager (http://developer.android.com/reference/android/app/AlarmManager.html) который помогает нам выдавать сообщения пользователю через некоторое время когда само наше приложение уже закрыто.
Этот AlarmManager — штука очень полезная, вот только проблема в том, что иногда он “теряет” созданные в нем нотификации! Мы потратили кучу времени чтобы понять как и почему это происходит, перерыли всю документацию и ничего не нашли. Совершенно случайно мы “набрели” на это обсуждение — http://stackoverflow.com/questions/9101818/how-to-create-a-persistent-alarmmanager.
Оказывается, что если приложение падает, или пользователь сам убивает приложение через task manager (что возможно и совершенно обычно), то ВСЕ нотификации для этого приложения в AlarmManager УДАЛЯЮТСЯ! Вот это — сюрприз! Особенно, мне понравился там один из комментариев: “Re Alarm canceled: Thanks for the clarification. I asked this on the Android team office hours g+ hangout and they even seemed confused about this behavior. Is it documented anywhere?”
Так что теперь при старте приложения мы вынуждены пересоздавать настроенные нотификации, так как даже нет API в AlarmManager чтобы проверить есть ли такие нотификации или нет.
Бывает и такое.
Вот вам еще несколько ошибок которые мы зарегистрировали у наших пользователей. В своем приложении мы используем OAuth идентификацию для различных социальных сетей и поэтому вынуждены запускать штатный браузер в Android (через который и должен работать OAuth). При этом, он периодически “падает”
Заключение
Несмотря на все вешеописанное, мне понравилась заниматься разработкой под Android. В основном API вполне продуманный и удобный.
Вот только везде стоит использовать try/catch . Даже там, где это совсем неочевидно.
Источник
NetworkOnMainThreadException #3184
Comments
rusmichal commented Feb 22, 2017
Hey. I know that there already is lot of similar issues but I cannot find solution.
I’ve started working with Espresso UI tests. I prepare custom MockTestRunner , MockApplication for initialization Dagger components and I’ve defined mock modules too. It looks like that:
MyApp is extended by
I added testInstrumentationRunner into gradle
I want run login tests in my LoginActivity
This is MockApiModule which extends ApiModule class
API login request looks like that:
It works if I change MockMyApplication into MyApp class of application in MockTestRunner
When I want to run my tests I got:
The text was updated successfully, but these errors were encountered:
swankjesse commented Feb 22, 2017
You’ll need to avoid starting your MockWebServer on the main thread.
rusmichal commented Feb 23, 2017
DavidEdwards commented Feb 23, 2017
If you are confused about threading, you should take a look at this:
There are some good explanations, and some solutions.
rusmichal commented Feb 23, 2017
I guess you don’t understand me. I know that about NetworkOnMainThreadException and why it is appearing in Android OS. But in my test in line .baseUrl(mockWebServer.url(«/»)) throw NetworkOnMainThreadException. I don’t understand why in this line?
Maybe I will explain more what I want get. I have finished my app, couple of activties and fragments. I have class BasicFragment where I inject ApiClient object to request with backend. For example let get LoginFragment . There is simple form fields email and password and login button. In my UI tests I type email and password and all looks like real user types. I added performClick for login button. So It start calls real backend. This I want change to call mock server and emit mock response from json file.
This is weird because I have Unit test where I test backend stuff and I use MockWebserver and there it works fine.
My first post is instrumental test but below is my unit test and it works:
DavidEdwards commented Feb 23, 2017 •
I am not very familiar with Dagger. However, to me it looks like this method is creating MockWebServer and Dagger is starting the mockWebServer ? If that is the case, you should check whether this thread is running on the Main Thread.
You can output to logcat to determine if this is where the issue is. In these two places add the following code.
- initComponent() method, at the start.
- provideRetrofit(OkHttpClient okHttpClient) method, at the start.
Log.w(«MAIN_THREAD_TEST», «On main thread=»+Looper.getMainLooper().equals(Looper.myLooper()));
rusmichal commented Feb 23, 2017
Class MyApp extends MultidexApplication where I initialize all components needed in app (Dagger). MockQrApplication extends MyApp and override initComponent for initialization mock modules for tests. So I don’t have to change my production code only I provide mock modules.
DavidEdwards commented Feb 23, 2017
Since both logs output true, it seems clear to me that you are creating your mockWebServer on the main thread. So my first step would be to push that onto a background thread.
I am not familiar with the lifecycles of the classes you are using. However, you could attempt to initialize / start the mockWebServer in its own thread.
rusmichal commented Feb 23, 2017
It doesn’t work because mockWebServer is null in MainThread.
In your solution and my (above) I got this:
swankjesse commented Mar 18, 2017
No action for us to take on this.
RafalManka commented Feb 11, 2019 •
If anyone else stumbles on this problem here is the solution. Modify your custom AndroidJUnitRunner like so:
Источник