题 如何修复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

我该如何解决这个问题?


1982
2018-06-14 12:02


起源


阅读这篇博客文章 有关更多信息,请参阅NetworkOnMainThreadException。它解释了为什么会出现在Android 3.0及更高版本上。 - Adrian Monk
要在rite轨道上首先阅读关于android中的网络请求然后我会建议学习“Volley”。 - Anuj Sharma
有许多替代库可以解决这个问题。列出了许多 在本页底部。如果你有更多,我们带他们:) - Snicolas
您需要在与主(UI)线程分开的线程上运行Internet活动 - Naveed Ahmad
stackoverflow.com/questions/16439587/...    以上链接很好 - nguyenvangiangbn


答案:


当应用程序尝试在其主线程上执行网络操作时,将引发此异常。运行你的代码 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"/>

2240
2018-06-14 12:15



我认为值得注意的是,上面的代码片段应该是一个子类(内部类),最好是私有的。这样,当AsyncTask完成时,您仍然可以操纵类的内部。 - dyslexicanaboko
实际上我做了与你上面提到的相同的事情,但我面临这个错误 java.lang.RuntimeException:无法在未调用Looper.prepare()的线程内创建处理程序 - Dhruv Tyagi
这是错误的答案。 我总是在人们的代码中遇到过这个问题,并且总是不得不一直修复它。 AsyncTask不应该用于网络活动,因为它与活动有关,而与活动生命周期无关。在此任务正在运行的情况下旋转设备将导致异常并导致应用程序崩溃。使用IntentService来删除sqlite数据库中的数据。 - Brill Pappin
注意,AsyncTask通常用于每个活动网络操作,当它不应该时。它的生命周期与活动不同步。要获取数据,您应该使用IntentService和视图后面的数据库。 - Brill Pappin
对于这种情况,我强烈建议使用AsyncTask Loaders。 - Roman Gherta


您应该几乎总是在线程上运行网络操作或作为异步任务。

但它  如果您愿意接受后果,可以删除此限制并覆盖默认行为。

加:

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


557
2018-02-15 06:59



这是一个非常糟糕的主意。解决方案是避免主线程上的网络IO(如接受的答案所示)。 - MByD
有了它,你只能隐藏你真正的问题。 - Alex
@TwistedUmbrella AsyncTask不添加代码页,它添加了6行(类声明,覆盖注释, doInBackground 声明,2个右括号和一个电话 execute())。另一方面,即使您提到的网站中的一次抓取也会导致UI响应性显着滞后。不要偷懒。 - Zoltán
@TwistedUmbrella 别傻了。这就是应用程序的糟糕程度。 无论是下载文本文件还是图库,都应遵循最佳实践。在主线程上运行网络IO不是最佳做法。 - States
Upvoted。这个答案是正确的,并且对于那些既不天真也不愚蠢,但只需要同步的许多程序员(即:这个 必须阻止该应用程序)电话,这正是需要的。我很高兴Android默认抛出异常(恕我直言,这是一个非常“有用”的事情!) - 但我同样乐意说“谢谢,但是 - 这实际上是我想要的”并且覆盖它。 - Adam


我用新的方法解决了这个问题 Thread

Thread thread = new Thread(new Runnable() {

    @Override
    public void run() {
        try  {
            //Your code goes here
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

thread.start(); 

346
2018-01-21 16:31



您可以使用单个线程执行程序服务,而不是每次要执行网络操作时都创建新线程。 - Alex Lockwood
简单但危险。匿名Runnable具有对封闭类的隐式引用(例如,您的Activity或Fragment),防止在线程完成之前对其进行垃圾回收。您至少应该将优先级设置为Process.BACKGROUND,否则此线程将以与main / ui线程相同的优先级运行,与生命周期方法和ui帧速率竞争(注意编排者在日志中的警告)。 - Stevie
@Stevie如何设置优先级?既不是runnble也不是executorService都有这样的setter方法 - J. K.
@ J.K。使用自定义ThreadFactory提供ExecutorService,并在返回之前在线程上调用Thread.setPriority。 - Stevie
为那些打算进行网络通话(会阻止)的东西设置优先级似乎有些过分。 - john16384


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

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


124
2018-06-14 12:07





接受的答案有一些重要的缺点。除非你这样做,否则不建议使用AsyncTask进行网络连接  知道你在做什么。一些不利方面包括:

  • 作为非静态内部类创建的AsyncTask具有对封闭的Activity对象,其上下文以及该活动创建的整个View层次结构的隐式引用。此引用可防止在AsyncTask的后台工作完成之前对Activity进行垃圾回收。如果用户的连接速度很慢,和/或下载量很大,则这些短期内存泄漏可能会成为问题 - 例如,如果方向发生多次更改(并且您没有取消正在执行的任务),或者用户导航远离活动。
  • AsyncTask具有不同的执行特性,具体取决于它执行的平台:API级别4之前AsyncTasks在单个后台线程上串行执行;从API级别4到API级别10,AsyncTasks在最多128个线程的池上执行;从API级别11开始,AsyncTask在单个后台线程上串行执行(除非您使用重载 executeOnExecutor方法并提供另一种执行者)。当在Gingerbread上同时执行时,在ICS上串行运行时运行正常的代码可能会中断,例如,如果您有无意的执行顺序依赖项。

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

  1. 使用一个为你做得很好的库 - 有一个很好的网络库比较 这个问题, 要么
  2. 用一个 Service 要么 IntentService 相反,也许是一个 PendingIntent 通过Activity返回结果 onActivityResult 方法。

IntentService方法

下行方面:

  • 比代码和复杂性更多 AsyncTask虽然没有你想象的那么多
  • 将队列请求排队并在a上运行它们  背景线程。您可以通过替换轻松控制它 IntentService 与等价物 Service 实施,也许就像 这个
  • 嗯,实际上我现在想不出任何其他人

向上方面:

  • 避免短期内存泄漏问题
  • 如果您的活动在网络操作正在进行时重新启动,它仍然可以通过其接收下载结果 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对象,Service将使用该对象返回结果:

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项目 这里


118
2018-01-22 13:17



IntentService是执行此操作的正确方法,而不是根除,因为AsyncTask正是不这样做的方式。 - Brill Pappin
@BrillPappin我几乎完全同意并且已经重新措辞以强调AsyncTask的缺点。 (我仍然认为有一个 很小 如果您真的知道自己在做什么的情况 - 它 威力 可以使用AsyncTask,但是接受的答案并没有指出任何缺点,并且太受欢迎了Android的好处。 - Stevie
你真的需要吗? IllustrativeRSS?如果你不使用RSS的东西怎么办? - Cullub


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

使用Service或AsyncTask

另请参阅Stack Overflow问题:

android.os.NetworkOnMainThreadException从Android发送电子邮件


59
2017-08-18 09:18



也许值得强调的一点是,如果你使用服务,你仍然需要创建一个单独的线程 - 服务回调在主线程上运行。另一方面,IntentService在后台线程上运行其onHandleIntent方法。 - Stevie
你不应该使用AsyncTask进行长时间运行!指南最多指定2到3秒。 - Dage


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

例如:

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

并将其添加到AndroidManifest.xml

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

51
2017-12-24 14:33



但是我们怎样才能找到线程何时完成,以便我们可以在UI线程中执行下一组任务? AsyncTask提供了执行此操作的工具。有没有办法使用runnable线程做同样的事情? - Piyush Soni
它将逐步处理您的代码,因此在代码结束时,您需要使用处理程序返回UI线程 - henry4343
mobileorchard.com/... - henry4343
您可以使用异步任务或意图服务,因为它在工作线程上执行。 - Chetan Chaudhari


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

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

不建议这样做: 使用 AsyncTask接口。

这两种方法的完整代码


46
2017-10-24 07:10



是的ANR错误会来。表示应用程序未在5秒内响应。 - Muhammad Mubashir
这是一个非常糟糕的答案。您不应该更改线程的策略,而是编写更好的代码:不要在主线程上进行网络操作! - shkschneider
不好,但对POC /调试很有用 - hB0
@Sandeep你和其他观众也应该读这个。 stackoverflow.com/a/18335031/3470479 - Prakhar1001