为 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 中已被弃用。
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"/>
您应该几乎总是在线程上或作为异步任务运行网络操作。
但是,如果您愿意接受后果,则可以删除此限制并覆盖默认行为。
添加:
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
我使用新的 Thread
解决了这个问题。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
//Your code goes here
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
公认的答案有一些明显的缺点。除非您真的知道自己在做什么,否则不建议使用 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。
您无法在 Honeycomb 上的 UI 线程上执行网络 I/O。从技术上讲,这是在早期版本的 Android 上是可能的,但这是一个非常糟糕的主意,因为它会导致您的应用停止响应,并可能导致操作系统因您的应用表现不佳而杀死您的应用。您需要运行后台进程或使用 AsyncTask 在后台线程上执行网络事务。
Android 开发者网站上有一篇关于 Painless Threading 的文章对此进行了很好的介绍,它将为您提供比此处实际提供的更深入的答案。
这个问题有两种解决方案。
不要在主 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;
在另一个线程上执行网络操作。
例如:
new Thread(new Runnable(){
@Override
public void run() {
// Do network action in this function
}
}).start();
并将其添加到文件 AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
不要使用 strictMode(仅在调试模式下) 不要更改 SDK 版本 不要使用单独的线程
使用 Service 或 AsyncTask
另请参阅堆栈溢出问题:
android.os.NetworkOnMainThreadException sending an email from Android
您可以使用以下代码禁用严格模式:
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
基于网络的操作不能在主线程上运行。您需要在子线程上运行所有基于网络的任务或实现 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 Annotations 是一个选项。它将允许您简单地在后台线程中运行任何方法:
// 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
。因此,您可以根据自己的要求使用其中任何一种。
顶部 answer of spektom 完美。
如果您正在编写 AsyncTask
内联而不是作为类扩展,并且除此之外,如果需要从 AsyncTask
中获取响应,则可以使用如下 get()
方法。
RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();
(来自他的例子。)
对我来说是这样的:
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="10" />
我正在测试我的应用程序的设备是 4.1.2,即 SDK 版本 16!
确保目标版本与您的 Android 目标库相同。如果您不确定您的目标库是什么,请右键单击您的项目 -> 构建路径 -> Android,它应该是勾选的那个。
此外,正如其他人所提到的,包括访问 Internet 的正确权限:
<uses-permission android:name="android.permission.INTERNET"/>
NetworkOnMainThreadException
是守护者,它告诉你:不要朝自己的脚开枪......你的解决方案是:让我们回到没有守护者的过去 - 现在我可以自由射击我的脚
在您的活动中使用它
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.
}
});
这个问题已经有很多很棒的答案,但是自从这些答案发布以来,已经出现了很多很棒的库。这是一种新手指南。
我将介绍几个用于执行网络操作的用例,并为每个用例提供一两个解决方案。
基于 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 Java 或 Volley 等其他库(网站: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 answer 至 How 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 的另一个优秀库。请参阅网站以获取一些很好的示例。
简单来说,
不要在 UI 线程中做网络工作
例如,如果您执行 HTTP 请求,那就是网络操作。
解决方案:
您必须创建一个新线程或使用 AsyncTask 类
方法:
把你所有的作品都放进去
新线程的 run() 方法或 AsyncTask 类的 doInBackground() 方法。
但:
当您从网络响应中获取某些内容并希望将其显示在您的视图中时(例如在 TextView 中显示响应消息),您需要返回到 UI 线程。
如果您不这样做,您将获得 ViewRootImpl$CalledFromWrongThreadException
。
如何
在使用 AsyncTask 时,从 onPostExecute() 方法更新视图或调用 runOnUiThread() 方法并在 run() 方法中更新视图。
您可以将部分代码移动到另一个线程以卸载 main thread
并避免出现 ANR、NetworkOnMainThreadException、IllegalStateException(例如,无法访问主线程上的数据库,因为它可能会锁定 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 来救场的原因
实现 ExecutorService 的 ThreadPoolExecutor 类可以对线程池进行精细控制(例如,核心池大小、最大池大小、保持活动时间等) ScheduledThreadPoolExecutor - 扩展 ThreadPoolExecutor 的类。它可以在给定的延迟后或定期安排任务。
FutureTask 执行异步处理,但是如果结果尚未准备好或处理尚未完成,调用 get() 将阻塞线程
AsyncTaskLoaders 因为它们解决了 AsyncTask 固有的许多问题
这是在 Android 上长时间运行处理的实际选择,一个很好的例子是上传或下载大文件。即使用户退出应用程序,上传和下载仍可能继续,并且您当然不希望在这些任务进行时阻止用户使用应用程序。
实际上,您必须创建一个服务并使用 JobInfo.Builder 创建一个作业,它指定何时运行该服务的标准。
使用可观察序列组成异步和基于事件的程序的库。
Coroutines(科特林)
它的主要要点是,它使异步代码看起来很像同步
已经解释了新的 Thread
和 AsyncTask 解决方案。
AsyncTask
最好用于短操作。普通 Thread
不适用于 Android。
查看使用 HandlerThread 和 Handler 的替代解决方案
处理线程
用于启动具有弯针的新线程的便捷类。然后可以使用 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。
科特林
如果您使用的是 Kotlin,则可以使用 coroutine:
fun doSomeNetworkStuff() {
GlobalScope.launch(Dispatchers.IO) {
// ...
}
}
虽然上面有一个巨大的解决方案池,但没有人提到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
}
});
这行得通。我只是让 Dr.Luiji's answer 简单一点。
new Thread() {
@Override
public void run() {
try {
//Your code goes here
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
主线程是 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
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() 回调。