程序员开发实例大全宝库

网站首页 > 编程文章 正文

如何修复 'android.os.NetworkOnMainThreadException'

zazugpt 2025-05-16 17:53:43 编程文章 23 ℃ 0 评论

技术背景

在 Android 开发中,从 Android 3.0(API 级别 11)及以上版本开始,系统不允许在主线程(也称为 UI 线程)中执行网络操作。这是因为网络操作通常是耗时的,如果在主线程中进行,会阻塞主线程,导致应用无响应(ANR,Application Not Responding),严重影响用户体验。当应用尝试在主线程执行网络操作时,就会抛出
android.os.NetworkOnMainThreadException
异常。

实现步骤

1. 使用 AsyncTask(已在 API 级别 30 中弃用)

AsyncTask 是 Android 提供的一个方便的类,用于在后台线程执行任务,并在主线程更新 UI。

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

    protected void onPostExecute(RSSFeed feed) {
        // 处理结果
    }
}

MainActivityonCreate 方法中执行任务:

new RetrieveFeedTask().execute(urlToRssFeed);

2. 使用新线程

创建一个新的线程来执行网络操作。

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            // 网络操作代码
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});
thread.start();

3. 使用 IntentService

IntentService 是一个异步的、会自动停止的服务,适合执行长时间运行的后台任务。

步骤 1:创建 IntentService

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);
        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) {
                reply.send(ERROR_CODE);
            }
        } catch (PendingIntent.CanceledException exc) {
            Log.i(TAG, "reply cancelled", exc);
        }
    }
}

步骤 2:在清单文件中注册服务

<service
    android:name=".DownloadIntentService"
    android:exported="false"/>

步骤 3:从 Activity 调用服务

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;
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

4. 使用 Retrofit

Retrofit 是一个优秀的网络请求库,用于处理 RESTful API。

步骤 1:添加依赖

build.gradle 中添加依赖:

implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

步骤 2:定义 API 接口

public interface FinancesApi {
    @GET("stocks")
    Call<ResponseWrapper<String>> listStocks();
    @GET("stocks/{symbol}")
    Call<Stock> getStock(@Path("symbol")String tickerSymbol);
    // 其他接口方法
}

步骤 3:构建 API 实例

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

步骤 4:调用 API

FinancesApi api = FinancesApiBuilder.build("http://api.example.com/");
api.getStock("INTC").enqueue(new Callback<Stock>(){
    @Override
    public void onResponse(Call<Stock> stockCall, Response<Stock> stockResponse){
        Stock stock = stockCall.body();
        // 处理响应
    }
    @Override
    public void onFailure(Call<Stock> stockCall, Throwable t){
        // 处理错误
    }
});

最佳实践

  • 选择合适的方法:根据任务的复杂度和需求选择合适的方法。对于简单的短期任务,可以使用 AsyncTask(如果在 API 级别 30 以下);对于长时间运行的任务,建议使用 IntentServiceRetrofit 等库。
  • 权限声明:在 AndroidManifest.xml 中添加网络权限:
<uses-permission android:name="android.permission.INTERNET"/>
  • 异常处理:在网络操作中,要对可能出现的异常进行捕获和处理,如 IOExceptionMalformedURLException 等。

常见问题

1. 使用新线程时无法更新 UI

由于 Android 不允许在非主线程更新 UI,因此在新线程中执行网络操作后,如果需要更新 UI,可以使用 runOnUiThread 方法:

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 更新 UI 的代码
    }
});

2. AsyncTask 导致内存泄漏

AsyncTask 如果作为非静态内部类使用,会持有外部 Activity 的引用,可能导致内存泄漏。可以将 AsyncTask 定义为静态内部类,并使用弱引用持有 Activity。

3. 网络请求超时或失败

网络请求可能会因为网络不稳定、服务器问题等原因超时或失败。可以设置合理的超时时间,并在请求失败时进行重试或提示用户。例如,使用 OkHttp 可以设置超时时间:

OkHttpClient client = new OkHttpClient.Builder()
       .connectTimeout(10, TimeUnit.SECONDS)
       .readTimeout(10, TimeUnit.SECONDS)
       .writeTimeout(10, TimeUnit.SECONDS)
       .build();

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表