Как исправить "android.os.NetworkOnMainThreadException"?
У меня возникла ошибка при запуске моего 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 устарел на уровне API 30.
https://developer.android.com/reference/android/os/AsyncTask
Это исключение возникает, когда приложение пытается выполнить сетевую операцию в своем основном потоке. Запустите свой код 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);
В вашем классе
и
ДОБАВИТЬ это разрешение в файл 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();
У принятого ответа есть ряд существенных недостатков. Не рекомендуется использовать AsyncTask для работы в сети, если вы действительно не знаете, что делаете. Некоторые из недостатков включают:
- AsyncTask, созданные как нестатические внутренние классы, имеют неявную ссылку на включающий объект Activity, его контекст и всю иерархию View, созданную этим действием. Эта ссылка предотвращает сборку мусора Activity до завершения фоновой работы AsyncTask. Если соединение пользователя медленное и / или загрузка велика, эти кратковременные утечки памяти могут стать проблемой - например, если ориентация меняется несколько раз (и вы не отменяете выполняемые задачи) или пользователь уходит от Activity.
- AsyncTask имеет разные характеристики выполнения в зависимости от платформы, на которой он выполняется: до API уровня 4 AsyncTasks выполняются последовательно в одном фоновом потоке; от уровня API 4 до уровня API 10 AsyncTasks выполняются в пуле до 128 потоков; начиная с уровня API 11 и далее AsyncTask выполняется последовательно в одном фоновом потоке (если вы не используете перегруженный
executeOnExecutor
метод и не предоставляете альтернативного исполнителя). Код, который отлично работает при последовательном запуске в ICS, может сломаться при одновременном выполнении в Gingerbread, скажем, если у вас есть непреднамеренные зависимости порядка выполнения.
Если вы хотите избежать краткосрочных утечек памяти, иметь четко определенные характеристики выполнения на всех платформах и иметь основу для создания действительно надежной сетевой обработки, вы можете подумать о следующем:
- Использование библиотеки, которая отлично справляется с этой задачей - в этом вопросе есть хорошее сравнение сетевых библиотек , или
- Используя
Service
илиIntentService
вместо этого, возможно, с помощью,PendingIntent
чтобы вернуть результат через метод ActivityonActivityResult
.
Подход IntentService
Нижние стороны:
- Больше кода и сложности, чем
AsyncTask
, хотя и не так много, как вы думаете - Будет ставить запросы в очередь и запускать их в одном фоновом потоке. Вы можете легко контролировать это, заменив
IntentService
его эквивалентнойService
реализацией, например, такой . - На самом деле я не могу думать ни о каких других прямо сейчас
Вверх:
- Избегает проблемы краткосрочной утечки памяти
- Если ваша активность перезапускается во время работы сети, она все равно может получить результат загрузки с помощью своего
onActivityResult
метода. - Лучшая платформа, чем AsyncTask, для создания и повторного использования надежного сетевого кода. Пример: если вам нужно выполнить важную загрузку, вы можете сделать это из
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, доступен здесь .
Вы не можете выполнять сетевой ввод-вывод в потоке пользовательского интерфейса в Honeycomb . Технически это возможно на более ранних версиях Android, но это действительно плохая идея , так как это приведет ваше приложение перестает отвечать на запросы, и может привести к ОС убивает ваше приложение для того плохо себя вели. Вам нужно будет запустить фоновый процесс или использовать AsyncTask для выполнения сетевой транзакции в фоновом потоке.
На сайте разработчиков 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"/>
- Не используйте strictMode (только в режиме отладки)
- Не менять версию SDK
- Не используйте отдельный поток
Используйте службу или AsyncTask
См. Также вопрос о переполнении стека:
android.os.NetworkOnMainThreadException отправка электронного письма с Android
Вы отключаете строгий режим, используя следующий код:
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 3.0 и выше. Начиная с Android 3.0 и более поздних версий, они ограничили использование сетевых операций (функций, имеющих доступ к Интернету) в основном потоке / потоке пользовательского интерфейса (что порождается вашими методами создания и возобновления в действии).
Это должно способствовать использованию отдельных потоков для сетевых операций. См. AsyncTask для получения дополнительных сведений о том, как правильно выполнять сетевые действия.
Можно использовать аннотации Android . Это позволит вам просто запустить любой метод в фоновом потоке:
// normal method
private void normal() {
doSomething(); // do something in background
}
@Background
protected void doSomething()
// run your networking code here
}
Обратите внимание: хотя он обеспечивает преимущества простоты и удобочитаемости, у него есть свои недостатки.
Ошибка возникает из-за выполнения длительных операций в основном потоке. Вы можете легко исправить проблему с помощью 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
}
});
Вы не должны выполнять какие-либо трудоемкие задачи в основном потоке (потоке пользовательского интерфейса), например, любые сетевые операции, операции ввода-вывода файлов или операции с базой данных SQLite. Итак, для такого рода операций вы должны создать рабочий поток, но проблема в том, что вы не можете напрямую выполнять какие-либо операции, связанные с пользовательским интерфейсом, из рабочего потока. Для этого вам нужно использовать Handler
и передать Message
.
Для того, чтобы упростить все эти вещи, Android предоставляет различные способы, как AsyncTask
, AsyncTaskLoader
, CursorLoader
или IntentService
. Таким образом, вы можете использовать любой из них в соответствии с вашими требованиями.
Верхний ответ спектом работает отлично.
Если вы пишете AsyncTask
встроенный и не расширяетесь как класс, и, кроме того, если есть необходимость получить ответ из AsyncTask
, можно использовать get()
метод, как показано ниже.
RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();
(Из его примера.)
Это выбрасывается только для приложений, ориентированных на Honeycomb SDK или выше. Приложениям, ориентированным на более ранние версии SDK, разрешено работать в сети в своих основных потоках цикла событий.
Ошибка - это предупреждение SDK!
Для меня это было так:
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="10" />
Устройство, на котором я тестировал свое приложение, было 4.1.2, то есть SDK версии 16!
Убедитесь, что целевая версия совпадает с вашей целевой библиотекой Android. Если вы не уверены, какая у вас целевая библиотека, щелкните правой кнопкой мыши свой проект -> Путь сборки -> Android , и он должен быть отмечен галочкой.
Кроме того, как уже упоминалось другими, укажите правильные разрешения для доступа в Интернет:
<uses-permission android:name="android.permission.INTERNET"/>
Используйте это в своей деятельности
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 intent = new Intent(getApplicationContext(), ComposeMail.class);
//intent.putParcelableArrayListExtra("valuesList", value);
startActivity(intent);
}
}).start();
}
});
Просто чтобы прояснить что-то явно:
Основной поток - это в основном поток пользовательского интерфейса.
Таким образом, утверждение о том, что вы не можете выполнять сетевые операции в основном потоке, означает, что вы не можете выполнять сетевые операции в потоке пользовательского интерфейса, что означает, что вы также не можете выполнять сетевые операции в *runOnUiThread(new Runnable() { ... }*
блоке внутри какого-либо другого потока.
(Я просто долго ломал голову, пытаясь понять, почему я получаю эту ошибку где-то еще, а не в основном потоке. Вот почему; эта ветка помогла; и, надеюсь, этот комментарий поможет кому-то другому.)
Это исключение возникает из-за любой тяжелой задачи, выполняемой в основном потоке, если выполнение этой задачи занимает слишком много времени .
Чтобы этого избежать, мы можем справиться с этим с помощью потоков или исполнителей.
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
// You can perform your task here.
}
});
На этот вопрос уже есть много отличных ответов, но с тех пор, как эти ответы были опубликованы, появилось много отличных библиотек. Это задумано как своего рода руководство для новичков.
Я охватывать несколько вариантов использования для выполнения сетевых операций и в растворе , или два для каждого из них.
ReST через HTTP
Обычно Json, может быть XML или что-то еще
Полный доступ к API
Допустим, вы пишете приложение, которое позволяет пользователям отслеживать цены на акции, процентные ставки и курсы обмена валют. Вы найдете Json API, который выглядит примерно так:
http://api.example.com/stocks //ResponseWrapper<String> object containing a list of Srings with ticker symbols
http://api.example.com/stocks/$symbol //Stock object http://api.example.com/stocks/$symbol/prices //PriceHistory<Stock> object
http://api.example.com/currencies //ResponseWrapper<String> object containing a list of currency abbreviation
http://api.example.com/currencies/$currency //Currency object http://api.example.com/currencies/$id1/values/$id2 //PriceHistory<Currency> object comparing the prices of the first currency (id1) to the second (id2)
Модернизация от Square
Это отличный выбор для API с несколькими конечными точками и позволяет объявлять конечные точки ReST вместо того, чтобы кодировать их по отдельности, как с другими библиотеками, такими как ion или Volley. (Веб-сайт:http://square.github.io/retrofit/)
Как вы используете его с API финансов?
build.gradle
Добавьте эти строки на уровень вашего модуля buid.gradle:
implementation 'com.squareup.retrofit2:retrofit:2.3.0' //retrofit library, current as of September 21, 2017
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' //gson serialization and deserialization support for retrofit, version must match retrofit version
FinancesApi.java
public interface FinancesApi {
@GET("stocks")
Call<ResponseWrapper<String>> listStocks();
@GET("stocks/{symbol}")
Call<Stock> getStock(@Path("symbol")String tickerSymbol);
@GET("stocks/{symbol}/prices")
Call<PriceHistory<Stock>> getPriceHistory(@Path("symbol")String tickerSymbol);
@GET("currencies")
Call<ResponseWrapper<String>> listCurrencies();
@GET("currencies/{symbol}")
Call<Currency> getCurrency(@Path("symbol")String currencySymbol);
@GET("currencies/{symbol}/values/{compare_symbol}")
Call<PriceHistory<Currency>> getComparativeHistory(@Path("symbol")String currency, @Path("compare_symbol")String currencyToPriceAgainst);
}
Финансы
public class FinancesApiBuilder {
public static FinancesApi build(String baseUrl){
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(FinancesApi.class);
}
}
FinancesFragment фрагмент
FinancesApi api = FinancesApiBuilder.build("http://api.example.com/"); //trailing '/' required for predictable behavior
api.getStock("INTC").enqueue(new Callback<Stock>(){
@Override
public void onResponse(Call<Stock> stockCall, Response<Stock> stockResponse){
Stock stock = stockCall.body();
//do something with the stock
}
@Override
public void onResponse(Call<Stock> stockCall, Throwable t){
//something bad happened
}
}
Если ваш API требует отправки ключа API или другого заголовка, такого как токен пользователя и т. Д., Retrofit упрощает это (подробности см. В этом замечательном ответе: https://stackoverflow.com/a/42899766/1024412).
Одноразовый доступ к ReST API
Предположим, вы создаете приложение «погода для настроения», которое проверяет местоположение пользователей по GPS, проверяет текущую температуру в этой области и сообщает им настроение. Приложению этого типа не нужно объявлять конечные точки API; ему просто нужно иметь доступ к одной конечной точке API.
Ион
Это отличная библиотека для такого типа доступа.
Пожалуйста, прочтите отличный ответ msysmilu (https://stackoverflow.com/a/28559884/1024412)
Загружать изображения через HTTP
Залп
Volley также можно использовать для API ReST, но из-за более сложной настройки я предпочитаю использовать Retrofit from Square, как указано выше (http://square.github.io/retrofit/)
Допустим, вы создаете приложение для социальной сети и хотите загрузить фотографии профилей друзей.
build.gradle
Добавьте эту строку на свой уровень модуля buid.gradle:
implementation 'com.android.volley:volley:1.0.0'
ImageFetch.java
Volley требует больше настроек, чем модернизация. Вам нужно будет создать такой класс, чтобы настроить RequestQueue, ImageLoader и ImageCache, но это не так уж плохо:
public class ImageFetch {
private static ImageLoader imageLoader = null;
private static RequestQueue imageQueue = null;
public static ImageLoader getImageLoader(Context ctx){
if(imageLoader == null){
if(imageQueue == null){
imageQueue = Volley.newRequestQueue(ctx.getApplicationContext());
}
imageLoader = new ImageLoader(imageQueue, new ImageLoader.ImageCache() {
Map<String, Bitmap> cache = new HashMap<String, Bitmap>();
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
});
}
return imageLoader;
}
}
user_view_dialog.xml
Добавьте следующее в XML-файл макета, чтобы добавить изображение:
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/profile_picture"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
app:srcCompat="@android:drawable/spinner_background"/>
UserViewDialog.java
Добавьте следующий код в метод onCreate (Fragment, Activity) или конструктор (Dialog):
NetworkImageView profilePicture = view.findViewById(R.id.profile_picture);
profilePicture.setImageUrl("http://example.com/users/images/profile.jpg", ImageFetch.getImageLoader(getContext());
Пикассо
Еще одна отличная библиотека от Square. Пожалуйста, посетите сайт, чтобы увидеть несколько отличных примеров:http://square.github.io/picasso/
Простыми словами,
НЕ РАБОТАЙТЕ ПО СЕТИ В РЕЖИМЕ UI
Например, если вы выполняете HTTP-запрос, это сетевое действие.
Решение:
- Вам нужно создать новую тему
- Или используйте класс AsyncTask
Путь:
Поместите все свои работы внутрь
run()
метод новой резьбы- Или
doInBackground()
метод класса AsyncTask.
Но:
Когда вы получаете что-то из сетевого ответа и хотите показать это в своем представлении (например, отобразить ответное сообщение в TextView), вам необходимо вернуться в поток пользовательского интерфейса .
Если вы этого не сделаете, вы получите ViewRootImpl$CalledFromWrongThreadException
.
Как?
- При использовании AsyncTask обновите представление из
onPostExecute()
метода - Или вызовите runOnUiThread()метод и обновите представление внутри
run()
метода.
Вы можете переместить часть своего кода в другой поток, чтобы разгрузить main thread
и избежать ANR , NetworkOnMainThreadException , IllegalStateException (например, невозможно получить доступ к базе данных в основном потоке, поскольку это может потенциально заблокировать пользовательский интерфейс на длительный период времени).
Есть несколько подходов, которые следует выбирать в зависимости от ситуации.
Java Thread или Android HandlerThread
Потоки Java используются только один раз и умирают после выполнения своего метода выполнения.
HandlerThread - удобный класс для запуска нового потока, в котором есть цикл.
AsyncTask
AsyncTask разработан как вспомогательный класс для Thread и Handler и не представляет собой общую структуру потоковой передачи. В идеале AsyncTasks следует использовать для коротких операций (максимум несколько секунд). Если вам нужно поддерживать работу потоков в течение длительного периода времени, настоятельно рекомендуется использовать различные API-интерфейсы, предоставляемые пакетом java.util.concurrent, такие как Исполнитель , ThreadPoolExecutor и FutureTask .
Поскольку основной поток монополизирует компоненты пользовательского интерфейса, доступ к некоторым View невозможен, поэтому Handler приходит на помощь.
Реализация пула потоков ThreadPoolExecutor , ScheduledThreadPoolExecutor ...
Класс ThreadPoolExecutor, реализующий ExecutorService, который дает точный контроль над пулом потоков (например, размер основного пула, максимальный размер пула, время сохранения активности и т. Д.)
ScheduledThreadPoolExecutor - класс, расширяющий ThreadPoolExecutor. Он может планировать задачи после заданной задержки или периодически.
FutureTask
FutureTask выполняет асинхронную обработку, однако, если результат еще не готов или обработка не завершена, вызов get () заблокирует поток.
AsyncTaskLoaders
AsyncTaskLoaders, поскольку они решают множество проблем, присущих AsyncTask.
IntentService
По умолчанию это выбор для длительной обработки на Android, хорошим примером может быть загрузка или скачивание больших файлов. Выгрузка и загрузка могут продолжаться, даже если пользователь выходит из приложения, и вы, конечно, не хотите, чтобы пользователь не мог использовать приложение, пока эти задачи выполняются.
JobScheduler
Фактически, вы должны создать службу и создать задание с помощью JobInfo.Builder, в котором указаны ваши критерии для запуска службы.
RxJava
Библиотека для создания асинхронных программ и программ, основанных на событиях, с использованием наблюдаемых последовательностей.
Корутины (Котлин)
Основная суть в том, что асинхронный код выглядит так же, как синхронный.
Подробнее здесь , здесь , здесь , здесь
Новые решения Thread
и решения AsyncTask уже были объяснены.
AsyncTask
в идеале следует использовать для коротких операций. Нормальный Thread
не предпочтительнее для Android.
Взгляните на альтернативное решение с использованием HandlerThread и Handler
HandlerThread
Удобный класс для запуска нового потока с петлителем. Затем петлитель можно использовать для создания классов обработчиков. Обратите внимание, что
start()
еще нужно называть.
Обработчик:
Обработчик позволяет отправлять и обрабатывать объекты Message и Runnable, связанные с MessageQueue потока. Каждый экземпляр Handler связан с одним потоком и очередью сообщений этого потока. Когда вы создаете новый обработчик, он привязывается к потоку / очереди сообщений потока, который его создает - с этого момента он будет доставлять сообщения и исполняемые файлы в эту очередь сообщений и выполнять их по мере выхода из сообщения. очередь.
Решение:
Создайте
HandlerThread
Вызов
start()
наHandlerThread
Создавайте
Handler
, получаяLooper
отHanlerThread
Вставьте код, связанный с сетевой операцией, в
Runnable
объектОтправить
Runnable
задачу вHandler
Пример фрагмента кода, адрес NetworkOnMainThreadException
HandlerThread handlerThread = new HandlerThread("URLConnection");
handlerThread.start();
handler mainHandler = new Handler(handlerThread.getLooper());
Runnable myRunnable = new Runnable() {
@Override
public void run() {
try {
Log.d("Ravi", "Before IO call");
URL page = new URL("http://www.google.com");
StringBuffer text = new StringBuffer();
HttpURLConnection conn = (HttpURLConnection) page.openConnection();
conn.connect();
InputStreamReader in = new InputStreamReader((InputStream) conn.getContent());
BufferedReader buff = new BufferedReader(in);
String line;
while ( (line = buff.readLine()) != null) {
text.append(line + "\n");
}
Log.d("Ravi", "After IO call");
Log.d("Ravi",text.toString());
}catch( Exception err){
err.printStackTrace();
}
}
};
mainHandler.post(myRunnable);
Плюсы использования этого подхода:
- Создание новых
Thread/AsyncTask
для каждой сетевой операции стоит дорого. ОнThread/AsyncTask
будет уничтожен и воссоздан для следующих сетевых операций. НоHandler
иHandlerThread
подход, вы можете отправить множество сетевых операций (как Runnable задач) для одногоHandlerThread
использованияHandler
.
Хотя выше есть огромный пул решений, никто не упомянул 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
}
});
Есть еще один очень удобный способ решить эту проблему - использовать возможности параллелизма 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 .
RxAndroid
- еще одна лучшая альтернатива этой проблеме, которая избавляет нас от хлопот с созданием потоков и последующей публикацией результатов в потоке пользовательского интерфейса Android. Нам просто нужно указать потоки, в которых должны выполняться задачи, и все будет обрабатываться внутри.
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);
}
});
При
(Schedulers.io())
указании RxAndroid будет работатьgetFavoriteMusicShows()
в другом потоке.Используя,
AndroidSchedulers.mainThread()
мы хотим наблюдать этот Observable в потоке пользовательского интерфейса, т.е. мы хотим, чтобы нашonNext()
обратный вызов вызывал в потоке пользовательского интерфейса.
Основной поток - это поток пользовательского интерфейса, и вы не можете выполнять операцию в основном потоке, которая может заблокировать взаимодействие с пользователем. Вы можете решить эту проблему двумя способами:
Принудительно выполнить задачу в основном потоке, как это
StrictMode.ThreadPolicy threadPolicy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(threadPolicy);
Или создайте простой обработчик и обновите главный поток, если хотите.
Runnable runnable;
Handler newHandler;
newHandler = new Handler();
runnable = new Runnable() {
@Override
public void run() {
try {
//update UI
} catch (Exception e) {
e.printStackTrace();
}
}
};
newHandler.post(runnable);
И чтобы остановить использование потока:
newHandler.removeCallbacks(runnable);
Для получения дополнительной информации ознакомьтесь с этим: Безболезненная заправка резьбы
Это работает. Просто сделал ответ доктора Луиджи немного проще.
new Thread() {
@Override
public void run() {
try {
//Your code goes here
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
В Android сетевые операции не могут выполняться в основном потоке. Вы можете использовать Thread, AsyncTask (краткосрочные задачи), Service (длительные задачи) для выполнения сетевых операций.