我在运行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

如何解决此问题?


当前回答

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

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

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

您可以设置不同的惩罚:

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

这里有更多关于StrictMode的信息:StrictMode|Android开发者。

其他回答

公认的答案有一些显著的缺点。除非您确实知道自己在做什么,否则不建议将AsyncTask用于网络。一些缺点包括:

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

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

使用一个库可以很好地完成这项工作-在这个问题中可以很好的比较网络库,或者而是使用Service或IntentService,或者使用PendingIntent,通过Activity的onActivityResult方法返回结果。

IntentService方法

缺点:

比AsyncTask更多的代码和复杂性,尽管没有您想象的那么多将请求排队并在单个后台线程上运行它们。您可以通过将IntentService替换为等效的服务实现(可能像这样)来轻松控制这一点。嗯,我现在真的想不出其他人了

优点:

避免短期内存泄漏问题如果您的活动在网络操作进行时重新启动,它仍然可以通过其onActivityResult方法接收下载结果一个比AsyncTask更好的平台来构建和重用健壮的网络代码。示例:如果您需要执行重要的上载,可以从“活动”中的AsyncTask执行,但如果用户上下文切换到应用程序之外以接听电话,则系统可能会在上载完成之前关闭应用程序。不太可能终止具有活动服务的应用程序。如果您使用自己的IntentService并发版本(如我上面链接的版本),您可以通过Executor控制并发级别。

实施总结

您可以很容易地实现IntentService在单个后台线程上执行下载。

步骤1:创建IntentService以执行下载。您可以通过Intent extra告诉它要下载什么,并将PendingIntent传递给它,用于将结果返回给“活动”:

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 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:从“活动”调用服务,传递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项目。

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

主线程基本上是UI线程。

因此,如果说您不能在主线程中执行网络操作,则意味着您不能在UI线程中进行网络操作,这意味着您也不能在其他线程中的*runOnUiThread(new Runnable(){…}*块中执行网络操作。

(我只是花了很长时间才弄明白为什么我在主线程之外的其他地方出现了这个错误。这就是为什么;这个线程有帮助;希望这个评论能帮助其他人。)

当应用程序尝试在其主线程上执行网络操作时,会引发此异常。如果您的任务耗时超过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
    }
}

使用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 RSSFeeds
             return RSSFeeds
         } catch (e: Exception) {
            //handle exception
            return null
        }
    }

    override fun onPostExecute(feed: RSSFeed) {
        // TODO: check this.exception
        // TODO: do something with the feed
    }
}

呼叫kotlin

MyNetworkTask().execute(url)

解决这个问题还有另一种非常方便的方法——使用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()调度程序的支持。

RxJava有一本很棒的电子书。