How to fix 'android.os.NetworkOnMainThreadExce

2020-01-22 09:35发布

I got an error while running my Android project for RssReader.

Code:

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

And it shows the below error:

android.os.NetworkOnMainThreadException

How can I fix this issue?

30条回答
该账号已被封号
2楼-- · 2020-01-22 10:18

There is another very convenient way for tackling this issue - use rxJava's concurrency capabilities. You can execute any task in background and post results to main thread in a very convenient way, so these results will be handed to processing chain.

The first verified answer advice is to use AsynTask. Yes, this is a solution, but it is obsolete nowadays, because there are new tools around.

String getUrl() {
    return "SomeUrl";
}

private Object makeCallParseResponse(String url) {
    return null;
    //
}

private void processResponse(Object o) {

}

The getUrl method provides the URL address, and it will be executed on the main thread.

makeCallParseResponse(..) - does actual work

processResponse(..) - will handle result on main thread.

The code for asynchronous execution will look like:

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

Compared to AsyncTask, this method allow to switch schedulers an arbitrary number of times (say, fetch data on one scheduler and process those data on another (say, Scheduler.computation()). You can also define you own schedulers.

In order to use this library, include following lines into you build.gradle file:

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

The last dependency includes support for the .mainThread() scheduler.

There is an excellent ebook for rx-java.

查看更多
再贱就再见
3楼-- · 2020-01-22 10:19

In simple words,

DO NOT DO NETWORK WORK IN THE UI THREAD

For example, if you do an HTTP request, that is a network action.

Solution:

  1. You have to create a new Thread
  2. Or use AsyncTask class

Way:

Put all your works inside

  1. run() method of new thread
  2. Or doInBackground() method of AsyncTask class.

But:

When you get something from Network response and want to show it on your view (like display response message in TextView), you need to return back to the UI thread.

If you don't do it, you will get ViewRootImpl$CalledFromWrongThreadException.

How to?

  1. While using AsyncTask, update view from onPostExecute() method
  2. Or call runOnUiThread() method and update view inside the run() method.
查看更多
走好不送
4楼-- · 2020-01-22 10:21

You should almost always run network operations on a thread or as an asynchronous task.

But it is possible to remove this restriction and you override the default behavior, if you are willing to accept the consequences.

Add:

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

StrictMode.setThreadPolicy(policy); 

In your class,

and

ADD this permission in android manifest.xml file:    

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

Consequences:

Your app will (in areas of spotty internet connection) become unresponsive and lock up, the user perceives slowness and has to do a force kill, and you risk the activity manager killing your app and telling the user that the app has stopped.

Android has some good tips on good programming practices to design for responsiveness: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html

查看更多
Juvenile、少年°
5楼-- · 2020-01-22 10:21

You cannot perform network I/O on the UI thread on Honeycomb. Technically, it is possible on earlier versions of Android, but it is a really bad idea as it will cause your app to stop responding, and can result in the OS killing your app for being badly behaved. You'll need to run a background process or use AsyncTask to perform your network transaction on a background thread.

There is an article about Painless Threading on the Android developer site which is a good introduction to this, and it will provide you with a much better depth of an answer than can be realistically provided here.

查看更多
地球回转人心会变
6楼-- · 2020-01-22 10:23

The accepted answer has some significant down-sides. It is not advisable to use AsyncTask for networking unless you really know what you are doing. Some of the down-sides include:

  • AsyncTask's created as non-static inner classes have an implicit reference to the enclosing Activity object, its context, and the entire View hierarchy created by that activity. This reference prevents the Activity from being garbage collected until the AsyncTask's background work completes. If the user's connection is slow, and/or the download is large, these short-term memory leaks can become a problem - for example if the orientation changes several times (and you don't cancel the executing tasks), or the user navigates away from the Activity.
  • AsyncTask has different execution characteristics depending on the platform it executes on: prior to API level 4 AsyncTasks execute serially on a single background thread; from API level 4 through API level 10, AsyncTasks execute on a pool of up to 128 threads; from API level 11 onwards AsyncTask executes serially on a single background thread (unless you use the overloaded executeOnExecutor method and supply an alternative executor). Code that works fine when run serially on ICS may break when executed concurrently on Gingerbread, say, if you have inadvertent order-of-execution dependencies.

If you want to avoid short-term memory leaks, have well defined execution characteristics across all platforms, and have a base to build really robust network handling, you might want to consider:

  1. Using a library that does a nice job of this for you - there's a nice comparison of networking libs in this question, or
  2. Using a Service or IntentService instead, perhaps with a PendingIntent to return the result via the Activity's onActivityResult method.

IntentService approach

Down-sides:

  • More code and complexity than AsyncTask, though not as much as you might think
  • Will queue requests and run them on a single background thread. You can easily control this by replacing IntentService with an equivalent Service implementation, perhaps like this one.
  • Um, I can't think of any others right now actually

Up-sides:

  • Avoids the short-term memory leak problem
  • If your activity restarts while network operations are in-flight it can still receive the result of the download via its onActivityResult method
  • Better platform than AsyncTask to build and re-use robust networking code. Example: if you need to do an important upload, you could do it from AsyncTask in an Activity, but if the user context-switches out of the app to take a phone-call, the system may kill the app before the upload completes. It is less likely to kill an application with an active Service.
  • If you use your own concurrent version of IntentService (like the one I linked above) you can control the level of concurrency via the Executor.

Implementation summary

You can implement an IntentService to perform downloads on a single background thread quite easily.

Step 1: Create an IntentService to perform the download. You can tell it what to download via Intent extra's, and pass it a PendingIntent to use to return the result to the 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);
        }
    }
}

Step 2: Register the service in the manifest:

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

Step 3: Invoke the service from the Activity, passing a PendingResult object which the Service will use to return the result:

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

Step 4: Handle the result in 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);
}

A github project containing a complete working Android-Studio/gradle project is available here.

查看更多
霸刀☆藐视天下
7楼-- · 2020-01-22 10:24

Network-based operations cannot be run on the main thread. You need to run all network-based tasks on a child thread or implement AsyncTask.

This is how you run a task in a child thread:

new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            // Your implementation goes here
        } 
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}).start();
查看更多
登录 后发表回答