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

如何解决此问题?

答案

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

后果:

您的应用程序(在 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 层次结构都有隐式引用。此参考可防止在 AsyncTask 的后台工作完成之前对 Activity 进行垃圾回收。如果用户的连接速度慢和 / 或下载量大,则这些短期内存泄漏可能会成为问题 - 例如,如果方向更改多次(并且您不取消执行的任务),或者用户导航远离活动。
  • AsyncTask 根据执行平台的不同而具有不同的执行特征:在 API 级别 4 之前,AsyncTasks 在单个后台线程上串行执行;从 API 级别 4 到 API 级别 10,AsyncTasks 在最多 128 个线程的池中执行;从 API 级别 11 开始,AsyncTask 在单个后台线程上串行执行(除非您使用重载的executeOnExecutor方法并提供替代的执行程序)。在 ICS 上串行运行时运行良好的代码在 Gingerbread 上同时执行时可能会中断,例如,如果您无意间执行了顺序。

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

  1. 使用一个可以为您做得很好的库 - 这个问题中网络库的比较不错,或者
  2. 而是使用ServiceIntentService ,或者使用PendingIntent通过 Activity 的onActivityResult方法返回结果。

IntentService 方法

缺点:

  • AsyncTask更多的代码和复杂性,尽管没有您想象的那么多
  • 将请求排队并在单个后台线程上运行它们。您可以通过用等效的Service实现替换IntentService来轻松地控制此操作,也许像这样
  • 嗯,我现在真的想不起其他人了

优点:

  • 避免短期内存泄漏问题
  • 如果在进行网络操作时重新启动活动,它仍然可以通过其onActivityResult方法接收下载结果
  • 比 AsyncTask 更好的平台可以构建和重用强大的网络代码。示例:如果需要进行重要的上载,则可以通过Activity AsyncTask进行,但是如果用户上下文切换出应用程序以拨打电话,则系统可能会在上载完成之前终止该应用程序。使用活动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);
}

包含一个完整的工作 Android 的工作室 / gradle 这个项目一个项目的 GitHub 可以在这里

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

Android 开发人员网站上有一篇有关无痛线程的文章,对此进行了很好的介绍,它将为您提供比此处实际提供的更好的答案深度。

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

使用服务或 AsyncTask

另请参阅堆栈溢出问题:

android.os.NetworkOnMainThreadException 从 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"/>

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

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