提问



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


我该如何解决这个问题?

最佳参考


当应用程序尝试在其主线程上执行网络操作时,将引发此异常。在AsyncTask中运行代码:[110]


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"/>

其它参考1


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


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


加:


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

StrictMode.setThreadPolicy(policy); 


在你的班上,





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


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


后果:


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


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

其它参考2


我用新的Thread解决了这个问题。


Thread thread = new Thread(new Runnable() {

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

thread.start(); 

其它参考3


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


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

其它参考4


接受的答案有一些重要的缺点。除非你真的知道你在做什么,否则不建议使用AsyncTask进行网络连接。一些不利方面包括:



  • 作为非静态内部类创建的AsyncTask具有对封闭的Activity对象,其上下文以及该Activity创建的整个View层次结构的隐式引用。此引用可防止在AsyncTask之前对Activity进行垃圾回收后台工作完成。如果用户的连接速度很慢,和/或下载量很大,这些短期内存泄漏可能会成为一个问题 - 例如,如果方向改变了几次(并且您不会取消执行的任务),或者用户导航远离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实现来轻松控制,可能就像这样。

  • 嗯,我现在还不能想到任何其他人



上行方面:[116]



  • 避免短期内存泄漏问题

  • 如果您的Activity在网络操作正在进行时重新启动,它仍然可以通过其onActivityResult方法接收下载结果

  • 比AsyncTask更好的平台,可以构建和重用强大的网络代码。示例:如果您需要进行重要上传,可以从Activity中的AsyncTask进行,但如果用户上下文 - 切换出应用程序以接听电话,系统< em>可能在上传完成之前终止该应用。杀死具有ActivityService的应用程序不太可能。

  • 如果您使用自己的IntentService并发版本(就像我上面链接的那样),您可以通过Executor控制并发级别。



实施摘要



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


步骤1:创建IntentService以执行下载。你可以告诉它通过Intent额外的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项目。[117]

其它参考5



  1. 不要使用strictMode(仅在调试模式下)

  2. 请勿更改SDK版本

  3. 不要使用单独的线程



使用Service或AsyncTask


另请参阅Stack 溢出问题:


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

其它参考6


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



  例如:



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


并将其添加到AndroidManifest.xml


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

其它参考7


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


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


不推荐:使用AsyncTask界面。


这两种方法的完整代码[119]

其它参考8


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


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


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

其它参考9


把你的代码放在里面:


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

其它参考10


使用Android Annotations是一种选择。它允许您在后台线程中简单地运行任何方法:[120]


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

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


请注意,虽然它提供了简单性和可读性的好处,但它有其缺点。

其它参考11


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


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

其它参考12


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


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

其它参考13


该错误是由于在主线程中执行长时间运行操作,您可以使用AsynTask或Thread轻松解决问题。您可以检查此库AsyncHTTPClient以便更好地处理。 [122] [123] [124]


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

其它参考14


spektom的最佳答案是完美的。


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


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


(从他的例子。)

其它参考15


仅针对Honeycomb SDK或更高版本的应用程序进行此操作。针对早期SDK版本的应用程序可以在其主要事件循环线程上进行联网。[126]


错误是SDK警告![127]

其它参考16


对我来说就是这样:


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


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


确保目标版本与Android目标库相同。如果您不确定目标库是什么,请右键单击您的项目 - > 构建路径 - > Android ,它应该是勾选的那个。


此外,正如其他人所提到的,包括访问Internet的正确权限:


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

其它参考17


只是明确说明一些事情:


主线程基本上是UI线程。


所以说你不能在主线程中进行网络操作意味着你不能在UI线程中进行网络操作,这意味着你不能在其他线程的*runOnUiThread(new Runnable() { ... }*块中进行网络操作,无论是。


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

其它参考18


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


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


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

其它参考19


    **Use like this in Your Activity**

    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 inten=new Intent(getApplicationContext(),ComposeMail.class);
            //intent.putParcelableArrayListExtra("valuesList", value);

            startActivity(inten);



                }
            }).start();
        }
    });

其它参考20


简单来说,


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


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


解决方案:



  1. 您必须创建新的主题

  2. 使用AsyncTask类



方式: [128]


把你所有的作品都放进去



  1. run()新线程的方法

  2. doInBackground() AsyncTask类的方法。



但:


当您从网络响应中获得某些内容并希望在您的视图中显示它时(如在TextView中显示响应消息),您需要返回到UI 主题。


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


如何?



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


其它参考21


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


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


通过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)


从Square改造



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


你如何在财务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://stackoverflow.com/a/42899766/1024412)


一次关闭ReST API访问



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


离子



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


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


通过HTTP

加载图像

排球



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


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


的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/[134]

其它参考22


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


它还可以异步非常简单:


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

其它参考23


这很有效。刚刚让路吉博士的答案变得更简单了。


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

其它参考24


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

其它参考25


从主(UI)线程访问网络资源会导致此异常。使用单独的线程或AsyncTask访问网络资源以避免此问题。

其它参考26


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()回调


其它参考27


已经解释了新Thread和AsyncTask解决方案。[136]


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


看一下使用HandlerThread和Handler [137] [138]的替代解决方案


HandlerThread



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



处理程序:



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



解:



  1. 创建HandlerThread

  2. HandlerThread上拨打start()

  3. HanlerThread获取Looper创建Handler

  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方法,您可以使用Handler向单个HandlerThread提交许多网络操作(作为Runnable任务)。


其它参考28


您不能在Android上的UI线程上实现网络操作。您将不得不使用AsyncTask类来执行网络相关的操作,如发送API请求,从URL下载图像等,并使用AsyncTask的回调方法,您可以获得onPostExecute menthod的结果,您将在UI线程和你可以使用来自Web服务或类似内容的数据填充UI。


示例:假设您要从URL下载图像:https://www.samplewebsite.com/sampleimage.jpg [139]


使用AsyncTask的解决方案:
 分别是。


    public class MyDownloader extends AsyncTask<String,Void,Bitmap>
    {
        @Override
        protected void onPreExecute() {
            // Show progress dialog
            super.onPreExecute();
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //Populate Ui
            super.onPostExecute(bitmap);
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            // Open URL connection read bitmaps and return form here
            return result;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            // Show progress update
            super.onProgressUpdate(values);
        }


    }
}


注意:不要忘记在Android清单文件中添加Internet权限。它会像魅力一样工作。 :)

其它参考29


还有另一种解决此问题的非常方便的方法 - 使用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有一本很棒的电子书。[140]