如何修复'android.os.NetworkOnMainThreadException'?


2391

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

如何解决此问题?


131
阅读有关NetworkOnMainThreadException的博客文章,了解更多信息。它说明了为什么在Android 3.0及更高版本上会发生这种情况。
Adrian Monk

6
要在仪式上先阅读有关android中的网络请求的信息,然后我建议学习“排球”。
2014年

3
有许多替代库可以解决此问题。许多内容在此页面的底部列出。如果您有更多,我们采取他们:)
Snicolas 2014年

您需要在与主(UI)线程不同的线程上运行Internet活动
Naveed Ahmad 2014年

“由于之前版本的Android中存在错误,因此系统未将写入主线程上的TCP套接字标记为严格模式违规。Android7.0修复了该错误。现在,表现出这种行为的应用会抛出android.os。 NetworkOnMainThreadException。” -所以我们中的一些人直到最近才实现这一目标! developer.android.com/about/versions/nougat/…–
Jay

Answers:


2547

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

37
我认为在这里值得注意的是,上面的代码片段应该是一个子类(内部类),最好是私有的。这样,当AsyncTask完成时,您仍然可以操纵类的内部。
dyslexicanaboko 2012年

4
实际上,我做过与您上述提到的相同的事情,但是我面临着此错误 java.lang.RuntimeException:无法在未调用Looper.prepare()的线程内创建处理程序
Dhruv Tyagi

68
这是完全错误的答案。我一直在人们的代码中遇到这个问题,并且很讨厌必须始终对其进行修复。AsyncTask不应用于网络活动,因为它与活动相关,但与活动生命周期无关。在运行此任务的情况下旋转设备会导致异常,并使您的应用程序崩溃。使用IntentService将数据拖放到sqlite数据库中。
Brill Pappin

5
注意,AsyncTask通常用于不宜使用的按活动网络操作。其生命周期与活动不同步。为了获取数据,您应该使用IntentService和视图后面的数据库。
Brill Pappin

1
@ BrillPappin,FWIW,此Android开发人员指南使用AsyncTask,并负责配置更改。
HeyJude

676

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

但它可以取消这一限制,并覆盖了默认的行为,如果你愿意接受的后果。

加:

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

StrictMode.setThreadPolicy(policy); 

在你班上

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

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

后果:

您的应用程序(在Internet连接不正常的区域)将变得无响应并被锁定,用户会感觉很慢,必须强行杀死它,并且冒险活动管理器会杀死您的应用程序并告诉用户该应用程序已停止。

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


455
这是一个非常糟糕的主意。解决方案是避免在主线程上使用网络IO(如公认的答案所示)。
MByD 2012年

74
这样,您只能隐藏真正的问题。
亚历克斯

28
@TwistedUmbrella AsyncTask不会添加代码页面,而是添加6行(类声明,覆盖注释,doInBackground声明,2个右括号和对的调用execute())。另一方面,即使从您提到的站点进行单个提取,也会在UI响应性方面带来很大的滞后。别偷懒
佐尔坦

19
我认为这是一个完美的解决方案,如果您只想运行一个示例代码块以查看在实现适当的AsyncTask之前是否有工作。这就是为什么我赞成这个答案,尽管正如所有其他人所说的那样,这不应该在生产应用程序中完成,而只是作为开发测试的快速解决方案。
hooked82

94
已投票。这个答案是正确的,对于许多既不幼稚又不愚蠢,但只需要SYNCHRONOUS(即,这必须阻止应用程序)调用的程序员而言,这正是需要的。我很高兴Android默认情况下会抛出异常(恕我直言,这是一件非常“有用的事情!”)-但是我同样高兴地说“谢谢,但是-这实际上是我想要的”并且可以覆盖它。
亚当

427

我用new解决了这个问题Thread

Thread thread = new Thread(new Runnable() {

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

thread.start(); 

7
您也可以使用单线程执行程序服务,而不是每次要执行网络操作时都创建一个新线程。
Alex Lockwood

66
简单但危险。匿名Runnable隐含了对封闭类的引用(例如,您的Activity或Fragment),以防止在线程完成之前对其进行垃圾回收。您至少应将优先级设置为Process.BACKGROUND,否则此线程将以与主/ ui线程相同的优先级运行,并与生命周期方法和ui帧速率竞争(请注意编排器日志中的警告)。
Stevie 2014年

1
@Stevie如何设置优先级?既不runnble也不ExecutorService的有这样的设置方法
JK

1
@JK为您的ExecutorService提供自定义的ThreadFactory,并在返回线程之前在线程上调用Thread.setPriority。
Stevie 2015年

1
“不建议直接在Android中使用线程。它导致的问题多于其解决的问题。”在此详细说明吗?实际上,AsyncTask正是为此而被弃用... techyourchance.com/asynctask-deprecated
Fran Marzoa

169

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

  • 创建为非静态内部类的AsyncTask对包含的Activity对象,其上下文以及该活动创建的整个View层次结构都有隐式引用。此参考可防止在AsyncTask的后台工作完成之前对Activity进行垃圾回收。如果用户的连接速度慢和/或下载量大,则这些短期内存泄漏可能会成为问题-例如,如果方向发生多次更改(并且您不取消正在执行的任务),或者用户离开活动。
  • AsyncTask根据执行平台的不同而具有不同的执行特征:在API级别4之前,AsyncTasks在单个后台线程上串行执行;从API级别4到API级别10,AsyncTasks在最多128个线程的池中执行;从API级别11开始,AsyncTask在单个后台线程上串行执行(除非您使用重载的executeOnExecutor方法并提供替代的执行程序)。在ICS上串行运行时运行良好的代码在Gingerbread上同时执行时可能会中断,例如,如果您无意间执行了顺序。

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

  1. 使用一个可以为您做得很好的库- 这个问题中网络库的比较不错,或者
  2. 使用ServiceIntentService代替,也许PendingIntent用来通过Activity的onActivityResult方法返回结果。

IntentService方法

缺点:

  • AsyncTask您想象的要多的代码和复杂性
  • 将请求排队并在单个后台线程上运行它们。您可以通过替换IntentService为等效的Service实现(例如与实现)轻松地控制它。
  • 嗯,我现在真的想不起其他人了

优点:

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

实施摘要

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

第1步:创建一个IntentService以执行下载。您可以通过Intentextra 告诉它要下载的内容,然后将其传递给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对象,服务将使用该对象返回结果:

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的工作室/摇篮项目可用一个Github的项目在这里


IntentService是执行此操作的正确方法,而不是连根拔起,因为AsyncTask正是不执行此操作的方法。
Brill Pappin

3
@BrillPappin我几乎完全同意并重新措词以强调AsyncTask的缺点。(我仍然认为,在极少数情况下-如果您真的知道自己在做什么- 可以使用AsyncTask,但是可接受的答案并没有指出任何缺点,并且对所有人来说太受欢迎了Android)。
Stevie

1
您真的需要IllustrativeRSS吗?如果您不使用RSS内容怎么办?
Cullub

我认为Google应该改变垃圾收集的错误实现方式,而不是将berdon置于程序员一边。这是操作系统的责任。如果程序员由于Android实现中的基本问题而使用IntentService或Service来完成这项工作,那么几年后Google会说IntentService也是不好的做法,并提出其他建议。这个故事还在继续...因此Google开发人员应该解决Android错误的内存管理,而不是程序员。
saeed khalafinejad

我只是要说一句:这个答案似乎是比更好的替代方案更多的共享项目的方式AsyncTask。该错误旨在阻止开发人员落后于UI,并不一定会诱使他们冒着一系列意图/服务冒险的风险。
被遗弃的购物车,

144

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

Android开发人员网站上有一篇有关无痛线程的文章,对此进行了很好的介绍,它可以为您提供比此处实际提供的更好的答案深度。


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

使用服务或AsyncTask

另请参阅堆栈溢出问题:

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


8
也许值得强调一下,如果您使用服务,您仍然需要创建一个单独的线程-服务回调在主线程上运行。另一方面,IntentService在后台线程上运行其onHandleIntent方法。
Stevie 2014年

您不应该将AsyncTask用于长时间运行的操作!指南中最多指定2到3秒。
Dage

76

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

例如:

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

并将其添加到AndroidManifest.xml

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

5
但是,我们如何找出线程何时完成此操作,以便可以在UI线程中执行下一组任务?AsyncTask提供了执行此操作的工具。有没有一种方法可以使用可运行线程?
Piyush Soni 2014年

3
它将逐步处理您的代码,因此在代码结束时,您需要使用处理程序返回UI线程
henry4343 2014年

1
您可以使用异步任务或意图服务,因为它在工作线程上执行。
Chetan Chaudhari

62

您可以使用以下代码禁用严格模式:

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

不建议这样做:使用AsyncTask界面。

两种方法的完整代码


2
是的,将会出现ANR错误。表示应用程序在5秒钟内没有响应。
穆罕默德·穆巴希尔

11
这是一个非常糟糕的答案。您不应更改线程的策略,而应编写更好的代码:不要在主线程上进行网络操作!
shkschneider

@Sandeep您和其他观众也应该阅读此内容。stackoverflow.com/a/18335031/3470479
Prakhar1001 2016年

54

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

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

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

匿名Runnable并不是最好的方法,因为它隐含了对封闭类的引用,并阻止了该类在线程完成之前被GC编译!同样,该线程将以与主线程/美国线程相同的优先级运行,与生命周期方法和UI帧速率竞争!
Yousha Aleayoub '16

49

将您的代码放入其中:

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

要么:

class DemoTask extends AsyncTask<Void, Void, Void> {

    protected Void doInBackground(Void... arg0) {
        //Your implementation
    }

    protected void onPostExecute(Void result) {
        // TODO: do something with the feed
    }
}

第二个会比第一最佳API超过11以上
罗希特夏尔哥斯瓦米

46

这发生在Android 3.0及更高版本中。在Android 3.0及更高版本中,它们已限制使用网络操作(访问Internet的功能)在主线程/ UI线程中运行(活动中的create方法和resume方法所产生的结果)。

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


46

您可以选择使用Android注释。它将允许您在后台线程中简单地运行任何方法:

// normal method
private void normal() {
    doSomething(); // do something in background
}

@Background
protected void doSomething() 
    // run your networking code here
}

注意,尽管它提供了简单性和可读性的好处,但是它也有缺点。


6
@Gavriel它会为您注释的所有内容创建重复项,无论是方法,活动,片段,单例等,因此代码的数量是其两倍,并且编译时间更长。由于库中的错误,它可能还会出现一些问题。调试和发现错误将变得更加困难。
Oleksiy 2015年

43

该错误是由于在主线程中执行了长时间运行的操作引起的,您可以使用AsynTaskThread轻松纠正该问题。您可以检出此库AsyncHTTPClient以获得更好的处理。

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

42

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

为了简化这些东西,Android提供各种方式,如AsyncTaskAsyncTaskLoaderCursorLoaderIntentService。因此,您可以根据自己的要求使用任何一种。


40

Spektom的最佳答案完美。

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

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

(从他的例子。)


5
使用get()是一个坏主意...它使AsyncTask再次“同步”
Selvin 2013年

有更好的其他方法吗?@Selvin
sivag1 2013年

2
我认为您可以向主线程发送有关结果的信息。例如,向包含结果的主线程发送广播。
Chine Gary


28

对我来说是这样的:

<uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="10" />

我测试我的应用程序的设备是4.1.2,即SDK版本16!

确保目标版本与您的Android目标库相同。如果不确定目标库是什么,请右键单击Project-> Build Path- > Android,然后将其选中。

另外,正如其他人提到的那样,请包括访问Internet的正确权限:

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

11
让我向您解释您在这里做什么:NetworkOnMainThreadException是监护人在告诉您:不要用自己的脚开枪...您的解决方法是:让我们回到没有监护人的过去-现在我可以向我射击自由行走
Selvin 2013年

1
我也采用这种方法,没有任何问题。监护人有时太挑剔。
FractalBob

25

在您的活动中使用它

    btnsub.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub

                    //Initialize soap request + add parameters
                    SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME1);

                    //Use this to add parameters
                    request.addProperty("pincode", txtpincode.getText().toString());
                    request.addProperty("bg", bloodgroup.getSelectedItem().toString());

                    //Declare the version of the SOAP request
                    SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);

                    envelope.setOutputSoapObject(request);
                    envelope.dotNet = true;

                    try {
                        HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

                        //this is the actual part that will call the webservice
                        androidHttpTransport.call(SOAP_ACTION1, envelope);

                        // Get the SoapResult from the envelope body.
                        SoapObject result = (SoapObject) envelope.getResponse();
                        Log.e("result data", "data" + result);
                        SoapObject root = (SoapObject) result.getProperty(0);
                        // SoapObject s_deals = (SoapObject) root.getProperty(0);
                        // SoapObject s_deals_1 = (SoapObject) s_deals.getProperty(0);
                        //

                        System.out.println("********Count : " + root.getPropertyCount());

                        value = new ArrayList<Detailinfo>();

                        for (int i = 0; i < root.getPropertyCount(); i++) {
                            SoapObject s_deals = (SoapObject) root.getProperty(i);
                            Detailinfo info = new Detailinfo();

                            info.setFirstName(s_deals.getProperty("Firstname").toString());
                            info.setLastName(s_deals.getProperty("Lastname").toString());
                            info.setDOB(s_deals.getProperty("DOB").toString());
                            info.setGender(s_deals.getProperty("Gender").toString());
                            info.setAddress(s_deals.getProperty("Address").toString());
                            info.setCity(s_deals.getProperty("City").toString());
                            info.setState(s_deals.getProperty("State").toString());
                            info.setPinecode(s_deals.getProperty("Pinecode").toString());
                            info.setMobile(s_deals.getProperty("Mobile").toString());
                            info.setEmail(s_deals.getProperty("Email").toString());
                            info.setBloodgroup(s_deals.getProperty("Bloodgroup").toString());
                            info.setAdddate(s_deals.getProperty("Adddate").toString());
                            info.setWaight(s_deals.getProperty("waight").toString());
                            value.add(info);
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    Intent intent = new Intent(getApplicationContext(), ComposeMail.class);
                    //intent.putParcelableArrayListExtra("valuesList", value);

                    startActivity(intent);
                }
            }).start();
        }
    });

23

只是要明确说明一些内容:

主线程基本上是UI线程。

因此,说您无法在主线程中执行联网操作意味着您无法在UI线程中执行联网操作,这意味着您也无法在*runOnUiThread(new Runnable() { ... }*其他某个线程内块中执行联网操作

(我花了很长的时间来试图弄清楚为什么我在主线程之外的某个地方遇到了该错误。这就是为什么;该线程有所帮助;希望此注释会对其他人有所帮助。)


22

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

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

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

18

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

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

通过HTTP ReST

通常,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/

您如何将它与Finances 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);
}

财务ApiBuilder

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

财务片段

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 : //stackoverflow.com/a/42899766/1024412)。

一站式ReST API访问

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

离子

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

请阅读msysmilu的出色答案(https://stackoverflow.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

排球比翻新需要更多的设置。您将需要创建一个类似这样的类来设置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/


16

简单来说

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

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

解:

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

道路:

把你所有的作品放进去

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

但:

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

如果不这样做,您将得到ViewRootImpl$CalledFromWrongThreadException

如何?

  1. 使用AsyncTask时,从onPostExecute()方法更新视图
  2. 调用runOnUiThread()方法并在run()方法内部更新视图。

12

您可以将代码的一部分移到另一个线程中以减轻负担,main thread并避免获取ANRNetworkOnMainThreadExceptionIllegalStateException(例如,无法访问主线程上的数据库,因为它可能长时间锁定UI)。

您应根据情况选择一些方法

Java 线程或Android HandlerThread

Java线程仅一次性使用,并且在执行其run方法后死亡。

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

异步任务

AsyncTask被设计为围绕ThreadHandler的帮助器类,并且不构成通用的线程框架。理想情况下,应将AsyncTasks用于较短的操作(最多几秒钟)。如果需要使线程长时间运行,则强烈建议您使用java.util.concurrent包提供的各种API,例如执行器ThreadPoolExecutorFutureTask

线程池实现ThreadPoolExecutorScheduledThreadPoolExecutor ...

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

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

未来任务

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

AsyncTaskLoaders

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

意图服务

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

作业调度程序

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

RxJava的

用于通过使用可观察序列组成异步程序和基于事件的程序的库。

协程科特琳

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

在这里这里这里这里阅读更多


为我工作...我已经广泛使用AsyncTask,但是当一个任务正在运行时,另一个将等待。我想解决这个问题。现在与executeonexecutor合作。让我们看看它在低内存设备上的表现。
Suraj Shingade'Mar

请看一下该方法:asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,params); 同时运行您的任务
yoAlex5 '18

10

尽管上面有一个巨大的解决方案池,但没有人提到com.koushikdutta.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
    }
});

10

新的ThreadAsyncTask解决方案已经进行了解释。

AsyncTask理想情况下应用于短期操作。正常Thread情况下,Android不可取。

看看使用HandlerThreadHandler的替代解决方案

处理器线程

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

处理程序:

处理程序使您可以发送和处理与线程的MessageQueue关联的Message和Runnable对象。每个Handler实例都与一个线程和该线程的消息队列关联。创建新的Handler时,它将绑定到正在创建它的线程的线程/消息队列中-从那时起,它将把消息和可运行对象传递到该消息队列,并在它们从消息中出来时执行它们队列。

解:

  1. 创建 HandlerThread

  2. 呼叫start()HandlerThread

  3. Handler获得创建LooperHanlerThread

  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

8

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,即我们希望onNext()在UI线程上调用回调


8

主线程是UI线程,并且您无法在主线程中执行可能阻止用户交互的操作。您可以通过两种方式解决此问题:

像这样强制在主线程中执行任务

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

或创建一个简单的处理程序并根据需要更新主线程。

Runnable runnable;
Handler newHandler;

newHandler = new Handler();
runnable = new Runnable() {
    @Override
    public void run() {
         try {
            //update UI
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
};
newHandler.post(runnable);

并停止使用该线程:

newHandler.removeCallbacks(runnable);

有关更多信息,请查看以下内容:无痛线程


谢谢。当在onCreate中添加为第一个Action时,版本1会有所帮助。
Ingo

7

这可行。只是使Luiji博士的答案简单了一点。

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.