如何修复 'android.os.NetworkOnMainThreadException' 错误

如何修复 ‘android.os.NetworkOnMainThreadException’ 错误

技术背景

在 Android 应用开发中,当应用尝试在主线程上执行网络操作时,就会抛出 android.os.NetworkOnMainThreadException 异常。从 Android 3.0(Honeycomb,API 级别 11)及以上版本开始,系统限制了在主线程/UI 线程中进行网络操作,目的是鼓励开发者使用单独的线程来处理网络请求,以避免应用无响应或被系统杀死。

实现步骤

方法一:使用 AsyncTask(已在 API 级别 30 中弃用)

  1. 创建一个继承自 AsyncTask 的类:
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
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) {
// TODO: check this.exception
// TODO: do something with the feed
}
}
  1. MainActivity.javaonCreate() 方法中执行任务:
1
new RetrieveFeedTask().execute(urlToRssFeed);
  1. AndroidManifest.xml 中添加网络权限:
1
<uses-permission android:name="android.permission.INTERNET"/>

方法二:使用新线程

1
2
3
4
5
6
7
8
9
10
11
12
13
Thread thread = new Thread(new Runnable() {

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

thread.start();

同样,需要在 AndroidManifest.xml 中添加网络权限。

方法三:使用 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);
}
}
}
  1. AndroidManifest.xml 中注册服务:
1
2
3
<service
android:name=".DownloadIntentService"
android:exported="false"/>
  1. 在 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);
  1. 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);
}

方法四:使用 Android Annotations

1
2
3
4
5
6
7
8
9
// normal method
private void normal() {
doSomething(); // do something in background
}

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

方法五:使用 AsyncHTTPClient 库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
}
});

核心代码

以下是使用新线程进行网络操作的核心代码示例:

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

最佳实践

  • 避免在主线程执行耗时任务,如网络操作、文件 I/O 或 SQLite 数据库操作。
  • 根据不同场景选择合适的异步处理方式:
    • 对于简单的短期任务,可以使用 AsyncTask,但要注意其在不同 API 级别上的执行特性。
    • 对于需要长时间运行的任务或需要避免内存泄漏的情况,建议使用 ServiceIntentService
  • 确保在 AndroidManifest.xml 中添加网络权限:
1
<uses-permission android:name="android.permission.INTERNET"/>

常见问题

1. 使用 AsyncTask 时出现内存泄漏问题

  • 原因:非静态内部类形式创建的 AsyncTask 会隐式引用外部 Activity 对象,阻止 Activity 被垃圾回收。
  • 解决方法:使用静态内部类或考虑使用其他异步处理方式,如 ServiceIntentService

2. 不同 API 级别上 AsyncTask 执行特性不同

  • 原因AsyncTask 在不同 API 级别上的执行方式有所不同,可能导致代码在不同版本上表现不一致。
  • 解决方法:使用 executeOnExecutor 方法并提供替代执行器,或者使用其他更稳定的异步处理方式。

3. 仍然抛出 NetworkOnMainThreadException 异常

  • 原因:可能是在 runOnUiThread 块中执行了网络操作。
  • 解决方法:确保所有网络操作都在单独的线程或异步任务中执行。

如何修复 'android.os.NetworkOnMainThreadException' 错误
https://119291.xyz/posts/2025-05-13.how-to-fix-android-os-networkonmainthreadexception/
作者
ww
发布于
2025年5月13日
许可协议