java - permission - networkonmainthreadexception httpurlconnection




如何修复android.os.NetworkOnMainThreadException? (20)

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

使用Service或AsyncTask

另请参阅Stack Overflow问题:

android.os.NetworkOnMainThreadException从Android发送电子邮件

我在为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

我该如何解决这个问题?


spektom的最佳答案是完美的。

如果您正在编写AsyncTask内联而不是作为类扩展,并且除此之外,如果需要从AsyncTask获取响应,可以使用如下的get()方法。

RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();

(从他的例子。)


您应该几乎总是在线程上运行网络操作或作为异步任务。

但是,如果您愿意接受后果, 可以删除此限制并覆盖默认行为。

加:

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

StrictMode.setThreadPolicy(policy); 

在你的班上,

在android manifest.xml文件中添加此权限:

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

后果:

您的应用程序将(在互联网连接不稳定的区域)变得无法响应并锁定,用户感知缓慢并且必须执行强制终止,并且您冒着活动管理器杀死您的应用并告诉用户该应用已停止的风险。

Android提供了一些关于良好编程实践的好技巧,以设计响应能力: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.htmlhttp://developer.android.com/reference/android/os/NetworkOnMainThreadException.html



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

我将介绍几个用于执行网络操作的用例和一个或两个解决方案。

通过HTTP重新安装

通常Json可以是XML或其他东西

完整的API访问权限

假设您正在编写一个应用程序,用户可以跟踪股票价格,利率和货币汇率。 您找到一个类似于以下内容的Json API:

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)

广场改造

对于具有多个端点的API而言,这是一个很好的选择,并允许您声明ReST端点,而不必像其他库(如ion或Volley)那样单独编写它们。 (网站: http://square.github.io/retrofit/http://square.github.io/retrofit/

你如何在财务API中使用它?

的build.gradle

将这些行添加到模块级别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片段

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密钥或其他标头(如用户令牌等),Retrofit可以轻松实现(请参阅此详细答案以获取详细信息: https://.com/a/42899766/1024412 : https://.com/a/42899766/1024412 )。

一次关闭ReST API访问

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

离子

对于这种类型的访问,这是一个很棒的库。

请阅读msysmilu的好答案(https://.com/a/28559884/1024412)

通过HTTP加载图像

齐射

Volley也可用于ReST API,但由于需要更复杂的设置,我更喜欢使用Square上的Retrofit(http://square.github.io/retrofit/

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

的build.gradle

将此行添加到模块级别buid.gradle:

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

ImageFetch.java

Volley需要比Retrofit更多的设置。您需要创建一个这样的类来设置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());

毕加索

Square的另一个优秀图书馆。请访问该网站以获取一些很好的例子:http://square.github.io/picasso/http://square.github.io/picasso/


只是明确说明一些事情:

主线程基本上是UI线程。

所以说你不能在主线程中进行网络操作意味着你不能在UI线程中进行网络操作,这意味着你不能在*runOnUiThread(new Runnable() { ... }*里面做一些其他线程的网络操作 ,或者。

(我只是想知道为什么我在主线程以外的其他地方得到了这个错误。我只是有一个长期头痛的时刻。这就是为什么;这个线程帮助了;希望这个评论会帮助其他人。)


基于网络的操作无法在主线程上运行。 您需要在子线程上运行所有基于网络的任务或实现AsyncTask。

这是您在子线程中运行任务的方式:

new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            // Your implementation goes here
        } 
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}).start();

如果执行任务花费太多时间,则会在主线程上执行任何繁重的任务,从而发生此异常。

为避免这种情况,我们可以使用线程执行器来处理它

Executors.newSingleThreadExecutor().submit(new Runnable() {
    @Override
    public void run() {
        // You can perform your task here.
    }
});

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

您不应该在主线程(UI线程)上执行任何耗时的任务,例如任何网络操作,文件I / O或SQLite数据库操作。 因此,对于这种操作,您应该创建一个工作线程,但问题是您无法直接从工作线程执行任何与UI相关的操作。 为此,您必须使用Handler并传递Message

为了简化所有这些,Android提供了各种方法,如AsyncTaskAsyncTaskLoaderCursorLoaderIntentService 。 因此,您可以根据您的要求使用其中任何一种。


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

Android开发者网站上有一篇关于无痛线程的文章,这是对此的一个很好的介绍,它将为您提供比这里实际提供的更好的答案深度。


我使用新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方法并提供替代执行程序)。 当在Gingerbread上同时执行时,在ICS上串行运行时运行正常的代码可能会中断,例如,如果您有无意的执行顺序依赖项。

如果您想避免短期内存泄漏,在所有平台上都有明确定义的执行特性,并且有基础来构建非常强大的网络处理,您可能需要考虑:

  1. 使用一个能为你做得很好的库 - 在这个问题中对网络库进行了很好的比较,或者
  2. 使用ServiceIntentService ,可能使用PendingIntent通过Activity的onActivityResult方法返回结果。

IntentService方法

下行方面:

  • AsyncTask更多的代码和复杂性,虽然没有你想象的那么多
  • 将队列请求排队并在单个后台线程上运行它们。 您可以通过将IntentService替换为等效的Service实现(可能就像这个)来轻松控制它。
  • 嗯,实际上我现在想不出任何其他人

向上方面:

  • 避免短期内存泄漏问题
  • 如果您的活动在网络操作正在进行时重新启动,它仍然可以通过其onActivityResult方法接收下载结果
  • 比AsyncTask更好的平台来构建和重用强大的网络代码。 示例:如果您需要执行重要上传,可以从Activity AsyncTask执行此AsyncTask ,但如果用户上下文切换出应用程序以接听电话,则系统可能会在上载完成之前终止应用程序。 使用活动Service终止应用程序的可能性较小
  • 如果您使用自己的并发版本的IntentService (就像我上面链接的那样),您可以通过Executor控制并发级别。

实施摘要

您可以非常轻松地实现IntentService以在单个后台线程上执行下载。

步骤1:创建IntentService以执行下载。 你可以告诉它通过Intent extra's下载什么,并传递一个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对象,Service将使用该对象返回结果:

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-Studio / gradle项目的github项目。


简单来说,

不要在UI线程中进行网络工作

例如,如果您执行HTTP请求,那就是网络操作。

解:

  1. 你必须创建一个新的线程
  2. 或者使用AsyncTask类

办法:

把你所有的作品都放进去

  1. 新线程的run()方法
  2. AsyncTask类的doInBackground()方法。

但:

当您从Network响应中获取某些内容并希望在视图中显示它时(如在TextView中显示响应消息),您需要返回UI线程。

如果你不这样做,你将获得ViewRootImpl$CalledFromWrongThreadException

如何?

  1. 使用AsyncTask时,从onPostExecute()方法更新视图
  2. 或者run()方法中调用runOnUiThread()方法并更新视图。

这很有效。 刚刚将Dr.Luiji的答案变得更简单一些。

new Thread() {
    @Override
    public void run() {
        try {
            //Your code goes here
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}.start();

这种情况发生在Android 3.0及更高版本中。 从Android 3.0及更高版本开始,他们限制使用网络操作(访问Internet的功能)在主线程/ UI线程中运行(从创建活动产生的内容和活动中的恢复方法)。

这是为了鼓励使用单独的线程进行网络操作。 有关如何以正确方式执行网络活动的更多详细信息,请参阅AsyncTask


在Android上,网络操作无法在主线程上运行。您可以使用Thread,AsyncTask(短期运行任务),Service(长时间运行的任务)来执行网络操作。


已经解释了新的ThreadAsynTask解决方案。

AsyncTask理想情况下应该用于短期操作。普通Thread不适合Android。

使用HandlerThreadHandler查看备用解决方案Handler

HandlerThread

用于启动具有looper的新线程的方便类。然后可以使用looper来创建处理程序类。请注意,start()仍然必须调用。

处理器:

Handler允许您发送和处理与线程的MessageQueue关联的Message和Runnable对象。每个Handler实例都与一个线程和该线程的消息队列相关联。当您创建一个新的Handler时,它被绑定到正在创建它的线程的线程/消息队列 - 从那时起,它将消息和runnables传递给该消息队列并在消息出来时执行它们队列。

解:

  1. 创建 HandlerThread

  2. 呼叫start()HandlerThread

  3. Handler通过获取Looper来创建HanlerThread

  4. 将与网络操作相关的代码嵌入到Runnable对象中

  5. 提交Runnable任务Handler

示例代码段,其中包含地址 NetworkOnMainThreadException

HandlerThread handlerThread = new HandlerThread("URLConnection");
handlerThread.start();
handler mainHandler = new Handler(handlerThread.getLooper());

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            Log.d("Ravi", "Before IO call");
            URL page = new URL("http://www.google.com");
            StringBuffer text = new StringBuffer();
            HttpURLConnection conn = (HttpURLConnection) page.openConnection();
            conn.connect();
            InputStreamReader in = new InputStreamReader((InputStream) conn.getContent());
            BufferedReader buff = new BufferedReader(in);
            String line;
            while ( (line =  buff.readLine()) != null) {
                text.append(line + "\n");
            }
            Log.d("Ravi", "After IO call");
            Log.d("Ravi",text.toString());

        }catch( Exception err){
            err.printStackTrace();
        }
    }
};
mainHandler.post(myRunnable);

使用这种方法的优点:

  1. Thread/AsyncTask为每个网络操作创建新的是昂贵的。在Thread/AsyncTask将被销毁且下一次网络运营重新创建。但随着HandlerHandlerThread方法,您可以提交许多网络操作(如Runnable的任务),以单HandlerThreadHandler

虽然上面有一个巨大的解决方案池,但没有人提到com.koushikdutta.ionhttps://github.com/koush/ionhttps://github.com/koush/ion

它也是异步的,使用起来非常简单

Ion.with(context)
.load("http://example.com/thing.json")
.asJsonObject()
.setCallback(new FutureCallback<JsonObject>() {
   @Override
    public void onCompleted(Exception e, JsonObject result) {
        // do stuff with the result or error
    }
});

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

rx-java一本很棒的电子书







thread-exceptions