Как я могу исправить 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
Как я могу исправить эту проблему?
65 ответов
Это исключение выдается, когда приложение пытается выполнить сетевую операцию в своем основном потоке. Запустите свой код в 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. Если подключение пользователя медленное и / или загрузка велика, эти кратковременные утечки памяти могут стать проблемой - например, если ориентация меняется несколько раз (и вы не отменяете выполнение задач), или пользователь перемещается от деятельности.
- AsyncTask имеет различные характеристики выполнения в зависимости от платформы, на которой он выполняется: до уровня API 4 AsyncTask выполняется последовательно в одном фоновом потоке; от уровня API 4 до уровня API 10 AsyncTasks выполняется в пуле до 128 потоков; начиная с уровня API 11 и выше AsyncTask выполняется последовательно в одном фоновом потоке (если вы не используете перегруженный
executeOnExecutor
метод и предоставить альтернативного исполнителя). Код, который отлично работает при последовательном запуске на ICS, может сломаться при одновременном выполнении на Gingerbread, например, если у вас есть непреднамеренные зависимости порядка выполнения.
Если вы хотите избежать кратковременных утечек памяти, иметь четко определенные характеристики выполнения на всех платформах и иметь базу для построения действительно надежной обработки сети, вы можете рассмотреть следующие вопросы:
- Используя библиотеку, которая хорошо с этим справится, есть хорошее сравнение сетевых библиотек в этом вопросе, или
- Используя
Service
или жеIntentService
вместо этого, возможно, сPendingIntent
вернуть результат через активностьonActivityResult
метод.
IntentService подход
Down-сторона:
- Больше кода и сложности, чем
AsyncTask
хотя и не так много, как вы думаете - Поставит запросы в очередь и запустит их в одном фоновом потоке. Вы можете легко контролировать это, заменяя
IntentService
с эквивалентнымService
реализация, возможно, как этот. - Хм, я не могу сейчас думать о других
Up-сторон:
- Предотвращает кратковременную проблему утечки памяти
- Если ваша активность возобновляется, когда сетевые операции находятся в полете, она все равно может получить результат загрузки через свой
onActivityResult
метод - Лучшая платформа, чем AsyncTask, для создания и повторного использования надежного сетевого кода. Пример: если вам нужно сделать важную загрузку, вы можете сделать это из
AsyncTask
вActivity
, но если пользователь переключает контекст из приложения, чтобы принять телефонный звонок, система может уничтожить приложение до завершения загрузки. Менее вероятно, чтобы убить приложение с активнымService
, - Если вы используете свою собственную параллельную версию
IntentService
(как тот, который я связал выше), вы можете контролировать уровень параллелизма черезExecutor
,
Краткое описание реализации
Вы можете реализовать IntentService
выполнять загрузку в одном фоновом потоке довольно легко.
Шаг 1: Создать IntentService
выполнить загрузку. Вы можете сказать, что скачать через Intent
дополнительные, и передать его 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, доступен здесь.
Вы не можете выполнять сетевой ввод / вывод в потоке пользовательского интерфейса на сотовой основе. Технически это возможно в более ранних версиях Android, но это действительно плохая идея, так как это заставит ваше приложение перестать отвечать на запросы и может привести к тому, что ОС убьет ваше приложение за плохое поведение. Вам нужно будет запустить фоновый процесс или использовать AsyncTask для выполнения вашей сетевой транзакции в фоновом потоке.
На сайте разработчиков Android есть статья о Painless Threading, которая является хорошим введением в это, и она даст вам гораздо более глубокую глубину ответа, чем это можно реально представить здесь.
Делать сетевые действия в другом потоке
Например:
new Thread(new Runnable(){
@Override
public void run() {
// Do network action in this function
}
}).start();
И добавьте это в AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
- Не используйте строгий режим (только в режиме отладки)
- Не меняйте версию 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, разрешено создавать сети в своих основных потоках цикла событий.
Для меня это было так:
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="10" />
Устройство, на котором я тестировал мое приложение, было 4.1.2, то есть SDK версии 16!
Убедитесь, что целевая версия совпадает с целевой библиотекой Android. Если вы не уверены, какая у вас целевая библиотека, щелкните правой кнопкой мыши на Project -> Build Path -> Android, и она должна быть отмечена галочкой.
Также, как уже упоминали другие, включите правильные разрешения на доступ к Интернету:
<uses-permission android:name="android.permission.INTERNET"/>
**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();
}
});
Это исключение происходит из-за любой тяжелой задачи, выполняемой в главном потоке, если выполнение этой задачи занимает слишком много времени.
Чтобы избежать этого, мы можем справиться с этим с помощью потоков или исполнителей
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
// You can perform your task here.
}
});
Просто, чтобы объяснить что-то явно:
Основной поток - это в основном поток пользовательского интерфейса.
То, что вы не можете выполнять сетевые операции в основном потоке, означает, что вы не можете выполнять сетевые операции в потоке пользовательского интерфейса, что означает, что вы не можете выполнять сетевые операции в *runOnUiThread(new Runnable() { ... }*
блок внутри какого-то другого потока, либо.
(У меня просто был долгий головокружительный момент, когда я пытался выяснить, почему я получаю эту ошибку где-то, кроме моего основного потока. Именно поэтому этот поток помог; и, надеюсь, этот комментарий поможет кому-то еще.)
На этот вопрос уже есть много хороших ответов, но с тех пор, как эти ответы были опубликованы, появилось много замечательных библиотек. Это задумано как своего рода новичок-гид.
Я расскажу о нескольких случаях использования для выполнения сетевых операций и одном или двух решениях для каждого.
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)
Дооснащение от площади
Это отличный выбор для 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);
}
FinancesApiBuilder
public class FinancesApiBuilder {
public static FinancesApi build(String baseUrl){
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(FinancesApi.class);
}
}
ФинансыФрагмент фрагмента
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 упрощает эту задачу (подробности см. В этом удивительном ответе: /questions/44796011/dobavit-parametr-zagolovka-v-modifikatsii/44796050#44796050).
Одноразовый доступ к API ReST
Допустим, вы создаете приложение "погода настроения", которое просматривает местоположение пользователей GPS, проверяет текущую температуру в этой области и сообщает им настроение. Приложение такого типа не должно объявлять конечные точки API; ему просто нужно иметь доступ к одной конечной точке API.
ион
Это отличная библиотека для этого типа доступа.
Пожалуйста, прочитайте отличный ответ msysmilu ( /questions/43554177/kak-ya-mogu-ispravit-androidosnetworkonmainthreadexception/43554195#43554195)
Загрузка изображений через HTTP
залп
Залп может также использоваться для API ReST, но из-за более сложной настройки я предпочитаю использовать Retrofit from Square, как указано выше ( http://square.github.io/retrofit/)
Допустим, вы создаете приложение для социальных сетей и хотите загружать фотографии из профилей друзей.
build.gradle
Добавьте эту строку на уровень вашего модуля buid.gradle:
implementation 'com.android.volley:volley:1.0.0'
ImageFetch.java
Залп требует больше настроек, чем модификация. Вам нужно будет создать такой класс, чтобы настроить 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());
Пикассо
Еще одна отличная библиотека с площади. Пожалуйста, посмотрите на сайт несколько замечательных примеров: 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 используются только один раз и умирают после выполнения метода run.
HandlerThread - удобный класс для запуска нового потока, в котором есть петлитель.
AsyncTask разработан как вспомогательный класс для Thread и Handler и не представляет собой универсальную среду потоков. AsyncTasks в идеале следует использовать для коротких операций (максимум несколько секунд). Если вам нужно, чтобы потоки работали в течение длительного периода времени, настоятельно рекомендуется использовать различные API, предоставляемые пакетом java.util.concurrent, такие как Executor, ThreadPoolExecutor и FutureTask.
Реализация пула потоков ThreadPoolExecutor, ScheduledThreadPoolExecutor...
Класс ThreadPoolExecutor, который реализует ExecutorService, который дает точный контроль над пулом потоков (например, размер пула ядра, максимальный размер пула, время поддержки и т. Д.)
ScheduledThreadPoolExecutor - класс, который расширяет ThreadPoolExecutor. Он может планировать задачи после определенной задержки или периодически.
FutureTask выполняет асинхронную обработку, однако, если результат еще не готов или обработка не завершена, вызов get() блокирует поток
AsyncTaskLoaders, поскольку они решают множество проблем, присущих AsyncTask
Это выбор по умолчанию для длительной обработки на Android, хорошим примером будет загрузка или загрузка больших файлов. Загрузка и выгрузка могут продолжаться, даже если пользователь выходит из приложения, и вы, конечно же, не хотите блокировать возможность пользователю использовать приложение во время выполнения этих задач.
По сути, вам нужно создать Службу и создать задание, используя JobInfo.Builder, в котором указаны ваши критерии, когда следует запускать Службу.
Библиотека для составления асинхронных и событийных программ с использованием наблюдаемых последовательностей.
Сопрограммы (Котлин)
Основная суть в том, что асинхронный код выглядит так же, как синхронный
Узнайте больше здесь:
8 способов сделать асинхронную обработку в Android и считать
Эволюция доступа к сети Android
Использование пула потоков в Android
Котлин
Если вы используете Kotlin, вы можете использовать coroutine
fun doSomeNetworkStuff() {
GlobalScope.launch(Dispatchers.IO) {
// ...
}
}
Это работает. Просто сделал ответ доктора Луиджи немного проще.
new Thread() {
@Override
public void run() {
try {
//Your code goes here
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
Новый Thread
Решения AsyncTask уже были объяснены.
AsyncTask
в идеале следует использовать для коротких операций. Нормальный Thread
не является предпочтительным для Android.
Взгляните на альтернативное решение, используя HandlerThread и Handler
HandlerThread
Удобный класс для запуска нового потока, который имеет петлитель. Цикл затем может быть использован для создания классов обработчиков. Обратите внимание, что
start()
должен все еще называться.
Обработчик:
Обработчик позволяет отправлять и обрабатывать объекты Message и Runnable, связанные с MessageQueue потока. Каждый экземпляр обработчика связан с одним потоком и очередью сообщений этого потока. Когда вы создаете новый обработчик, он привязывается к потоку / очереди сообщений потока, который его создает - с этого момента он будет доставлять сообщения и исполняемые файлы в эту очередь сообщений и выполнять их по мере их выхода из сообщения. очередь.
Решение:
Создайте
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
подход, вы можете отправить много сетевых операций (как запускаемые задачи) в один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
}
});
Основной поток является потоком пользовательского интерфейса, и вы не можете выполнить в основном потоке операцию, которая может заблокировать взаимодействие с пользователем. Вы можете решить это двумя способами:
Заставить сделать задачу в главном потоке вот так
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);
Для получения дополнительной информации проверьте это: безболезненный поток
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()
обратный вызов для вызова в потоке пользовательского интерфейса
Android не позволяет выполнять длительные операции в основном потоке. Поэтому просто используйте другой поток и опубликуйте результат в основном потоке, когда это необходимо.
new Thread(new Runnable() {
@Override
public void run() {
/*
// run operation here
*/
//after getting result
runOnUiThread(new Runnable() {
@Override
public void run() {
//post result to main thread
}
});
}
}).start();