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条回答
beautiful°
2楼-- · 2020-01-22 10:32

This exception is thrown when an application attempts to perform a networking operation on its main thread. Run your code in 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
    }
}

How to execute the task:

In MainActivity.java file you can add this line within your oncreate() method

new RetrieveFeedTask().execute(urlToRssFeed);

Don't forget to add this to AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET"/>
查看更多
等我变得足够好
3楼-- · 2020-01-22 10:32

The error is due to executing long running operations in main thread,You can easily rectify the problem by using AsynTask or Thread. You can checkout this library AsyncHTTPClient for better handling.

AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {

    @Override
    public void onStart() {
        // Called before a request is started
    }

    @Override
    public void onSuccess(int statusCode, Header[] headers, byte[] response) {
        // Called when response HTTP status is "200 OK"
    }

    @Override
    public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
        // Called when response HTTP status is "4XX" (for example, 401, 403, 404)
    }

    @Override
    public void onRetry(int retryNo) {
        // Called when request is retried
    }
});
查看更多
唯我独甜
4楼-- · 2020-01-22 10:32

You should not do any time-consuming task on the main thread (UI thread), like any network operation, file I/O, or SQLite database operations. So for this kind of operation, you should create a worker thread, but the problem is that you can not directly perform any UI related operation from your worker thread. For that, you have to use Handler and pass the Message.

To simplify all these things, Android provides various ways, like AsyncTask, AsyncTaskLoader, CursorLoader or IntentService. So you can use any of these according to your requirements.

查看更多
forever°为你锁心
5楼-- · 2020-01-22 10:33

This happens in Android 3.0 and above. From Android 3.0 and above, they have restricted using network operations (functions that access the Internet) from running in the main thread/UI thread (what spawns from your on create and on resume methods in the activity).

This is to encourage using separate threads for network operations. See AsyncTask for more details on how to perform network activities the right way.

查看更多
迷人小祖宗
6楼-- · 2020-01-22 10:36

There are many great answers already on this question, but a lot of great libraries have come out since those answers were posted. This is intended as a kind of newbie-guide.

I will cover several use cases for performing network operations and a solution or two for each.

ReST over HTTP

Typically Json, can be XML or something else

Full API Access

Let's say you are writing an app that lets users track stock prices, interest rates and currecy exchange rates. You find an Json API that looks something like this:

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

Retrofit from Square

This is an excellent choice for an API with multiple endpoints and allows you to declare the ReST endpoints instead of having to code them individually as with other libraries like ion or Volley. (website: http://square.github.io/retrofit/)

How do you use it with the finances API?

build.gradle

Add these lines to your Module level buid.gradle:

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

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

FinancesApiBuilder

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

FinancesFragment snippet

FinancesApi api = FinancesApiBuilder.build("http://api.example.com/"); //trailing '/' required for predictable behavior
api.getStock("INTC").enqueue(new Callback<Stock>(){
    @Override
    public void onResponse(Call<Stock> stockCall, Response<Stock> stockResponse){
        Stock stock = stockCall.body();
        //do something with the stock
    }
    @Override
    public void onResponse(Call<Stock> stockCall, Throwable t){
        //something bad happened
    }
}

If your API requires an API Key or other header like a user token, etc. to be sent, Retrofit makes this easy (see this awesome answer for details: https://stackoverflow.com/a/42899766/1024412).

One off ReST API access

Let's say you're building a "mood weather" app that looks up the users GPS location and checks the current temperature in that area and tells them the mood. This type of app doesn't need to declare API endpoints; it just needs to be able to access one API endpoint.

Ion

This is a great library for this type of access.

Please read msysmilu's great answer (https://stackoverflow.com/a/28559884/1024412)

Load images via HTTP

Volley

Volley can also be used for ReST APIs, but due to the more complicated setup required I prefer to use Retrofit from Square as above (http://square.github.io/retrofit/)

Let's say you are building a social networking app and want to load profile pictures of friends.

build.gradle

Add this line to your Module level buid.gradle:

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

ImageFetch.java

Volley requires more setup than Retrofit. You will need to create a class like this to setup a RequestQueue, an ImageLoader and an ImageCache, but it's not too bad:

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>();
                @Override
                public Bitmap getBitmap(String url) {
                    return cache.get(url);
                }
                @Override
                public void putBitmap(String url, Bitmap bitmap) {
                    cache.put(url, bitmap);
                }
            });
        }
        return imageLoader;
    }
}

user_view_dialog.xml

Add the following to your layout xml file to add an image:

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

UserViewDialog.java

Add the following code to the onCreate method (Fragment, Activity) or the constructor (Dialog):

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

Picasso

Another excellent library from Square. Please see the site for some great examples: http://square.github.io/picasso/

查看更多
我想做一个坏孩纸
7楼-- · 2020-01-22 10:36

RxAndroid is another better alternative to this problem and it saves us from hassles of creating threads and then posting results on Android UI thread. We just need to specify threads on which tasks need to be executed and everything is handled internally.

Observable<List<String>> musicShowsObservable = Observable.fromCallable(new Callable<List<String>>() { 

  @Override 
  public List<String> call() { 
    return mRestClient.getFavoriteMusicShows(); 
  }
});

mMusicShowSubscription = musicShowsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<String>>() {

    @Override 
    public void onCompleted() { }

    @Override 
    public void onError(Throwable e) { }

    @Override 
    public void onNext(List<String> musicShows){
        listMusicShows(musicShows);
    }
});
  1. By specifiying (Schedulers.io()),RxAndroid will run getFavoriteMusicShows() on a different thread.

  2. By using AndroidSchedulers.mainThread() we want to observe this Observable on the UI thread, i.e. we want our onNext() callback to be called on the UI thread

查看更多
登录 后发表回答