[英]How can I fix '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
我该如何解决这个问题?
注意: AsyncTask 在 API 级别 30 中已弃用。
异步任务 | Android 开发人员
当应用程序尝试在其主线程上执行网络操作时,将引发此异常。 在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"/>
您应该几乎总是在线程上或作为异步任务运行网络操作。
但是,如果您愿意接受后果,则可以删除此限制并覆盖默认行为。
添加:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
在您的 class 中,
和
在 Android manifest.xml文件中添加此权限:
<uses-permission android:name="android.permission.INTERNET"/>
结果:
您的应用程序将(在互联网连接不稳定的区域)变得无响应并被锁定,用户认为速度很慢并且必须强制终止,并且您冒着活动管理器杀死您的应用程序并告诉用户应用程序已停止的风险。
Android 有一些关于设计响应性的良好编程实践的好技巧: NetworkOnMainThreadException | Android 开发人员
我使用新的Thread
解决了这个问题。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
//Your code goes here
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
公认的答案有一些明显的缺点。 除非您真的知道自己在做什么,否则不建议使用 AsyncTask 进行联网。 一些缺点包括:
executeOnExecutor
方法并提供替代执行器)。 在 ICS 上串行运行时运行良好的代码在 Gingerbread 上并发执行时可能会中断,例如,如果您有无意的执行顺序依赖项。如果您想避免短期 memory 泄漏,在所有平台上具有明确定义的执行特征,并有基础构建真正强大的网络处理,您可能需要考虑:
Service
或IntentService
代替,可能使用PendingIntent
通过 Activity 的onActivityResult
方法返回结果。缺点:
AsyncTask
更多的代码和复杂性,虽然没有你想象的那么多IntentService
替换为等效的Service
实现来轻松控制这一点,也许就像这样。优点:
onActivityResult
方法接收下载结果Activity
中的AsyncTask
进行,但如果用户上下文切换到应用程序之外接听电话,系统可能会在上传完成之前终止应用程序。 杀死具有活动Service
的应用程序的可能性较小。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 object,服务将使用它返回结果:
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);
}
这个问题有两种解决方案。
不要在主 UI 线程中使用网络调用。 为此使用异步任务。
在setContentView(R.layout.activity_main); 之后将以下代码写入您的 MainActivity 文件中; :
if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); }
并将以下导入语句放入您的 Java 文件中。
import android.os.StrictMode;
在另一个线程上执行网络操作。
例如:
new Thread(new Runnable(){
@Override
public void run() {
// Do network action in this function
}
}).start();
并将其添加到文件AndroidManifest.xml :
<uses-permission android:name="android.permission.INTERNET"/>
使用 Service 或 AsyncTask
另请参阅堆栈溢出问题:
您可以使用以下代码禁用严格模式:
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy =
new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
不推荐这样做:使用AsyncTask
接口。
基于网络的操作不能在主线程上运行。 您需要在子线程上运行所有基于网络的任务或实现 AsyncTask。
这是在子线程中运行任务的方式:
new Thread(new Runnable(){
@Override
public void run() {
try {
// Your implementation goes here
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}).start();
将您的代码放入:
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
}
}
这发生在 Android 3.0 及更高版本中。 从 Android 3.0 及更高版本开始,它们已限制使用网络操作(访问 Internet 的函数)在主线程/UI 线程中运行(从活动中的 on create 和 on resume 方法产生的内容)。
这是为了鼓励使用单独的线程进行网络操作。 有关如何以正确方式执行网络活动的更多详细信息,请参阅AsyncTask 。
使用Android 注释是一个选项。 它将允许您简单地在后台线程中运行任何方法:
// normal method
private void normal() {
doSomething(); // do something in background
}
@Background
protected void doSomething()
// run your networking code here
}
请注意,尽管它提供了简单性和可读性的优点,但也有其缺点。
该错误是由于在主线程中执行了长时间运行的操作,您可以使用AsynTask或Thread轻松纠正该问题。 您可以签出此库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
}
});
您不应在主线程(UI 线程)上执行任何耗时的任务,例如任何网络操作、文件 I/O 或 SQLite 数据库操作。 所以对于这种操作,你应该创建一个工作线程,但问题是你不能直接从你的工作线程执行任何 UI 相关的操作。 为此,您必须使用Handler
并传递Message
。
为了简化所有这些事情,Android 提供了各种方式,如AsyncTask
、 AsyncTaskLoader
、 CursorLoader
或IntentService
。 因此,您可以根据自己的要求使用其中任何一种。
spektom 的最佳答案非常完美。
如果您正在编写AsyncTask
内联而不是扩展为 class,除此之外,如果需要从AsyncTask
中获取响应,可以使用get()
方法,如下所示。
RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();
(来自他的例子。)
这仅适用于针对Honeycomb SDK 或更高版本的应用程序。 允许针对早期 SDK 版本的应用程序在其主事件循环线程上进行联网。
对我来说是这样的:
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="10" />
我正在测试我的应用程序的设备是 4.1.2,即 SDK 版本 16!
确保目标版本与您的 Android 目标库相同。 如果您不确定您的目标库是什么,请右键单击您的项目 ->构建路径-> Android ,它应该是勾选的那个。
此外,正如其他人所提到的,包括访问 Internet 的正确权限:
<uses-permission android:name="android.permission.INTERNET"/>
在您的活动中使用它
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();
}
});
只是为了明确地说明一些事情:
主线程基本上是 UI 线程。
所以说你不能在主线程中进行网络操作意味着你不能在 UI 线程中进行网络操作,这意味着你不能在*runOnUiThread(new Runnable() {... }*
块内的某个其他线程中进行网络操作, 任何一个。
(我花了很长时间试图弄清楚为什么我在主线程以外的地方遇到了这个错误。这就是原因;这个线程有帮助;希望这个评论能帮助其他人。)
如果执行任务花费太多时间,则由于在主线程上执行的任何繁重任务会发生此异常。
为了避免这种情况,我们可以使用线程或执行器来处理它
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
// You can perform your task here.
}
});
这个问题已经有很多很棒的答案,但是自从这些答案发布以来,已经出现了很多很棒的库。 这是一种新手指南。
我将介绍几个用于执行网络操作的用例,并为每个用例提供一两个解决方案。
通常是 JSON,但也可以是 XML 或其他。
假设您正在编写一个应用程序,让用户可以跟踪股票价格、利率和货币汇率。 你会发现一个 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)
This is an excellent choice for an API with multiple endpoints and allows you to declare the REST endpoints instead of having to code them individually as with other libraries like Amazon Ion Java or Volley (website: Retrofit ).
您如何将其与财务 API 一起使用?
将这些行添加到您的模块级别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
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);
}
public class FinancesApiBuilder {
public static FinancesApi build(String baseUrl){
return new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(FinancesApi.class);
}
}
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
}
}
If your API requires an API key or other header, like a user token, etc. to be sent, Retrofit makes this easy (see this awesome answer to Add Header Parameter in Retrofit for details).
假设您正在构建一个“心情天气”应用程序,它会查找用户的 GPS 位置并检查该区域的当前温度并告诉他们心情。 此类应用无需声明 API 端点; 它只需要能够访问一个 API 端点。
对于此类访问,这是一个很棒的库。
请阅读msysmilu对How can I fix 'android.os.NetworkOnMainThreadException' 的精彩回答? .
Volley 也可用于 REST API,但由于需要更复杂的设置,我更喜欢使用上述 Square 的Retrofit 。
假设您正在构建一个社交网络应用程序并想要加载朋友的个人资料图片。
将此行添加到您的模块级别build.gradle文件:
implementation 'com.android.volley:volley:1.0.0'
Volley 需要比 Retrofit 更多的设置。 您需要像这样创建 class 来设置 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;
}
}
将以下内容添加到您的布局 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"/>
将以下代码添加到 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 的另一个优秀图书馆。 请参阅网站以获取一些很好的示例。
简单来说,
不要在 UI 线程中做网络工作
例如,如果您执行 HTTP 请求,则这是一个网络操作。
解决方案:
方法:
把你所有的作品都放进去
run()
方法doInBackground()
方法。但:
当您从网络响应中获取某些内容并希望将其显示在您的视图中时(例如在 TextView 中显示响应消息),您需要返回到 UI线程。
如果你不这样做,你会得到ViewRootImpl$CalledFromWrongThreadException
。
如何
onPostExecute()
方法更新视图runOnUiThread()
方法并更新run()
方法中的视图。 您可以将部分代码移动到另一个线程以卸载main thread
并避免出现ANR 、 NetworkOnMainThreadException 、 IllegalStateException (例如,无法访问主线程上的数据库,因为它可能会长时间锁定 UI) .
您应该根据情况选择一些方法
Java 线程或 Android HandlerThread :
Java 线程是一次性使用的,并且在执行其运行方法后死亡。
HandlerThread 是一个方便的 class ,用于启动具有弯针的新线程。
AsyncTask (在 API 级别 30 中已弃用)
AsyncTask被设计为围绕Thread和Handler的 helper class,并不构成通用线程框架。 AsyncTasks 最好用于短操作(最多几秒钟)。如果您需要保持线程长时间运行,强烈建议您使用 java.util.concurrent package 提供的各种 API,例如Executor 、 ThreadPoolExecutor和FutureTask 。
由于主线程独占了 UI 组件,所以无法访问某些 View,这就是 Handler 来救场的原因
实现 ExecutorService 的 ThreadPoolExecutor class 可以对线程池进行精细控制(例如,核心池大小、最大池大小、保持活动时间等)
ScheduledThreadPoolExecutor - 扩展 ThreadPoolExecutor 的 class。 它可以在给定的延迟后或定期安排任务。
FutureTask 执行异步处理,但是如果结果尚未准备好或处理尚未完成,调用 get() 将阻塞线程
AsyncTaskLoaders 因为它们解决了 AsyncTask 固有的许多问题
这是在 Android 上长时间运行处理的实际选择,一个很好的例子是上传或下载大文件。 即使用户退出应用程序,上传和下载仍可能继续,并且您当然不希望在这些任务进行时阻止用户使用应用程序。
实际上,您必须创建一个服务并使用 JobInfo.Builder 创建一个作业,它指定何时运行该服务的标准。
使用可观察序列组成异步和基于事件的程序的库。
协程(Kotlin)
它的主要要点是,它使异步代码看起来很像同步
已经解释了新的Thread
和AsyncTask解决方案。
理想情况下, AsyncTask
应该用于短操作。 普通Thread
不适用于 Android。
看看使用HandlerThread和Handler的替代解决方案
处理线程
方便的 class 用于启动具有弯针的新线程。 然后可以使用 looper 来创建处理程序类。 请注意,仍然必须调用
start()
。
处理程序:
Handler 允许您发送和处理与线程的 MessageQueue 关联的 Message 和 Runnable 对象。 每个 Handler 实例都与单个线程和该线程的消息队列相关联。 当您创建一个新的处理程序时,它会绑定到创建它的线程的线程/消息队列——从那时起,它会将消息和可运行对象传递到该消息队列并在它们从消息中出来时执行它们队列。
解决方案:
创建HandlerThread
在HandlerThread
上调用start()
通过从HanlerThread
获取Looper
来创建Handler
在Runnable
object 中嵌入您的网络操作相关代码
将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
任务是昂贵的。 Thread/AsyncTask
将被销毁并为下一次网络操作重新创建。 但是使用Handler
和HandlerThread
方法,您可以使用Handler
将许多网络操作(作为 Runnable 任务)提交给单个HandlerThread
。如果您使用的是 Kotlin,则可以使用协程:
fun doSomeNetworkStuff() {
GlobalScope.launch(Dispatchers.IO) {
// ...
}
}
这行得通。 我只是让Luiji博士的回答简单一点。
new Thread() {
@Override
public void run() {
try {
//Your code goes here
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
虽然上面有一个巨大的解决方案池,但没有人提到com.koushikdutta.ion
: https://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
}
});
解决这个问题还有另一种非常方便的方法——使用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() 调度程序的支持。
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()
回调。
主线程是 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
在 Android 上,网络操作无法在主线程上运行。 您可以使用 Thread、AsyncTask(短期运行任务)、Service(长期运行任务)来进行网络操作。
从主 (UI) 线程访问网络资源会导致此异常。 使用单独的线程或 AsyncTask 访问网络资源以避免此问题。
Kotlin是Android的新官方语言。 您可以在此处找到更多信息: Kotlin for Android 。
Anko是 Android 中 Kotlin 的受支持库。 GitHub 页面上有一些文档。
真正有用的解决方案,只有@AntonioLeiva 编写的几行代码: Using Anko to run background tasks with Kotlin in Android (KAD 09) 。
doAsync {
var result = runLongTask()
uiThread {
toast(result)
}
}
虽然很简单,当您在 UI 线程上运行后台作业时会发生NetworkOnMainThread
,因此您必须做的一件事就是在后台运行longTask 作业。 您可以在 Android 应用程序中使用此方法和带有 Anko的Kotlin来执行此操作。
Android 不允许在主线程上运行长时间运行的操作。
所以只需使用不同的线程并在需要时将结果发布到主线程。
new Thread(new Runnable() {
@Override
public void run() {
/*
// Run operation here
*/
// After getting the result
runOnUiThread(new Runnable() {
@Override
public void run() {
// Post the result to the main thread
}
});
}
}).start();
不允许在 Android 上的 UI 线程上实现网络操作。 You will have to use AsyncTask class to perform network related operations like sending API request, downloading image from a URL, etc. and using callback methods of AsyncTask, you can get you result in onPostExecute menthod and you will be in the UI thread and you可以使用来自 web 服务或类似服务的数据填充 UI。
示例:假设您要从 URL 下载图像: https://www.samplewebsite.com/sampleimage.jpg
使用 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 权限。 它会像魅力一样工作。 :)
我们还可以使用RxJava将网络操作移至后台线程。 它也相当简单。
webService.doSomething(someData)
.subscribeOn(Schedulers.newThread())-- This for background thread
.observeOn(AndroidSchedulers.mainThread()) -- for callback on UI
.subscribe(result -> resultText.setText("It worked!"),
e -> handleError(e));
你可以用 RxJava 做更多的事情。 以下是 RxJava 的一些链接。 随意挖掘。
在 Android 上,网络操作无法在主线程上运行。 您可以使用Thread
、 AsyncTask
(短期运行任务)、 Service
(长期运行任务)来进行网络操作。 当应用程序尝试在其主线程上执行网络操作时,会引发android.os.NetworkOnMainThreadException
。 如果您的任务耗时超过 5 秒,则需要强制关闭。
在AsyncTask
中运行您的代码:
class FeedTask extends AsyncTask<String, Void, Boolean> {
protected RSSFeed doInBackground(String... urls) {
// TODO: Connect
}
protected void onPostExecute(RSSFeed feed) {
// TODO: Check this.exception
// TODO: Do something with the feed
}
}
或者
new Thread(new Runnable(){
@Override
public void run() {
try {
// Your implementation
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}).start();
不建议这样做。
但出于调试目的,您也可以使用以下代码禁用严格模式:
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy =
new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
Google 在 Android 11 中弃用了 Android AsyncTask API。
即使你在 main Activity 之外创建了一个线程 class,只要在 main 中调用它,你也会得到同样的错误。 调用必须在可运行线程内,但如果您需要一些异步代码在后台执行或稍后在此处发布,您可以查看 Kotlin 和 Java 的一些替代方案:
*https://stackoverflow.com/questions/58767733/android-asynctask-api-deprecating-in-android-11-what-are-the-alternatives*
对我特别有用的是 mayank1513 对上述链接中可运行线程的Java 8实现的回答。 代码如下:
new Thread(() -> {
// do background stuff here
runOnUiThread(()->{
// OnPostExecute stuff here
});
}).start();
但是,您可以先在代码的某些部分定义线程,然后在其他地方启动它,如下所示:
线程定义
Thread thread = new Thread(() -> {
// do background stuff here
runOnUiThread(()->{
// OnPostExecute stuff here
});
});
线程调用
thread.start();
我希望这可以避免人们看到已弃用的 AsyncTask 的头痛。
当应用程序尝试在其主线程上执行网络操作时,将引发此异常。 如果您的任务耗时超过 5 秒,则需要强制关闭。
在AsyncTask
中运行您的代码:
class RetrieveFeedTask extends AsyncTask<String, Void, Boolean> {
protected RSSFeed doInBackground(String... urls) {
// TODO: Connect
}
protected void onPostExecute(RSSFeed feed) {
// TODO: Check this.exception
// TODO: Do something with the feed
}
}
您还可以使用以下代码使用严格模式来解决此问题。 它也是解决此问题的替代方法。
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
但最佳实践是使用 AsyncTask。
如何修复 android.os.NetworkOnMainThreadException
什么是 NetworkOnMainThreadException:
在 Android 中,我们必须在 UI 线程(主线程)上进行的所有 UI 操作。 如果我们在主线程上执行后台操作或一些网络操作,那么我们就有可能发生这个异常并且应用程序不会响应。
如何修复它:
为了避免这个问题,您必须使用另一个线程进行后台操作或网络操作,例如使用 asyncTask 并使用一些库进行网络操作,例如 Volley、AsyncHttp 等。
android.os.NetworkOnMainThreadException 在主线程进行网络操作时抛出。 您最好在 AsyncTask 中执行此操作以删除此异常。 这样写:
new AsyncTask<Void,String,String>(){
@Override
protected Void doInBackground(Void... params) {
// Perform your network operation.
// Get JSON or XML string from the server.
// Store in a local variable (say response) and return.
return response;
}
protected void onPostExecute(String results){
// Response returned by doInBackGround() will be received
// by onPostExecute(String results).
// Now manipulate your jason/xml String(results).
}
}.execute();
}
使用AsycTask
在后台线程中执行此操作
Java
class NetworkThread extends AsyncTask<String, Void, String> {
protected Void doInBackground(String... arg0) {
//Your implementation
}
protected void onPostExecute(String result) {
// TODO: do something with the feed
}
}
随时随地拨打电话
new NetworkThread().execute("Your URL here");
Kotlin
internal class MyNetworkTask : AsyncTask<String, Void, RSSFeed>() {
override fun doInBackground(vararg urls: String): RSSFeed? {
try {
// download
// prepare RSSFeeds
return RSSFeeds
} catch (e: Exception) {
//handle exception
return null
}
}
override fun onPostExecute(feed: RSSFeed) {
// TODO: check this.exception
// TODO: do something with the feed
}
}
致电 kotlin
MyNetworkTask().execute(url)
我有一个类似的问题。 我刚刚在您的活动的oncreate方法中使用了以下内容。
// Allow strict mode
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
而且效果很好。
需要注意的是,将其用于超过 100 毫秒的网络请求会导致明显的 UI 冻结和潜在的 ANR(应用程序无响应),因此请记住这一点。
来自开发者安卓:
理想情况下,AsyncTasks 应该用于短操作(最多几秒钟。)
使用newCachedThreadPool
是一个不错的选择。 您也可以考虑其他选项,例如newSingleThreadExecutor
、 newFixedThreadPool
ExecutorService myExecutor = Executors.newCachedThreadPool();
myExecutor.execute(new Runnable() {
@Override
public void run() {
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);
}
});
ThreadPoolExecutor是一个帮助器 class 使这个过程更容易。 这个 class 管理一组线程的创建,设置它们的优先级,并管理工作在这些线程之间的分配方式。 随着工作负载的增加或减少,class 会启动或销毁更多线程以适应工作负载。
有关 Android 线程的更多信息,请参阅此内容。
我以一种简单的方式解决了这个问题......
我在oncreate
StrictMode.enableDefaults();
之后添加并解决了这个问题。
或者
使用Service
或AsyncTask
来解决这个问题
笔记:
Do not change SDK version
Do not use a separate thread
有关更多信息, 请查看此。
由于 Android 是在单线程上工作,所以你不应该在主线程上做任何网络操作。 有多种方法可以避免这种情况。
使用以下方式进行网络操作
永远不要使用 StrictMode.setThreadPolicy(policy) ,因为它会冻结你的 UI 并且根本不是一个好主意。
您不能在主线程或 UI 线程上调用网络。 在 Android 上,如果你想调用网络,有两个选项 -
我个人更喜欢异步任务。 有关更多信息,您可以参考此链接。
如果您在 Kotlin 和Anko工作,您可以添加:
doAsync {
method()
}
这些答案需要更新以使用更现代的方式连接到 Internet 上的服务器并处理一般的异步任务。
例如,您可以找到在Google Drive API 示例中使用任务的示例。 在这种情况下应该使用相同的方法。 我将使用 OP 的原始代码来演示这种方法。
首先,您需要定义一个非主线程执行器,并且只需要执行一次:
private val mExecutor: Executor = Executors.newSingleThreadExecutor()
然后在该执行程序中处理您的逻辑,该执行程序将从主线程运行
Tasks.call (mExecutor, Callable<String> {
val url = URL(urlToRssFeed)
val factory = SAXParserFactory.newInstance()
val parser = factory.newSAXParser()
val xmlreader = parser.getXMLReader()
val theRSSHandler = RssHandler()
xmlreader.setContentHandler(theRSSHandler)
val is = InputSource(url.openStream())
xmlreader.parse(is)
theRSSHandler.getFeed()
// Complete processing and return a String or other object.
// E.g., you could return Boolean indicating a success or failure.
return@Callable someResult
}).continueWith{
// it.result here is what your asynchronous task has returned
processResult(it.result)
}
continueWith子句将在您的异步任务完成后执行,您将可以访问任务通过it.result返回的值。
您实际上可以启动一个新线程。 我之前遇到过这个问题,就通过这种方式解决了。
Android Jetpack引入WorkManager
,修复了Android 8.1 (Oreo) 后台服务限制问题, Android 5.0以下使用Alarm Manager和JobScheduler 5.0以上)
请使用WorkManager
在后台线程上运行任务,即使在用户关闭应用后它也会继续运行。
我将网络访问 function 返回值转换为暂停 function,如下所示:
suspend fun isInternetReachable(): Boolean {
...
...
return result
}
然后我修改了我使用 function 的位置以适应这个:
...
Globalscope.async{
...
result = isInternetReachable()
...
}
...
永远不要在 UI 线程上做任何长时间运行的工作。 长时间运行的工作可以是与服务器的通信、文件的读/写等。这些任务应该在后台线程上。 这就是创建Service
、 AsyncTask
和Threads
的原因。 您可以禁用StrictMode
,这将防止崩溃。 但是,从不建议这样做。
我建议您至少在调试模式下利用StrictMode
。 使用下面的代码来获取在主线程上减慢您的应用程序的任何问题的日志。
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
您可以设置不同的惩罚:
penaltyLog() // to print log
penaltyDeath() // This will crash you App(so costly penalty)
penaltyDialog() // Show alert when something went lazy on Main thread
这里有更多关于StrictMode
的信息: StrictMode | Android 开发人员。
截至 2018 年,我建议在 Kotlin 中使用RxJava进行网络获取。 下面是一个简单的例子。
Single.fromCallable {
// Your Network Fetching Code
Network.fetchHttp(url)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// What you need to do with your result on the view
result -> view.updateScreen(result)
}
不同的选择:
使用普通的 Java 可运行线程来处理网络任务,并且可以使用 runOnUIThread() 来更新 UI
如果您想在获得网络响应后更新 UI,可以使用 intentservice/ async 任务
internal class RetrieveFeedTask : AsyncTask<String, Void, RSSFeed>() {
override fun doInBackground(vararg urls: String): RSSFeed? {
try {
// download
// prepare RSSFeeds
return RSSFeeds
} catch (e: Exception) {
//handle exception
return null
}
}
override fun onPostExecute(feed: RSSFeed) {
// TODO: check this.exception
// TODO: do something with the feed
}
}
调用示例,
RetrieveFeedTask().execute(url)
这是一个使用OkHttp
、 Future
、 ExecutorService
和Callable
的简单解决方案:
final OkHttpClient httpClient = new OkHttpClient();
final ExecutorService executor = newFixedThreadPool(3);
final Request request = new Request.Builder().url("http://example.com").build();
Response response = executor.submit(new Callable<Response>() {
public Response call() throws IOException {
return httpClient.newCall(request).execute();
}
}).get();
我在 Kotlin 中使用Thread
解决了。 有很多使用 Java 的示例,所以我想在 Kotlin 中添加一个对我有用的解决方案。
Thread {
println("NEW THREAD")
callAPI() // add your own task
}.start()
因此,正如许多其他人所解释的那样,您不能在调用时阻塞主线程,因此有必要创建一个新线程。
Executors.newFixedThreadPool(3).execute(() -> {
//DO Task;
});
使用以下代码执行繁重的任务。
// Your package here
import java.util.List;
import org.apache.http.NameValuePair;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.view.View.OnSystemUiVisibilityChangeListener;
public class AsyncRequest extends AsyncTask<String, Integer, String> {
Context context;
ProgressDialog pDialog;
// Three Constructors
public AsyncRequest(Activity a, String m, List<NameValuePair> p) {
context = a;
method = m;
parameters = p;
}
public AsyncRequest(Activity a) {
this.caller = (OnAsyncRequestComplete) a;
context = a;
}
public String doInBackground(String... urls) {
//Perform your task here
return result;
}
public void onPreExecute() {
pDialog = new ProgressDialog(context);
pDialog.setMessage("Please wait..");
pDialog.setCancelable(false);
pDialog.show();
}
public void onProgressUpdate(Integer... progress) {
// You can implement some progressBar and update it in this record.
// setProgressPercent(progress[0]);
}
public void onPostExecute(String response) {
if (pDialog != null && pDialog.isShowing()) {
pDialog.dismiss();
}
// Get the result here
}
protected void onCancelled(String response) {
if (pDialog != null && pDialog.isShowing()) {
pDialog.dismiss();
}
}
}
您只需在清单标记后的文件manifest.xml中添加以下行
<uses-permission android:name="android.permission.INTERNET"/>
并在活动文件中,在绑定语句后添加如下代码:
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
Android不允许单独的进程进入主活动线程,HTTP连接在这里是一个独立的线程。 这就是您收到“ android.os.NetworkOnMainThreadException ”的原因。
可能需要在向用户显示 webview 之前检查实际的 Internet 连接,因为如果没有 Internet,web 视图将向用户显示页面未找到错误,通常您不会显示什么.
要检查 Internet 可用性,可以使用 ping 命令,但如果 Wi-Fi ping 可以在 Wi-Fi 服务器上禁用,因此在这种情况下,您使用 HTTP 连接来检查请求的状态。
如果您在向用户显示 webview 之前检查自己的 webview URL 链接,这可能是正确的方法。 在这种情况下,您可以使用 Android 的严格模式,但不要允许所有策略,因为您不需要它。
您应该只为严格模式提供网络允许策略。 只需将以下行添加到您的代码中,您就不会收到此错误。
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.