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

如何解决此问题?


当前回答

我也有类似的问题。我刚才在活动的oncreate方法中使用了以下内容。

// Allow strict mode
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);

而且效果很好。

需要注意的是,对超过100毫秒的网络请求使用此选项将导致明显的UI冻结,并可能导致ANR(应用程序未响应),因此请记住这一点。

其他回答

使用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)

如何修复android.os.NetworkOnMainThreadException

什么是NetworkOnMainThreadException:

在Android中,我们必须在UI线程(主线程)上执行所有UI操作。如果我们在主线程上执行后台操作或某些网络操作,那么我们可能会发生此异常,应用程序将不会响应。

如何修复:

为了避免这个问题,您必须使用另一个线程进行后台操作或网络操作,例如使用asyncTask,并使用一些库进行网络操作,如Volley、AsyncHttp等。

关于这个问题,已经有很多很好的答案,但自从这些答案发布以来,已经有了很多很棒的图书馆。这是一种新手指南。

我将介绍几个用于执行网络操作的用例,并为每个用例提供一两个解决方案。

HTTP上的REST

通常是JSON,但也可以是XML或其他格式。

完全API访问

假设你正在编写一个应用程序,让用户跟踪股价、利率和货币汇率。您可以找到一个JSON API,它看起来像这样:

http://api.example.com/stocks                       // ResponseWrapper<String> object containing a
                                                    // list of strings 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)

从广场改装

对于具有多个端点的API来说,这是一个很好的选择,它允许您声明REST端点,而不必像其他库(如AmazonIonJava或Volley(网站:改装))那样单独对它们进行编码。

如何将其与财务API一起使用?

文件build.gradle

将这些行添加到模块级build.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

文件Financeapi.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);
}

类财务PiBuilder

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

类FinanceFragment代码段

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

如果您的API需要发送API密钥或其他标头(如用户令牌等),则“改装”会使此操作变得简单(有关详细信息,请参阅“改装”中添加标头参数的精彩答案)。

一次性REST API访问

假设你正在构建一个“情绪天气”应用程序,它可以查找用户的GPS位置,并检查该区域的当前温度,然后告诉他们情绪。这种类型的应用程序不需要声明API端点;它只需要能够访问一个API端点。

Ion

这是一个非常适合这种访问的库。

请阅读msysmilu对如何修复“android.os.NetworkOnMainThreadException”的精彩回答?。

通过HTTP加载图像

截击

Volley也可以用于RESTAPI,但由于需要更复杂的设置,我更喜欢使用如上所述的来自Square的改装。

假设您正在构建一个社交网络应用程序,并希望加载朋友的个人资料图片。

文件build.gradle

将此行添加到模块级build.gradle文件中:

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

文件ImageFetch.java

Volley需要比改装更多的设置。您需要创建一个这样的类来设置RequestQueue、ImageLoader和ImageCache,但这并不太糟糕:

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

将以下内容添加到布局XML文件中以添加图像:

<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

将以下代码添加到onCreate方法(Fragment,Activity)或构造函数(Dialog):

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

毕加索

毕加索是另一个来自广场的优秀图书馆。请查看网站以了解一些很棒的示例。

这些答案需要更新,以使用更现代的方式连接到Internet上的服务器,并处理一般的异步任务。

例如,您可以在GoogleDriveAPI示例中找到使用Tasks的示例。在这种情况下也应使用相同的方法。我将使用OP的原始代码来演示这种方法。

首先,您需要定义一个非主线程执行器,并且只需要执行一次:

private val mExecutor: Executor = Executors.newSingleThreadExecutor()

然后在该执行器中处理逻辑,该执行器将在主线程之外运行

Tasks.call (mExecutor, Callable<String> {

        val url = URL(urlToRssFeed)
        val factory = SAXParserFactory.newInstance()
        val parser = factory.newSAXParser()
        val xmlreader = parser.getXMLReader()
        val theRSSHandler = RssHandler()
        xmlreader.setContentHandler(theRSSHandler)
        val is = InputSource(url.openStream())
        xmlreader.parse(is)
        theRSSHandler.getFeed()

        // Complete processing and return a String or other object.
        // E.g., you could return Boolean indicating a success or failure.
        return@Callable someResult
}).continueWith{
    // it.result here is what your asynchronous task has returned
    processResult(it.result)
}

continueWith子句将在异步任务完成后执行,您将有权访问任务通过其.result返回的值。

在主线程上执行网络操作时,将引发android.os.NetworkOnMainThreadException。您最好在AsyncTask中执行此操作以删除此异常。这样写:

    new AsyncTask<Void,String,String>(){

        @Override
        protected Void doInBackground(Void... params) {
            // Perform your network operation.
            // Get JSON or XML string from the server.
            // Store in a local variable (say response) and return.
            return response;
        }

        protected void onPostExecute(String results){
            // Response returned by doInBackGround() will be received
            // by onPostExecute(String results).
            // Now manipulate your jason/xml String(results).
        }

    }.execute();
}