如何修复 'android.os.NetworkOnMainThreadException'

如何修复 ‘android.os.NetworkOnMainThreadException’

技术背景

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

实现步骤

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 方法中执行任务:

1
new RetrieveFeedTask().execute(urlToRssFeed);

2. 使用新线程

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

1
2
3
4
5
6
7
8
9
10
11
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
// 网络操作代码
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();

3. 使用 IntentService

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

步骤 1:创建 IntentService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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:在清单文件中注册服务

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

步骤 3:从 Activity 调用服务

1
2
3
4
5
6
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 中处理结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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 中添加依赖:

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

步骤 2:定义 API 接口

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

步骤 3:构建 API 实例

1
2
3
4
5
6
7
8
9
public class FinancesApiBuilder {
public static FinancesApi build(String baseUrl){
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(FinancesApi.class);
}
}

步骤 4:调用 API

1
2
3
4
5
6
7
8
9
10
11
12
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 中添加网络权限:
1
<uses-permission android:name="android.permission.INTERNET"/>
  • 异常处理:在网络操作中,要对可能出现的异常进行捕获和处理,如 IOExceptionMalformedURLException 等。

常见问题

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

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

1
2
3
4
5
6
runOnUiThread(new Runnable() {
@Override
public void run() {
// 更新 UI 的代码
}
});

2. AsyncTask 导致内存泄漏

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

3. 网络请求超时或失败

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

1
2
3
4
5
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();

如何修复 'android.os.NetworkOnMainThreadException'
https://119291.xyz/posts/2025-04-22.how-to-fix-android-os-networkonmainthreadexception/
作者
ww
发布于
2025年4月23日
许可协议