如何修复android.os.NetworkOnMainThreadException?

我在运行RssReader的Android项目时遇到错误。

代码:

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

如何解决此问题?

1432025 次浏览

您无法在蜂巢上的UI线程上执行网络输入输出。从技术上讲,它可以在早期版本的Android上执行,但这是一个非常糟糕的主意,因为它会导致您的应用程序停止响应,并可能导致操作系统因行为不端而杀死您的应用程序。你需要运行后台进程或使用AsyncTeam在后台线程上执行网络事务。

Android开发者网站上有一篇关于无痛螺纹的文章,这是一个很好的介绍,它将为您提供比这里实际提供的答案更深入的答案。

注意:在API级别30中不建议使用AsyncTeam。
AsyncTask_安卓开发者

当应用程序尝试在其主线程上执行网络操作时,会引发此异常。在#0中运行您的代码:

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);

在你的课上,

添加 Androidmanifest.xml文件中的此权限:

<uses-permission android:name="android.permission.INTERNET"/>

后果:

您的应用程序将(在互联网连接不稳定的区域)变得无响应并锁定,用户感知到缓慢并必须执行强制杀死,并且您冒着活动管理器杀死您的应用程序并告诉用户应用程序已停止的风险。

Android有一些关于良好编程实践的好技巧来设计响应性:NetworkOnMainThreadException|Android Developers

您可以使用以下代码禁用严格模式:

if (android.os.Build.VERSION.SDK_INT > 9) {StrictMode.ThreadPolicy policy =new StrictMode.ThreadPolicy.Builder().permitAll().build();StrictMode.setThreadPolicy(policy);}

这个不推荐:使用AsyncTask接口。

两种方法的完整代码

这个问题有两种解决方案。

  1. 不要在主UI线程中使用网络调用。为此使用异步任务。

  2. setContentView(R.layout.activity_main);之后将以下代码写入MainActivity文件:

    if(android.os.Build.VERSION.SDK_INT>9){StrictModel. ThreadPolicy=新的StrictModel. ThreadPolicy. Builder(). permitAll(). build();StrictMode.setThreadPolicy(Policy);}

并将下面的导入语句导入到您的Java文件中。

import android.os.StrictMode;

我使用新的Thread解决了这个问题。

Thread thread = new Thread(new Runnable() {
@Overridepublic void run() {try  {//Your code goes here} catch (Exception e) {e.printStackTrace();}}});
thread.start();

顶部spektom的答案工作完美。

如果您正在编写AsyncTask内联而不是作为类扩展,并且最重要的是,如果需要从AsyncTask中获取响应,可以使用get()方法,如下所示。

RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();

(从他的例子。

  1. 不要使用严格模式(仅在调试模式下)
  2. 不要更改SDK版本
  3. 不要使用单独的线程

使用服务或异步任务

另请参阅堆栈溢出问题:

这种情况发生在Android 3.0及更高版本中。从Android 3.0及更高版本开始,他们限制使用网络操作(访问Internet的函数)在主线程/UI线程中运行(从活动中的创建和恢复方法中产生)。

这是为了鼓励使用单独的线程进行网络操作。有关如何正确执行网络活动的更多详细信息,请参阅异步任务

对我来说是这样的:

<uses-sdkandroid:minSdkVersion="8"android:targetSdkVersion="10" />

我测试我的应用程序的设备是4.1.2,这是SDK版本16!

确保目标版本与您的Android目标库相同。如果您不确定目标库是什么,请右键单击您的项目->构建路径->android,它应该是勾选的那个。

此外,正如其他人提到的,包括访问Internet的正确权限:

<uses-permission android:name="android.permission.INTERNET"/>

在另一个线程上执行网络操作。

例如:

new Thread(new Runnable(){@Overridepublic void run() {// Do network action in this function}}).start();

并将其添加到文件AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

你不应该在主线程(UI线程)上执行任何耗时的任务,例如任何网络操作、文件I/O或SQLite数据库操作。因此对于这种操作,你应该创建一个工作线程,但问题是你不能直接从工作线程执行任何与UI相关的操作。为此,你必须使用Handler并传递Message

为了简化所有这些事情,Android提供了各种方式,例如AsyncTaskAsyncTaskLoaderCursorLoaderIntentService。因此,您可以根据需要使用其中的任何一种。

基于网络的操作不能在主线程上运行。您需要在子线程上运行所有基于网络的任务或实现AsyncStory。

这是在子线程中运行任务的方式:

new Thread(new Runnable(){@Overridepublic void run() {try {// Your implementation goes here}catch (Exception ex) {ex.printStackTrace();}}}).start();

公认的答案有一些明显的缺点。除非你真的知道你在做什么,否则不建议使用AsyncTranst进行网络连接。一些缺点包括:

  • 作为非静态内部类创建的AsyncCT有一个隐式引用,指向封闭的活动对象、它的上下文以及由该活动创建的整个View层次结构。此引用可防止在AsyncCT的后台工作完成之前对活动进行垃圾收集。如果用户的连接很慢,并且/或者下载很大,这些短期内存泄漏可能会成为一个问题-例如,如果方向多次更改(并且您没有取消正在执行的任务),或者用户导航离开活动。
  • 根据执行平台的不同,AsyncTeam具有不同的执行特性:在API级别4之前,AsyncT的执行在单个后台线程上串行;从API级别4到API级别10,AsyncT的执行在最多128个线程的池中;从API级别11开始,AsyncT的执行在单个后台线程上串行(除非你使用重载的executeOnExecutor方法并提供替代执行器)。在ICS上串行运行时运行良好的代码在Ginger面包上并发执行时可能会中断,比如如果你无意中有执行顺序依赖项。

如果您想避免短期内存泄漏,在所有平台上具有明确定义的执行特征,并有基础构建真正强大的网络处理,您可能需要考虑:

  1. 使用一个为你做了很好工作的库-在这个问题中有一个很好的网络库比较,或者
  2. 使用ServiceIntentService代替,也许使用PendingIntent通过活动的onActivityResult方法返回结果。

IntentService方法

缺点:

  • AsyncTask更多的代码和复杂性,尽管没有你想象的那么多
  • 将队列请求并在单一后台线程上运行它们。您可以通过将IntentService替换为等效的Service实现(可能类似于这一个)来轻松控制它。
  • 呃我现在想不出其他的了

缺点:

  • 避免短期内存泄漏问题
  • 如果您的活动在网络操作进行中时重新启动,它仍然可以通过其onActivityResult方法接收下载结果
  • 一个比AsyncTeam更好的平台,用于构建和重用健壮的网络代码。示例:如果你需要进行重要的上传,你可以从Activity中的AsyncTask开始进行,但是如果用户上下文切换出应用程序去打电话,系统可能会在上传完成之前杀死该应用程序。杀死一个活动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 reuse, in the case where more than one intent is queuedparser = new IllustrativeRSSParser();}
@Overrideprotected 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 individuallyreply.send(ERROR_CODE);}} catch (PendingIntent.CanceledException exc) {Log.i(TAG, "reply cancelled", exc);}}}

第2步:在清单中注册服务:

<serviceandroid:name=".DownloadIntentService"android:exported="false"/>

步骤3:从活动调用服务,传递一个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);

第四步:处理onActivityResult中的结果:

@Overrideprotected 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);}

包含完整工作的Android Studio/Gradle项目的GitHub项目可用这里

这仅针对针对蜂巢 SDK或更高版本的应用程序引发。允许针对早期SDK版本的应用程序在其主事件循环线程上进行网络连接。

错误是SDK警告!

在你的活动中使用它

    btnsub.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Thread(new Runnable() {
@Overridepublic void run() {// TODO Auto-generated method stub
//Initialize soap request + add parametersSoapObject request = new SoapObject(NAMESPACE, METHOD_NAME1);
//Use this to add parametersrequest.addProperty("pincode", txtpincode.getText().toString());request.addProperty("bg", bloodgroup.getSelectedItem().toString());
//Declare the version of the SOAP requestSoapSerializationEnvelope 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 webserviceandroidHttpTransport.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();}});

使用Android注释是一种选择。它将允许您简单地在后台线程中运行任何方法:

// normal methodprivate void normal() {doSomething(); // do something in background}
@Backgroundprotected void doSomething()// run your networking code here}

请注意,尽管它提供了简单性和易读性的优点,但它也有其缺点。

只是为了明确地说明一些事情:

主线程基本上是UI线程。

所以说你不能在主线程中进行网络操作意味着你不能在UI线程中进行网络操作,这意味着在其他线程中也不能进行您不能在#0块中进行网络操作

(我只是有一个长时间的头疼时刻试图弄清楚为什么我在主线程之外的某个地方得到这个错误。这就是原因;这个线程有帮助;希望这个评论能帮助其他人。)

错误是由于在主线程中执行长时间运行的操作。您可以使用异步任务线程轻松纠正问题。您可以检查此库异步HTTPClient以获得更好的处理。

AsyncHttpClient client = new AsyncHttpClient();client.get("http://www.google.com", new AsyncHttpResponseHandler() {
@Overridepublic void onStart() {// Called before a request is started}
@Overridepublic void onSuccess(int statusCode, Header[] headers, byte[] response) {// Called when response HTTP status is "200 OK"}
@Overridepublic void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {// Called when response HTTP status is "4XX" (for example, 401, 403, 404)}
@Overridepublic void onRetry(int retryNo) {// Called when request is retried}});

如果执行任务需要太多的时间,则由于在主线程上执行的任何繁重任务而发生此异常。

为了避免这种情况,我们可以使用线程执行器来处理它

Executors.newSingleThreadExecutor().submit(new Runnable() {@Overridepublic void run() {// You can perform your task here.}});

把你的代码放在里面:

new Thread(new Runnable(){@Overridepublic 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不允许单独的进程进入主活动线程,HTTP连接在这里是一个独立的线程。这就是你得到“android.os.NetworkOnMainThreadException”的原因。

可能需要在向用户显示webview之前检查实际的Internet连接,因为如果没有Internet,Web视图将向用户显示页面未找到错误,通常您不会显示什么。

为了检查Internet可用性,可以使用ping命令,但在Wi-Fi服务器上可以禁用Wi-Fi ping,因此在这种情况下,您可以使用HTTP连接来检查请求的状态。

如果您在向用户显示webview之前检查自己的webview URL链接,这可能是正确的方法。在这种情况下,您可以使用Android的严格模式,但不要允许所有策略,因为您不需要它。

您应该只为严格模式提供网络允许策略。只需将以下行添加到您的代码中,您就不会收到此错误。

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();StrictMode.setThreadPolicy(policy);

我用简单的方法解决了这个问题…

我在oncreateStrictMode.enableDefaults();之后添加并解决了这个问题。

使用ServiceAsyncTask来解决这个问题

备注:

Do not change SDK versionDo not use a separate thread

更多,检查这个

使用下面的代码来执行繁重的任务。

// Your package here

import java.util.List;import org.apache.http.NameValuePair;
import android.app.Activity;import android.app.ProgressDialog;import android.content.Context;import android.os.AsyncTask;import android.view.View.OnSystemUiVisibilityChangeListener;
public class AsyncRequest extends AsyncTask<String, Integer, String> {
Context context;ProgressDialog pDialog;
// Three Constructorspublic AsyncRequest(Activity a, String m, List<NameValuePair> p) {context = a;method = m;parameters = p;}
public AsyncRequest(Activity a) {this.caller = (OnAsyncRequestComplete) a;context = a;}
public String doInBackground(String... urls) {
//Perform your task herereturn result;}
public void onPreExecute() {pDialog = new ProgressDialog(context);pDialog.setMessage("Please wait..");pDialog.setCancelable(false);pDialog.show();}
public void onProgressUpdate(Integer... progress) {// You can implement some progressBar and update it in this record.//   setProgressPercent(progress[0]);}
public void onPostExecute(String response) {if (pDialog != null && pDialog.isShowing()) {pDialog.dismiss();}// Get the result here}
protected void onCancelled(String response) {
if (pDialog != null && pDialog.isShowing()) {pDialog.dismiss();}}}

这很有效。我只是让路易医生的回答简单一点。

new Thread() {@Overridepublic void run() {try {//Your code goes here} catch (Exception e) {e.printStackTrace();}}}.start();

虽然上面有一个巨大的解决方案池,但没有人提到com.koushikdutta.ionhttps://github.com/koush/ion

它也是异步非常简单使用:

Ion.with(context).load("http://example.com/thing.json").asJsonObject().setCallback(new FutureCallback<JsonObject>() {@Overridepublic void onCompleted(Exception e, JsonObject result) {// do stuff with the result or error}});

您必须简单地在清单标记之后的文件manifest.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);}

在Android上,网络操作不能在主线程上运行。可以使用Thread、AsyncTeam(短运行任务)、Service(长运行任务)进行网络操作。

简单地说,

在UI线程中不做网络工作

例如,如果您执行HTTP请求,那就是网络操作。

解决方案:

  1. 你必须创建一个新的线程
  2. 使用异步任务类

方式:

把你所有的作品放在里面

  1. 新线程的run()方法
  2. doInBackground() AsyncTeam类的方法。

但是:

当您从网络响应中获取某些内容并希望将其显示在您的视图中(例如在TextView中显示响应消息)时,您需要返回到UI线程。

如果你不这样做,你会得到ViewRootImpl$CalledFromWrongThreadException

2.操作方法

  1. 在使用AsyncTeam时,从onPostExecute()方法更新视图
  2. 调用#0方法并更新run()方法内的视图。

从主线程(UI)访问网络资源会导致此异常。使用单独的线程或AsyncTeam访问网络资源可避免此问题。

你实际上可以开始一个新的线程。我以前遇到过这个问题,并通过这种方式解决了它。

你不允许在Android上的UI线程上实现网络操作。你必须使用AsyncTeam类来执行与网络相关的操作,如发送API请求、从URL下载图像等,并使用AsyncTeam的回调方法,你可以在onPostExecute menthod中获得结果,你将在UI线程中,你可以用来自Web服务的数据或类似的东西填充UI。

示例:假设您想从URL下载图像:https://www.samplewebsite.com/sampleimage.jpg

使用异步任务的解决方案:分别为

    public class MyDownloader extends AsyncTask<String,Void,Bitmap>{@Overrideprotected void onPreExecute() {// Show progress dialogsuper.onPreExecute();}
@Overrideprotected void onPostExecute(Bitmap bitmap) {//Populate Uisuper.onPostExecute(bitmap);}
@Overrideprotected Bitmap doInBackground(String... params) {// Open URL connection read bitmaps and return form herereturn result;}
@Overrideprotected void onProgressUpdate(Void... values) {// Show progress updatesuper.onProgressUpdate(values);}

}}

注意:不要忘记在Android清单文件中添加Internet权限。它会像魅力一样工作。:)

当应用程序尝试在其主线程上执行网络操作时,会引发此异常。如果你的任务超过5秒,它需要一个力接近。

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}}

发生NetworkOnMainThread异常是因为您在默认线程(即UI线程)上调用了一些网络操作。按照不允许的Android版本android3(蜂巢),您应该在主线程之外调用网络操作。

您可以使用AsyncStory、IntentService或创建自己的线程并在run方法中调用。有关更多信息,请访问连接到网络

我们还可以使用RxJava将网络操作移动到后台线程。它也相当简单。

webService.doSomething(someData).subscribeOn(Schedulers.newThread())-- This for background thread.observeOn(AndroidSchedulers.mainThread()) -- for callback on UI.subscribe(result -> resultText.setText("It worked!"),e -> handleError(e));

你可以用RxJava做更多的事情。这里有一些RxJava的链接。随意挖掘。

Android中的RxJava异步任务

http://blog.stablekernel.com/replace-asynctask-asynctaskloader-rx-observable-rxjava-android-patterns/

还有另一种非常方便的方法来解决这个问题——使用rxjava的并发功能。你可以在后台执行任何任务,并以非常方便的方式将结果发布到主线程,因此这些结果将被交给处理链。

第一个经过验证的答案建议是使用AsynTeam。是的,这是一个解决方案,但它现在已经过时了,因为周围有新的工具。

String getUrl() {return "SomeUrl";}
private Object makeCallParseResponse(String url) {return null;//}
private void processResponse(Object o) {
}

获取URL方法提供URL地址,它将在主线程上执行。

makeCallParseResponse(..)-实际工作

processResponse(..)-将在主线程上处理结果。

异步执行的代码如下所示:

rx.Observable.defer(new Func0<rx.Observable<String>>() {@Overridepublic rx.Observable<String> call() {return rx.Observable.just(getUrl());}}).subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).map(new Func1<String, Object>() {@Overridepublic Object call(final String s) {return makeCallParseResponse(s);}}).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Object>() {@Overridepublic void call(Object o) {processResponse(o);}},new Action1<Throwable>() {@Overridepublic void call(Throwable throwable) {// Process error here, it will be posted on// the main thread}});

与Async任务相比,此方法允许切换调度程序任意次数(例如,在一个调度程序上获取数据并在另一个调度程序上处理这些数据(例如,Scheduler.computation())。您还可以定义自己的调度程序。

为了使用此库,请在build.gradle文件中包含以下行:

   compile 'io.reactivex:rxjava:1.1.5'compile 'io.reactivex:rxandroid:1.2.0'

最后一个依赖项包括对. main Thread()调度程序的支持。

RxJava的优秀电子书

如何修复android.os.NetworkOnMainThreadException

什么是NetworkOnMainThreadException:

在Android中,我们必须在UI线程(主线程)上执行的所有UI操作。如果我们在主线程上执行后台操作或一些网络操作,那么我们有可能发生此异常并且应用程序不会响应。

如何修复它:

为了避免这个问题,你必须使用另一个线程来进行后台操作或网络操作,比如使用asyncTeam,并使用一些库来进行网络操作,比如Volley、AsyncHttp等。

android.os.NetworkOnMainThreadException在主线程上执行网络操作时抛出。您最好在Async任务中执行此操作以删除此异常。这样写:

    new AsyncTask<Void,String,String>(){
@Overrideprotected Void doInBackground(Void... params) {// Perform your network operation.// Get JSON or XML string from the server.// Store in a local variable (say response) and return.return response;}
protected void onPostExecute(String results){// Response returned by doInBackGround() will be received// by onPostExecute(String results).// Now manipulate your jason/xml String(results).}
}.execute();}

您也可以通过使用以下代码使用严格模式解决此问题。这也是解决此问题的替代方案。

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();StrictMode.setThreadPolicy(policy);

但最好的做法是使用AsyncTeam。

由于Android在单个线程上工作,因此您不应该在主线程上进行任何网络操作。有多种方法可以避免这种情况。

使用以下方式执行网络操作

  • 自动化任务:对于不需要太多时间的小型操作。
  • 意向服务:对于需要大量时间的网络操作。
  • 使用凌空改造等自定义库进行处理复杂的网络操作

永远不要使用StrictMode.setThreadPolicy(策略),因为它会冻结你的UI,根本不是一个好主意。

您不能在主线程或UI线程上调用网络。在Android上,如果您想调用网络,有两种选择-

  1. 调用async任务,它将运行一个后台线程来处理网络操作。
  2. 您可以创建自己的可运行线程来处理网络操作。

就个人而言,我更喜欢异步任务。有关更多信息,您可以参考此链接

新的Thread异步任务解决方案已经解释过了。

AsyncTask最好用于短操作。正常的Thread不适合Android。

看看使用处理线程处理程序的替代解决方案

处理线程

方便的类,用于启动具有循环器的新线程。循环器然后可用于创建处理程序类。请注意,仍必须调用start()

处理程序:

Handler允许您发送和处理与线程的MessageQueue关联的Message和Runnable对象。每个Handler实例都与单个线程和该线程的消息队列相关联。创建新Handler时,它将绑定到创建它的线程的线程/消息队列-从那时起,它将消息和可运行对象传递到该消息队列,并在它们从消息队列出来时执行它们。

解决方案:

  1. 创建HandlerThread

  2. 呼叫start()HandlerThread

  3. 通过从HanlerThread获取Looper来创建Handler

  4. Runnable对象中嵌入网络操作相关代码

  5. Runnable任务提交给Handler

示例代码片段,地址NetworkOnMainThreadException

HandlerThread handlerThread = new HandlerThread("URLConnection");handlerThread.start();handler mainHandler = new Handler(handlerThread.getLooper());
Runnable myRunnable = new Runnable() {@Overridepublic 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);

使用这种方法的优点:

  1. 为每个网络操作创建新的Thread/AsyncTask是昂贵的。Thread/AsyncTask将被销毁并为下一次网络操作重新创建。但是使用HandlerHandlerThread方法,您可以使用Handler将许多网络操作(作为可运行任务)提交给单个HandlerThread

RxAndroid是这个问题的另一个更好的替代方案,它使我们免于创建线程然后在Android UI线程上发布结果的麻烦。

我们只需要指定需要在哪些线程上执行任务,并且一切都在内部处理。

Observable<List<String>> musicShowsObservable = Observable.fromCallable(new Callable<List<String>>() {
@Overridepublic List<String> call() {return mRestClient.getFavoriteMusicShows();}
});
mMusicShowSubscription = musicShowsObservable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<String>>() {
@Overridepublic void onCompleted() { }
@Overridepublic void onError(Throwable e) { }
@Overridepublic void onNext(List<String> musicShows) {listMusicShows(musicShows);}});
  1. 通过指定(Schedulers.io()),RxAndroid将在不同的线程上运行getFavoriteMusicShows()

  2. 通过使用AndroidSchedulers.mainThread(),我们希望在UI线程上观察这个Observable,即我们希望在UI线程上调用onNext()回调。

您可以使用静态编程语言安科

静态编程语言android的新官方语言。你可以在这里找到更多关于它的信息:Android静态编程语言.

安科是Android中受支持的静态编程语言库。一些留档在的github页面上。

这个解决方案非常有用,只有@AntonioLeiva编写的几行代码:使用Anko在Android中使用静态编程语言运行后台任务(KAD 09).

doAsync {var result = runLongTask()uiThread {toast(result)}}

尽管很简单,但当你在UI线程上运行后台作业时,会发生NetworkOnMainThread,所以你必须做的一件事就是在后台运行你的长任务任务。你可以使用此方法执行此操作,并在Android应用程序中使用静态编程语言安科执行此操作。

永远不要在UI线程上做任何长时间运行的工作。那些长时间运行的工作可以是与服务器的通信,读取/写入文件等。这些任务应该在后台线程上。这就是创建ServiceAsyncTaskThreads的原因。您可以禁用StrictMode,这将防止崩溃。但是,永远不推荐这样做。

我建议您利用StrictMode,至少在调试模式下。使用下面的代码获取任何导致应用程序在主线程上变慢的问题的日志。

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());

您可以设置不同的处罚:

penaltyLog() // to print logpenaltyDeath() // This will crash you App(so costly penalty)penaltyDialog() // Show alert when something went lazy on Main thread

这里有更多关于StrictMode的信息:StrictMode|Android Developers

主线程是UI线程,不能在主线程中做可能会阻塞用户交互的操作。你可以通过两种方式解决这个问题:

强制在主线程中像这样做任务

StrictMode.ThreadPolicy threadPolicy = new StrictMode.ThreadPolicy.Builder().permitAll().build();StrictMode.setThreadPolicy(threadPolicy);

或者创建一个简单的处理程序并根据需要更新主线程。

Runnable runnable;Handler newHandler;
newHandler = new Handler();runnable = new Runnable() {@Overridepublic void run() {try {//update UI} catch (Exception e) {e.printStackTrace();}}};newHandler.post(runnable);

并停止线程使用:

newHandler.removeCallbacks(runnable);

有关更多信息,请查看:无痛线程

关于这个问题已经有很多很好的答案,但是自从这些答案发布以来,已经出现了很多很棒的库。这是一种新手指南。

我将介绍执行网络操作的几个用例,每个用例有一个解决方案或两个。

基于HTTP的REST

通常是JSON,但它可以是XML或其他东西。

完整的API访问

假设您正在编写一个允许用户跟踪股票价格、利率和货币汇率的应用程序。您会发现一个JSON API如下所示:

http://api.example.com/stocks                       // ResponseWrapper<String> object containing a// list of strings with ticker symbolshttp://api.example.com/stocks/$symbol               // Stock objecthttp://api.example.com/stocks/$symbol/prices        // PriceHistory<Stock> objecthttp://api.example.com/currencies                   // ResponseWrapper<String> object containing a// list of currency abbreviationhttp://api.example.com/currencies/$currency         // Currency objecthttp://api.example.com/currencies/$id1/values/$id2  // PriceHistory<Currency> object comparing the prices// of the first currency (id1) to the second (id2)

从广场改造

对于具有多个端点的API来说,这是一个很好的选择,允许您声明REST端点,而不必像亚马逊离子Java凌空(网站:改造)等其他库那样单独编码它们。

您如何将其与财务API一起使用?

Filebuild.gradle

将这些行添加到您的模块级别build.gradle文件中:

implementation 'com.squareup.retrofit2:retrofit:2.3.0' // Retrofit library, current as of September 21, 2017implementation 'com.squareup.retrofit2:converter-gson:2.3.0' // Gson serialization and deserialization support for retrofit, version must match retrofit version

FileFinancesApi.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);}

ClassFinancesApiBuilder

public class FinancesApiBuilder {public static FinancesApi build(String baseUrl){return new Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(GsonConverterFactory.create()).build().create(FinancesApi.class);}}

这个很棒的答案到在改造中添加标头参数)。

一次性REST API访问

假设您正在构建一个“情绪天气”应用程序,该应用程序可以查找用户的GPS位置并检查该区域的当前温度并告诉他们情绪。这种类型的应用程序不需要声明API端点;它只需要能够访问一个API端点。

Ion

对于这种类型的访问,这是一个很棒的库。

请阅读姆西西姆伟大的回答如何修复android.os.NetworkOnMainThreadException?

通过HTTP加载图像

Volley

Volley也可以用于REST API,但由于需要更复杂的设置,我更喜欢使用

假设您正在构建一个社交网络应用程序,并希望加载朋友的个人资料图片。

Filebuild.gradle

将此行添加到您的模块级别build.gradle文件中:

implementation 'com.android.volley:volley:1.0.0'

FileImageFetch.java

Volley需要比Retrofit更多的设置。你需要创建一个像这样的类来设置请求队列、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>();@Overridepublic Bitmap getBitmap(String url) {return cache.get(url);}@Overridepublic void putBitmap(String url, Bitmap bitmap) {cache.put(url, bitmap);}});}return imageLoader;}}

Fileuser_view_dialog.xml

将以下内容添加到布局XML文件以添加图像:

<com.android.volley.toolbox.NetworkImageViewandroid: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"/>

FileUserViewDialog.java

将以下代码添加到onCreate方法(Fra的,活动)或构造函数(对话框):

NetworkImageView profilePicture = view.findViewById(R.id.profile_picture);profilePicture.setImageUrl("http://example.com/users/images/profile.jpg", ImageFetch.getImageLoader(getContext());

毕加索

毕加索是Square的另一个优秀库。请参阅网站以获取一些很棒的示例。

截至2018年,我建议使用静态编程语言RxJava进行网络获取。下面是一个简单的示例。

Single.fromCallable {// Your Network Fetching CodeNetwork.fetchHttp(url)}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe {// What you need to do with your result on the viewresult -> view.updateScreen(result)}

您可以将部分代码移动到另一个线程中以卸载main thread并避免获得ANR主线程上的网络异常非法国家例外(例如,无法访问主线程上的数据库,因为它可能会长时间锁定UI)。

有一些方法你应该选择取决于情况

Java线程或Android处理线程

Java线程只能一次性使用,并在执行其run方法后死亡。

HandlerThread是一个方便的类,用于启动具有循环器的新线程。

异步任务(API级别30中的已弃用

异步任务被设计为围绕线程处理程序的帮助类,并不构成通用的线程框架。理想情况下,异步任务应该用于短操作(最多几秒钟)。如果您需要保持线程长时间运行,强烈建议您使用java.util.concurrent包提供的各种API,例如遗嘱执行人线程池执行器任务列表

由于主要线程垄断了UI组件,因此无法访问某些View,这就是Handler前来救援的原因

[执行器框架]

实现ExecutorService的ThreadPoolExecator类,它对线程池进行精细控制(例如,核心池大小、最大池大小、保持存活时间等)

SchduledThreadPoolExecator-一个扩展ThreadPoolExecator的类。它可以在给定延迟后或定期调度任务。

未来任务

但是,如果结果尚未准备好或处理尚未完成,则调用get()将阻塞线程

异步任务加载器

AsyncTaskLoader,因为它们解决了许多固有的问题

服务

这是Android上长时间运行处理的事实选择,一个很好的例子是上传或下载大文件。即使用户退出应用程序,上传和下载也可能继续,并且您当然不想阻止用户在这些任务进行时使用该应用程序。

JobScheduler

实际上,您必须创建一个Service并使用JobInfo. Builder创建一个作业,该作业指定了何时运行服务的标准。

RxJava

使用可观察序列编写异步和基于事件的程序的库。

协程(静态编程语言)

它的主要要点是,它使异步代码看起来很像同步代码

阅读更多这里这里这里这里

不同的选项:

  1. 使用正常Java可运行线程来处理网络任务,可以使用runOnUIThread()来更新UI

  2. intentservice/async任务可用于在获得网络响应后想要更新UI的情况下

如果您使用静态编程语言和安科,您可以添加:

doAsync {method()}

您可以使用静态编程语言协程

 class YoutActivity : AppCompatActivity, CoroutineScope {      
override fun onCreate(...) {launch {  yourHeavyMethod() }}
suspend fun yourHeavyMethod() {with(Dispatchers.IO){ yourNetworkCall() }......}}

您可以使用本指南

静态编程语言版本

internal class RetrieveFeedTask : AsyncTask<String, Void, RSSFeed>() {
override fun doInBackground(vararg urls: String): RSSFeed? {
try {// download// prepare RSSFeedsreturn RSSFeeds
} catch (e: Exception) {
//handle exceptionreturn null}}
override fun onPostExecute(feed: RSSFeed) {// TODO: check this.exception// TODO: do something with the feed}}

调用示例,

RetrieveFeedTask().execute(url)

在后台线程中使用AsycTask执行此操作

Java

class NetworkThread extends AsyncTask<String, Void, String> {
protected Void doInBackground(String... arg0) {//Your implementation}
protected void onPostExecute(String result) {// TODO: do something with the feed}}

无论你需要什么打电话

new NetworkThread().execute("Your URL here");

静态编程语言

internal class MyNetworkTask : AsyncTask<String, Void, RSSFeed>() {
override fun doInBackground(vararg urls: String): RSSFeed? {try {// download// prepare RSSFeedsreturn RSSFeeds} catch (e: Exception) {//handle exceptionreturn null}}
override fun onPostExecute(feed: RSSFeed) {// TODO: check this.exception// TODO: do something with the feed}}

调用kotlin

MyNetworkTask().execute(url)

我遇到了类似的问题。我只是在您的活动的oncreate方法中使用了以下内容。

// Allow strict modeStrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();StrictMode.setThreadPolicy(policy);

而且效果很好。

需要注意的是,将其用于超过100毫秒的网络请求将导致明显的UI冻结和潜在的ANR(应用程序未响应),因此请记住这一点。

android jetpack引入了#0,它修复了android8.1(Oreo)中的后台服务限制问题,并使用android5.0(Lollipop)下面的警报管理器和Lolipop上面的JobScheduler。

请使用#0在后台线程上运行任务,即使用户关闭应用程序,它也会继续运行。

开发人员-android

理想情况下,异步任务应该用于短操作(最多几秒钟)。

使用newCachedThreadPool是好的,你也可以考虑其他选项,如newSingleThreadExecutornewFixedThreadPool

    ExecutorService myExecutor = Executors.newCachedThreadPool();myExecutor.execute(new Runnable() {@Overridepublic void run() {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);}});

线程池执行器是一个帮助类,使这个过程更容易。这类管理一组线程的创建,设置它们的优先级,并管理工作如何在这些线程之间分配。随着工作负载的增加或减少,类会旋转或破坏更多的线程来适应工作负载。

有关Android线程的更多信息,请参阅这个

在Android上,网络操作不能在主线程上运行。您可以使用ThreadAsyncTask(短时间运行的任务)、Service(长时间运行的任务)进行网络操作。当应用程序尝试在其主线程上执行网络操作时,会抛出android.os.NetworkOnMainThreadException。如果您的任务耗时超过5秒,则需要强制关闭。

AsyncTask中运行您的代码:

class FeedTask 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}}

new Thread(new Runnable(){@Overridepublic void run() {try {// Your implementation}catch (Exception ex) {ex.printStackTrace();}}}).start();

这是不推荐的。

但出于调试的目的,您也可以使用以下代码禁用严格模式:

if (android.os.Build.VERSION.SDK_INT > 9) {StrictMode.ThreadPolicy policy =new StrictMode.ThreadPolicy.Builder().permitAll().build();StrictMode.setThreadPolicy(policy);}

这些答案需要更新,以使用更现代的方式连接到Internet上的服务器并处理一般的异步任务。

例如,您可以找到Google Drive API示例中使用任务的示例。在这种情况下也应该使用相同的方法。我将使用OP的原始代码来演示这种方法。

首先,您需要定义一个离主线程执行器,并且只需要执行一次:

private val mExecutor: Executor = Executors.newSingleThreadExecutor()

然后在那个执行器中处理你的逻辑,它将在主线程中运行

Tasks.call (mExecutor, Callable<String> {
val url = URL(urlToRssFeed)val factory = SAXParserFactory.newInstance()val parser = factory.newSAXParser()val xmlreader = parser.getXMLReader()val theRSSHandler = RssHandler()xmlreader.setContentHandler(theRSSHandler)val is = InputSource(url.openStream())xmlreader.parse(is)theRSSHandler.getFeed()
// Complete processing and return a String or other object.// E.g., you could return Boolean indicating a success or failure.return@Callable someResult}).continueWith{// it.result here is what your asynchronous task has returnedprocessResult(it.result)}

继续子句将在您的异步任务完成后执行,您将有权访问任务通过it.result返回的值。

静态编程语言

如果您使用的是静态编程语言,则可以使用协程

fun doSomeNetworkStuff() {GlobalScope.launch(Dispatchers.IO) {// ...}}

Google已弃用Android 11中的Android AsyncTaskAPI。

即使你在main活动之外创建了一个线程类,只要在main中调用它,你也会得到同样的错误。调用必须在可运行线程中,但是如果你需要一些异步代码在后台执行或之后在帖子中执行,你可以在这里查看静态编程语言和Java的一些替代方案:

*https://stackoverflow.com/questions/58767733/android-asynctask-api-deprecating-in-android-11-what-are-the-alternatives*

对我特别有效的是mayank1513对上述链接上可运行线程的Java8实现的回答。代码如下:

new Thread(() -> {// do background stuff hererunOnUiThread(()->{// OnPostExecute stuff here});}).start();

但是,您可以首先在代码的某些部分中定义线程,然后在其他地方启动它,如下所示:

线程定义

Thread thread = new Thread(() -> {// do background stuff hererunOnUiThread(()->{// OnPostExecute stuff here});});

线程调用

thread.start();

我希望这可以避免有人看到已弃用的AsyncTeam的头痛。

这里有一个使用OkHttpFutureExecutorServiceCallable的简单解决方案:

final OkHttpClient httpClient = new OkHttpClient();final ExecutorService executor = newFixedThreadPool(3);
final Request request = new Request.Builder().url("http://example.com").build();
Response response = executor.submit(new Callable<Response>() {public Response call() throws IOException {return httpClient.newCall(request).execute();}}).get();

Android不允许在主线程上运行长时间运行的操作。

因此,只需使用不同的线程并在需要时将结果发布到主线程。

new Thread(new Runnable() {@Overridepublic void run() {/*// Run operation here*/// After getting the resultrunOnUiThread(new Runnable() {@Overridepublic void run() {// Post the result to the main thread}});}}).start();

我将返回值的网络访问函数转换为挂起函数,如下所示:

suspend fun isInternetReachable(): Boolean {......return result}

然后我修改了我使用函数的位置以适应这个:

...Globalscope.async{...result = isInternetReachable()...}...
Executors.newFixedThreadPool(3).execute(() -> {//DO Task;});

我在静态编程语言中使用Thread解决了这个问题。有很多使用Java的例子,所以我想添加一个适用于静态编程语言的解决方案。

 Thread {println("NEW THREAD")callAPI() // add your own task}.start()

因此,正如许多其他人所解释的那样,您不能用调用阻止主线程,因此有必要创建一个新线程。