ChatGPT解决这个技术问题 Extra ChatGPT

如何修复“android.os.NetworkOnMainThreadException”?

为 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

我该如何解决这个问题?

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

S
Shayan Shafiq

注意: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 应用程序)。??
这是一个很好的解决方案,它节省了我的时间!
由于 AsyncTask 已被弃用,最新的解决方案是什么?
P
Peter Mortensen

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

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

添加:

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,我有点困惑它在做什么。当网络低时,我看到了你所说的后果。
V
V-rund Puro-hit

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

Thread thread = new Thread(new Runnable() {

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

thread.start(); 

您将如何将参数传递给此?
如果您需要在请求后访问 UI,则需要在最后返回主线程,如 here 所述。
简单而且有效,非常感谢你:)
P
Peter Mortensen

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

作为非静态内部类创建的 AsyncTask 具有对封闭 Activity 对象、其上下文以及由该 Activity 创建的整个 View 层次结构的隐式引用。此引用可防止在 AsyncTask 的后台工作完成之前对 Activity 进行垃圾收集。如果用户的连接速度很慢,和/或下载量很大,这些短期内存泄漏可能会成为一个问题——例如,如果方向改变了几次(并且你没有取消正在执行的任务),或者用户导航离开活动。

AsyncTask 具有不同的执行特性,具体取决于它执行的平台:在 API 级别 4 之前,AsyncTask 在单个后台线程上串行执行;从 API 级别 4 到 API 级别 10,AsyncTasks 在多达 128 个线程的池中执行;从 API 级别 11 开始,AsyncTask 在单个后台线程上串行执行(除非您使用重载的 executeOnExecutor 方法并提供替代执行器)。在 ICS 上串行运行时运行良好的代码在 Gingerbread 上并发执行时可能会中断,例如,如果您有无意的执行顺序依赖项。

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

使用可以为您完成这项工作的库 - 在这个问题中有一个很好的网络库比较,或者使用服务或 IntentService 代替,可能使用 PendingIntent 通过活动的 onActivityResult 方法返回结果。

IntentService 方法

缺点:

比 AsyncTask 更多的代码和复杂性,虽然没有你想象的那么多

将请求排队并在单个后台线程上运行它们。您可以通过将 IntentService 替换为等效的 Service 实现来轻松控制这一点,也许就像这样。

嗯,我现在真的想不出其他人了

优点:

避免短期内存泄漏问题

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

一个比 AsyncTask 更好的平台来构建和重用强大的网络代码。示例:如果您需要进行重要的上传,您可以从 Activity 中的 AsyncTask 进行,但如果用户上下文切换到应用程序之外接听电话,系统可能会在上传完成之前终止应用程序。杀死具有活动服务的应用程序的可能性较小。

如果您使用自己的 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);
}

提供了一个 GitHub 项目,其中包含一个完整的 Android Studio/Gradle 项目here


P
Peter Mortensen

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

Android 开发者网站上有一篇关于 Painless Threading 的文章对此进行了很好的介绍,它将为您提供比此处实际提供的更深入的答案。


P
Peter Mortensen

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

不要在主 UI 线程中使用网络调用。为此使用异步任务。在 setContentView(R.layout.activity_main);: if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll( )。建造(); StrictMode.setThreadPolicy(policy); }

并将下面的 import 语句放入您的 Java 文件中。

import android.os.StrictMode;

遵循您的第二个解决方案是一种不好的做法。异步是正确执行此操作的方法。如果您更改政策,您就像隐藏您的问题!
P
Peter Mortensen

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

例如:

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

并将其添加到文件 AndroidManifest.xml:

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

C
Community

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

使用 Service 或 AsyncTask

另请参阅堆栈溢出问题:

android.os.NetworkOnMainThreadException sending an email from Android


也许值得强调一点,如果你使用服务,你仍然需要创建一个单独的线程——服务回调在主线程上运行。另一方面,IntentService 在后台线程上运行其 onHandleIntent 方法。
您不应该将 AsyncTask 用于长时间运行的操作!指南指定最多 2 到 3 秒。
P
Piyush-Ask Any Difference

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

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

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

Full code for both the methods


是的,会出现 ANR 错误。表示应用程序在 5 秒内没有响应。
这是一个非常糟糕的答案。您不应该更改线程的策略,而是编写更好的代码:不要在主线程上进行网络操作!
P
Peter Mortensen

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

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

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

Anonymous Runnable 不是最好的方法,因为它对封闭类有一个隐式引用,并防止它在线程完成之前被 GC 编辑!此外,该线程将以与主/美国线程相同的优先级运行,与生命周期方法和 UI 帧速率竞争!
P
Peter Mortensen

将您的代码放入:

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

P
Peter Mortensen

这发生在 Android 3.0 及更高版本中。从 Android 3.0 及更高版本开始,它们已限制使用网络操作(访问 Internet 的函数)在主线程/UI 线程中运行(从您的 on create 和 on resume 方法产生的活动)。

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


O
Oleksiy

使用 Android Annotations 是一个选项。它将允许您简单地在后台线程中运行任何方法:

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

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

请注意,尽管它提供了简单性和可读性的优点,但也有其缺点。


@Gavriel 它会创建您注释的所有内容的副本,无论是方法、活动、片段、单例等,因此代码量是原来的两倍,而且编译它需要更长的时间。由于库中的错误,它也可能存在一些问题。调试和查找错误将变得更加困难。
A
Ashwin S Ashok

该错误是由于在主线程中执行了长时间运行的操作,您可以使用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
    }
});

P
Peter Mortensen

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

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


C
Community

顶部 answer of spektom 完美。

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

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

(来自他的例子。)


P
Peter Mortensen

这仅针对面向 Honeycomb SDK 或更高版本的应用程序抛出。允许针对早期 SDK 版本的应用程序在其主事件循环线程上进行联网。

The error is the SDK warning!


P
Peter Mortensen

对我来说是这样的:

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

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

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

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

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

让我解释一下你在这里做什么:NetworkOnMainThreadException 是守护者,它告诉你:不要朝自己的脚开枪......你的解决方案是:让我们回到没有守护者的过去 - 现在我可以自由射击我的脚
我也采用了这种方法,没有任何问题。 Guardian有时太挑剔了。
C
Catalina T.

在您的活动中使用它

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

N
Novak

只是为了明确地说明一些事情:

主线程基本上是 UI 线程。

因此,说您不能在主线程中进行网络操作意味着您不能在 UI 线程中进行网络操作,这意味着 您也不能在某个其他线程内的 *runOnUiThread(new Runnable() { ... }* 块中进行网络操作

(我花了很长的时间想弄清楚为什么我在主线程以外的地方遇到了这个错误。这就是原因;这个线程有帮助;希望这个评论可以帮助其他人。)


P
Peter Mortensen

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

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

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

P
Peter Mortensen

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

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

基于 HTTP 的 REST

通常是 JSON,但它可以是 XML 或其他东西。

完整的 API 访问权限

假设您正在编写一个应用程序,让用户可以跟踪股票价格、利率和货币汇率。您会发现一个如下所示的 JSON API:

http://api.example.com/stocks                       // ResponseWrapper<String> object containing a
                                                    // list of strings with ticker symbols
http://api.example.com/stocks/$symbol               // Stock object
http://api.example.com/stocks/$symbol/prices        // PriceHistory<Stock> object
http://api.example.com/currencies                   // ResponseWrapper<String> object containing a
                                                    // list of currency abbreviation
http://api.example.com/currencies/$currency         // Currency object
http://api.example.com/currencies/$id1/values/$id2  // PriceHistory<Currency> object comparing the prices
                                                    // of the first currency (id1) to the second (id2)

从 Square 改造

这是具有多个端点的 API 的绝佳选择,它允许您声明 REST 端点,而不必像使用 Amazon Ion JavaVolley 等其他库(网站:Retrofit)。

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

文件 build.gradle

将这些行添加到您的模块级 build.gradle 文件中:

implementation 'com.squareup.retrofit2:retrofit:2.3.0' // Retrofit library, current as of September 21, 2017
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' // Gson serialization and deserialization support for retrofit, version must match retrofit version

文件 FinanceApi.java

public interface FinancesApi {
    @GET("stocks")
    Call<ResponseWrapper<String>> listStocks();
    @GET("stocks/{symbol}")
    Call<Stock> getStock(@Path("symbol")String tickerSymbol);
    @GET("stocks/{symbol}/prices")
    Call<PriceHistory<Stock>> getPriceHistory(@Path("symbol")String tickerSymbol);

    @GET("currencies")
    Call<ResponseWrapper<String>> listCurrencies();
    @GET("currencies/{symbol}")
    Call<Currency> getCurrency(@Path("symbol")String currencySymbol);
    @GET("currencies/{symbol}/values/{compare_symbol}")
    Call<PriceHistory<Currency>> getComparativeHistory(@Path("symbol")String currency, @Path("compare_symbol")String currencyToPriceAgainst);
}

班级财务ApiBuilder

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 可以轻松完成此操作(有关详细信息,请参阅 this awesome answer to Add 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

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

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

文件 ImageFetch.java

Volley 比改造需要更多的设置。您将需要创建一个这样的类来设置 RequestQueue、ImageLoader 和 ImageCache,但这还不错:

public class ImageFetch {
    private static ImageLoader imageLoader = null;
    private static RequestQueue imageQueue = null;

    public static ImageLoader getImageLoader(Context ctx){
        if(imageLoader == null){
            if(imageQueue == null){
                imageQueue = Volley.newRequestQueue(ctx.getApplicationContext());
            }
            imageLoader = new ImageLoader(imageQueue, new ImageLoader.ImageCache() {
                Map<String, Bitmap> cache = new HashMap<String, Bitmap>();
                @Override
                public Bitmap getBitmap(String url) {
                    return cache.get(url);
                }
                @Override
                public void putBitmap(String url, Bitmap bitmap) {
                    cache.put(url, bitmap);
                }
            });
        }
        return imageLoader;
    }
}

文件 user_view_dialog.xml

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

<com.android.volley.toolbox.NetworkImageView
    android:id="@+id/profile_picture"
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    app:srcCompat="@android:drawable/spinner_background"/>

文件 UserViewDialog.java

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

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

毕加索

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


P
Peter Mortensen

简单来说,

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

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

解决方案:

您必须创建一个新线程或使用 AsyncTask 类

方法:

把你所有的作品都放进去

新线程的 run() 方法或 AsyncTask 类的 doInBackground() 方法。

但:

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

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

如何

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


P
Peter Mortensen

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

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

Java Thread 或 Android HandlerThread

Java 线程只能一次性使用,并在执行其 run 方法后终止。 HandlerThread 是一个方便的类,用于启动具有循环器的新线程。

AsyncTask(在 API 级别 30 中已弃用

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

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

[Executor framework]

实现 ExecutorService 的 ThreadPoolExecutor 类可以对线程池进行精细控制(例如,核心池大小、最大池大小、保持活动时间等) ScheduledThreadPoolExecutor - 扩展 ThreadPoolExecutor 的类。它可以在给定的延迟后或定期安排任务。

FutureTask

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

AsyncTaskLoaders

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

IntentService

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

JobScheduler

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

RxJava

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

Coroutines(科特林)

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

阅读更多 herehereherehere


R
Ravindra babu

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

AsyncTask 最好用于短操作。普通 Thread 不适用于 Android。

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

处理线程

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

处理程序:

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

解决方案:

Create HandlerThread 在 HandlerThread 上调用 start() 通过从 HanlerThread 获取 Looper 创建 Handler 在 Runnable 对象中嵌入您的网络操作相关代码 提交 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);

使用这种方法的优点:

为每个网络操作创建新的线程/异步任务是昂贵的。 Thread/AsyncTask 将被销毁并为下一次网络操作重新创建。但是使用 Handler 和 HandlerThread 方法,您可以使用 Handler 将许多网络操作(作为 Runnable 任务)提交给单个 HandlerThread。


P
Peter Mortensen

科特林

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

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

m
msysmilu

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

P
Peter Mortensen

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

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

P
Peter Mortensen

主线程是 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);

如需更多信息,请查看:Painless threading


P
Peter Mortensen

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

通过指定 (Schedulers.io()),RxAndroid 将在不同的线程上运行 getFavoriteMusicShows()。通过使用 AndroidSchedulers.mainThread(),我们希望在 UI 线程上观察这个 Observable,即,我们希望在 UI 线程上调用我们的 onNext() 回调。