【问题标题】:How can I fix 'android.os.NetworkOnMainThreadException'?如何修复“android.os.NetworkOnMainThreadException”?
【发布时间】:2021-10-19 19:38:00
【问题描述】:

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

我该如何解决这个问题?

【问题讨论】:

  • Read this blog post 在 NetworkOnMainThreadException 上了解更多信息。它解释了为什么会在 Android 3.0 及更高版本上发生这种情况。
  • 要进入正轨,首先阅读 android 中的网络请求,然后我建议学习“Volley”。
  • 有很多替代库可以解决这个问题。许多都列出了at the bottom of this page。如果你有更多,我们会带走它们:)
  • 您需要在独立于主 (UI) 线程的线程上运行互联网活动
  • "由于以前版本的 Android 中的一个错误,系统没有将写入主线程上的 TCP 套接字标记为违反严格模式。Android 7.0 修复了此错误。出现此问题的应用程序行为现在抛出一个 android.os.NetworkOnMainThreadException。” - 所以我们中的一些人直到最近才遇到这个! developer.android.com/about/versions/nougat/…

标签: java android android-networking networkonmainthread


【解决方案1】:

我们还可以使用RxJava 将网络操作移至后台线程。而且它也相当简单。

webService.doSomething(someData)
          .subscribeOn(Schedulers.newThread())-- This for background thread
          .observeOn(AndroidSchedulers.mainThread()) -- for callback on UI
          .subscribe(result -> resultText.setText("It worked!"),
              e -> handleError(e));

你可以用 RxJava 做更多的事情。以下是 RxJava 的一些链接。随意挖掘。

RxJava async task in Android

http://blog.stablekernel.com/replace-asynctask-asynctaskloader-rx-observable-rxjava-android-patterns/

【讨论】:

  • 第二个链接断开(404)。
  • 谢谢,@PeterMortensen 更新了文章的链接。
【解决方案2】:

切勿在 UI 线程上执行任何长时间运行的工作。长时间运行的工作可以是与服务器通信、读取/写入文件等。这些任务应该在后台线程上。这就是创建ServiceAsyncTaskThreads 的原因。您可以禁用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 Developers

【讨论】:

    【解决方案3】:

    您只需在文件 manifest.xml 中的清单标记之后添加以下行

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

    并在activity文件中,在绑定语句后添加如下代码:

    if (android.os.Build.VERSION.SDK_INT > 9) {
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
    }
    

    【讨论】:

    • 绕过 UI 线程上的网络代码检测确实是个糟糕的建议,它的存在是有原因的。
    • 不理解这个答案的仇恨,即使通常不是最佳的。很高兴有选择
    【解决方案4】:

    Android Jetpack引入了WorkManager,修复了Android 8.1(奥利奥)后台服务限制和Android 5.0(棒棒糖)下面使用AlarmManager和棒棒糖上面JobScheduler的问题。

    请使用WorkManager在后台线程运行任务,即使用户关闭应用程序也会继续运行。

    【讨论】:

    • 只有一个后台线程还是有几个(在这种情况下)?
    【解决方案5】:

    不同的选择:

    1. 使用普通的 Java 可运行线程来处理网络任务,并且可以使用 runOnUIThread() 来更新 UI

    2. intentservice/ async 任务可用于在获得网络响应后更新 UI 的情况

    【讨论】:

      【解决方案6】:

      截至 2018 年,我建议在 Kotlin 中使用 RxJava 进行网络获取。下面是一个简单的例子。

      Single.fromCallable {
              // Your Network Fetching Code
              Network.fetchHttp(url) 
          }
          .subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe {
              // What you need to do with your result on the view 
              result -> view.updateScreen(result) 
          }
      

      【讨论】:

      【解决方案7】:

      我将返回值的网络访问函数转换为挂起函数,如下所示:

      
      suspend fun isInternetReachable(): Boolean {
        ...
        ...
        return result
      }
      

      然后我修改了我使用该函数的位置以适应这个:

      ...
      Globalscope.async{
        ...
        result = isInternetReachable()
        ...
      }
      ...
      

      【讨论】:

        【解决方案8】:

        Android 不允许在主线程上运行长时间运行的操作。

        所以只需使用不同的线程,并在需要时将结果发布到主线程。

        new Thread(new Runnable() {
                @Override
                public void run() {
                    /*
                    // Run operation here
                    */
                    // After getting the result
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            // Post the result to the main thread
                        }
                    });
                }
            }).start();
        

        【讨论】:

          【解决方案9】:

          您实际上可以开始一个新线程。我之前也遇到过这个问题,就是这样解决的。

          【讨论】:

            【解决方案10】:

            Google 在 Android 11 中弃用了 Android AsyncTask API。

            即使你在 main Activity 之外创建了一个线程类,只要在 main 中调用它,你也会得到同样的错误。调用必须在可运行线程内,但如果您需要一些异步代码在后台执行或稍后在此处发布,您可以查看 Kotlin 和 Java 的一些替代方案:

            *https://stackoverflow.com/questions/58767733/android-asynctask-api-deprecating-in-android-11-what-are-the-alternatives*
            

            对我特别有用的是 mayank1513 对上述链接中可运行线程的Java 8 实现的回答。代码如下:

            new Thread(() -> {
                    // do background stuff here
                    runOnUiThread(()->{
                        // OnPostExecute stuff here
                    });
                }).start();
            

            但是,您可以先在代码的某些部分定义线程,然后在其他地方启动它,如下所示:

            线程定义

            Thread thread = new Thread(() -> {
                        // do background stuff here
                        runOnUiThread(()->{
                            // OnPostExecute stuff here
                        });
                    });
            

            线程调用

            thread.start();
            

            我希望这可以避免看到已弃用的 AsyncTask 的麻烦。

            【讨论】:

              【解决方案11】:

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

              例如,您可以找到在Google Drive API sample 中使用任务 的示例。在这种情况下应该使用相同的方法。我将使用 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 子句将在您的异步任务完成后执行,您将可以通过 it.result 访问任务返回的值。

              【讨论】:

                【解决方案12】:

                你可以使用 Kotlin coroutines:

                 class YoutActivity : AppCompatActivity, CoroutineScope {
                      
                      override fun onCreate(...) {
                         launch {  yourHeavyMethod() }
                      }
                
                      suspend fun yourHeavyMethod() {
                         with(Dispatchers.IO){ yourNetworkCall() }
                         ...
                         ...
                      }
                 } 
                

                你可以关注this guide

                【讨论】:

                  【解决方案13】:

                  如果您在 Kotlin 和 Anko 工作,您可以添加:

                  doAsync {
                      method()
                  }
                  

                  【讨论】:

                  【解决方案14】:

                  来自developer-android

                  理想情况下,AsyncTasks 应该用于短时间的操作(最多几秒钟。)

                  使用newCachedThreadPool 是一个不错的选择。您也可以考虑其他选项,例如newSingleThreadExecutornewFixedThreadPool

                      ExecutorService myExecutor = Executors.newCachedThreadPool();
                      myExecutor.execute(new Runnable() {
                          @Override
                          public void run() {
                              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);
                          }
                      });
                  

                  ThreadPoolExecutor 是一个帮助类来简化这个过程。这 类管理一组线程的创建,设置它们的 优先级,并管理工作在这些线程之间的分配方式。 随着工作量的增加或减少,类旋转或破坏 更多线程以适应工作负载。

                  有关 Android 线程的更多信息,请参阅this

                  【讨论】:

                    【解决方案15】:

                    我遇到了类似的问题。我刚刚在您的活动的 oncreate 方法中使用了以下内容。

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

                    而且效果很好。

                    需要注意的是,将其用于超过 100 毫秒的网络请求会导致明显的 UI 冻结和潜在的 ANR(应用程序无响应),因此请记住这一点。

                    【讨论】:

                    • @RichardKamere 从StrictMode 的官方文档中,您可以检查“StrictMode 是一种开发人员工具,它可以检测您可能不小心做的事情并引起您的注意,以便您可以修复它们”和“您永远不应该在 Google Play 上分发的应用程序中启用 StrictMode。” Check Here
                    • 这将有助于测试目的,但不能被视为解决方案,请改用 asynctask。
                    • @Zun 如果我想在 API 服务器上检查互联网连接怎么办?
                    【解决方案16】:

                    科特林

                    如果您使用的是 Kotlin,则可以使用 coroutine

                    fun doSomeNetworkStuff() {
                        GlobalScope.launch(Dispatchers.IO) {
                            // ...
                        }
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      在 Android 上,网络操作不能在主线程上运行。您可以使用ThreadAsyncTask(短期任务)、Service(长期任务)进行网络操作。当应用程序尝试在其主线程上执行网络操作时,会抛出 android.os.NetworkOnMainThreadException。如果您的任务耗时超过 5 秒,则需要强制关闭。

                      AsyncTask中运行你的代码:

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

                      或者

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

                      不建议这样做。

                      但出于调试的目的,您也可以使用以下代码禁用严格模式:

                      if (android.os.Build.VERSION.SDK_INT > 9) {
                          StrictMode.ThreadPolicy policy =
                              new StrictMode.ThreadPolicy.Builder().permitAll().build();
                          StrictMode.setThreadPolicy(policy);
                      }
                      

                      【讨论】:

                        【解决方案18】:

                        您可以使用KotlinAnko

                        KotlinAndroid 的新官方语言。你可以在这里找到更多关于它的信息: Kotlin for Android.

                        Anko 是 Android 中支持的 Kotlin 库。一些文档位于the GitHub page

                        真正有用的解决方案,只有@AntonioLeiva 编写的几行代码: Using Anko to run background tasks with Kotlin in Android (KAD 09).

                        doAsync {
                            var result = runLongTask()
                            uiThread {
                                toast(result)
                            }
                        }
                        

                        虽然很简单,NetworkOnMainThread 在您在 UI 线程上运行后台作业时发生,所以您必须做的一件事就是在后台运行您的 longTask 作业。您可以在您的 Android 应用中使用此方法和 KotlinAnko 来完成此操作。

                        【讨论】:

                        • 来自 GitHub 页面:“Anko 已弃用。有关详细信息,请参阅 this page。”
                        【解决方案19】:

                        这行得通。我只是让Dr.Luiji's answer 简单一点。

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

                        【讨论】:

                          【解决方案20】:

                          还有另一种非常方便的方法可以解决此问题 - 使用 RxJava's 并发功能。您可以在后台执行任何任务并将结果以非常方便的方式发布到主线程,因此这些结果将交给处理链。

                          第一个经过验证的答案建议是使用 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() 调度程序的支持。

                          an excellent e-book for RxJava

                          【讨论】:

                            【解决方案21】:

                            RxAndroid 是解决这个问题的另一个更好的选择,它让我们免于创建线程然后在 Android UI 线程上发布结果的麻烦。

                            我们只需要指定需要在哪些线程上执行任务,一切都在内部处理。

                            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. 通过指定(Schedulers.io()),RxAndroid 将在不同的线程上运行getFavoriteMusicShows()

                            2. 通过使用AndroidSchedulers.mainThread(),我们希望在 UI 线程上观察这个 Observable,即,我们希望在 UI 线程上调用我们的 onNext() 回调。

                            【讨论】:

                              【解决方案22】:

                              您可以将部分代码移动到另一个线程以卸载main thread 并避免获得ANRNetworkOnMainThreadExceptionIllegalStateException(例如,无法访问主线程上的数据库,因为它可能潜在地长时间锁定 UI)。

                              你应该根据情况选择一些方法

                              Java Thread 或 Android HandlerThread

                              Java 线程只能一次性使用,并在执行其 run 方法后终止。

                              HandlerThread 是一个方便的类,用于启动具有循环器的新线程。

                              AsyncTask(在 API 级别 30 中已弃用

                              AsyncTask 被设计为围绕 ThreadHandler 的辅助类,并不构成通用线程框架。 AsyncTasks 最适合用于短时间的操作(最多几秒钟)。如果您需要保持线程长时间运行,强烈建议您使用 java.util.concurrent 包提供的各种 API,例如ExecutorThreadPoolExecutorFutureTask

                              由于main线程独占了UI组件,无法访问某些View,这就是Handler来救援的原因

                              [Executor framework]

                              实现 ExecutorService 的 ThreadPoolExecutor 类可以对线程池进行精细控制(例如,核心池大小、最大池大小、保持活动时间等)

                              ScheduledThreadPoolExecutor - 一个扩展 ThreadPoolExecutor 的类。它可以在给定的延迟后或定期安排任务。

                              FutureTask

                              FutureTask 执行异步处理,但是如果结果尚未准备好或处理未完成,调用 get() 将阻塞线程

                              AsyncTaskLoaders

                              AsyncTaskLoaders 因为它们解决了 AsyncTask 固有的许多问题

                              IntentService

                              这是在 Android 上长时间运行处理的实际选择,一个很好的例子是上传或下载大文件。即使用户退出应用程序,上传和下载仍可能继续,您当然不希望在这些任务进行时阻止用户使用应用程序。

                              JobScheduler

                              实际上,您必须创建一个服务并使用 JobInfo.Builder 创建一个作业,它指定您何时运行该服务的标准。

                              RxJava

                              使用可观察序列编写异步和基于事件的程序的库。

                              Coroutines (Kotlin)

                              它的主要要点是,它使异步代码看起来很像同步

                              阅读更多herehereherehere

                              【讨论】:

                                【解决方案23】:

                                简单来说,

                                不要在 UI 线程中做网络工作

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

                                解决方案:

                                1. 您必须创建一个新线程
                                2. 使用AsyncTask class

                                方式:

                                把你所有的作品都放进去

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

                                但是:

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

                                如果你不这样做,你会得到ViewRootImpl$CalledFromWrongThreadException

                                操作方法

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

                                【讨论】:

                                  【解决方案24】:

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

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

                                  REST 通过 HTTP

                                  通常是 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)
                                  

                                  从 Square 改造

                                  对于具有多个端点的 API,这是一个很好的选择,它允许您声明 REST 端点,而不必像 Amazon Ion JavaVolley 等其他库那样单独编码它们(网站:Retrofit )。

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

                                  文件 build.gradle

                                  将这些行添加到您的 module 级别的 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
                                  

                                  文件 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 sn-p

                                  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 可以轻松完成(请参阅this awesome answerAdd Header Parameter in Retrofit 了解详细信息)。

                                  一次性 REST API 访问

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

                                  离子

                                  对于此类访问来说,这是一个很棒的库。

                                  请阅读msysmilu's great answerHow can I fix 'android.os.NetworkOnMainThreadException'?

                                  通过HTTP加载图片

                                  凌空

                                  Volley 也可用于 REST API,但由于需要更复杂的设置,我更喜欢使用上述 Square 的 Retrofit

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

                                  文件 build.gradle

                                  将此行添加到您的 module 级别的 build.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());
                                  

                                  毕加索

                                  Picasso 是 Square 的另一个优秀库。请参阅该网站以获取一些很好的示例。

                                  【讨论】:

                                    【解决方案25】:

                                    在另一个线程上执行网络操作。

                                    例如:

                                    new Thread(new Runnable(){
                                        @Override
                                        public void run() {
                                            // Do network action in this function
                                        }
                                    }).start();
                                    

                                    并将其添加到文件 AndroidManifest.xml

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

                                    【讨论】:

                                      【解决方案26】:

                                      这个问题有两种解决方案。

                                      1. 不要在主 UI 线程中使用网络调用。为此使用异步任务。

                                      2. setContentView(R.layout.activity_main); 之后将以下代码写入您的 MainActivity 文件:

                                        if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy 策略 = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); }

                                      下面的 import 语句到你的 Java 文件中。

                                      import android.os.StrictMode;
                                      

                                      【讨论】:

                                      • 遵循第二个解决方案是一种不好的做法。异步是正确的方法。如果您更改政策,您就像隐藏您的问题!
                                      【解决方案27】:

                                      公认的答案有一些明显的缺点。除非您真的知道自己在做什么,否则不建议使用 AsyncTask 进行联网。一些缺点包括:

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

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

                                      1. 使用可以很好地为您完成此工作的库 - this question 中有一个很好的网络库比较,或者
                                      2. 使用 ServiceIntentService 代替,也许使用 PendingIntent 通过 Activity 的 onActivityResult 方法返回结果。

                                      IntentService 方法

                                      缺点:

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

                                      优点:

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

                                      实施总结

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

                                      第 1 步:创建一个 IntentService 以执行下载。您可以通过Intent extras 告诉它要下载什么,并将其传递给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 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 步:从 Activity 调用服务,传递一个 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);
                                      

                                      第四步:在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 项目可用here

                                      【讨论】:

                                        【解决方案28】:

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

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

                                        添加:

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

                                        在你的课堂上,

                                        在 Android manifest.xml 文件中添加这个权限:

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

                                        后果:

                                        您的应用将(在互联网连接不稳定的区域)变得无响应并被锁定,用户认为速度很慢并且必须强制终止,并且您冒着活动管理器终止您的应用并告诉用户应用已停止的风险.

                                        Android 有一些关于响应性设计的良好编程实践的好技巧: NetworkOnMainThreadException | Android Developers

                                        【讨论】:

                                        • 哇,谢谢你的解释,我现在明白了。我看到了一个应用程序,它已经在它的 java 类中实现了 ThreadPolicy,我有点困惑它在做什么。当网络不足时,我看到了你所说的后果。
                                        【解决方案29】:

                                        注意:AsyncTask 在 API 级别 30 中已被弃用。
                                        AsyncTask | Android Developers

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

                                        【讨论】:

                                        • 所以这个在主线程上运行的网络操作只会在android中出现问题,而不是在标准java代码中(用java编写的代码但不适用于android应用程序)。??
                                        • 这是一个很好的解决方案,它节省了我的时间!
                                        【解决方案30】:

                                        这是一个使用OkHttpFutureExecutorServiceCallable 的简单解决方案:

                                        final OkHttpClient httpClient = new OkHttpClient();
                                        final ExecutorService executor = newFixedThreadPool(3);
                                        
                                        final Request request = new Request.Builder().url("http://example.com").build();
                                        
                                        Response response = executor.submit(new Callable<Response>() {
                                           public Response call() throws IOException {
                                              return httpClient.newCall(request).execute();
                                           }
                                        }).get();
                                        

                                        【讨论】:

                                        • 出现错误:System.err: java.util.concurrent.ExecutionException: javax.net.ssl.SSLException: Unable to parse TLS packet header
                                        • @ArpanSaini 您的确切 URL 是否适用于 curl 或浏览器?也许从 https 切换到 http(反之亦然),看看它是否有效?
                                        猜你喜欢
                                        • 2013-11-06
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2014-01-08
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多