我对Android中的
Handlers
,AsyncTask
和Threads
之间的区别感到有些困惑。我已经在StackOverflow上阅读了很多博客和问题。 Handler
是后台线程,可让您与UI进行通信。例如,应通过Handler
完成进度条的更新。使用处理程序具有MessagingQueues
的优势,因此,如果您要计划消息或更新多个UI元素或有重复的任务。 t可以在UI线程中运行,因此对于获取数据(例如获取Web服务)非常有用。稍后您可以与UI进行交互。AsyncTask
但是无法与UI进行交互,提供更多的“基本”线程,并且您会错过Handler
的所有抽象。 ,我想在服务中运行套接字连接。应该在处理程序或线程中运行,还是在Thread
中运行?完全不需要UI交互。我使用的性能是否有所不同?同时,文档已得到重大改进。
#1 楼
正如使用Handlers进行Android后台处理的教程一样,Vogella网站上的AsyncTask和Loaders指出:Handler
类可用于注册到线程,并提供简单的通道将数据发送到该线程。 。AsyncTask
类封装了后台进程的创建以及与主线程的同步。它还支持报告正在运行的任务的进度。Thread
基本上是多线程的核心元素,开发人员可以使用它,但有以下缺点:如果使用Java线程,则必须在自己的代码中满足以下要求:
如果将结果回传到用户界面,则与主线程同步
没有默认的取消线程
没有默认的线程池
没有默认的用于处理Android中的配置更改
并且将
AsyncTask
视为Android Developer's Reference指出:AsyncTask
支持正确且轻松地使用UI线程。此类允许执行后台操作并在UI
线程上发布结果,而无需操纵线程和/或处理程序。
AsyncTask
设计为Thread
的辅助类。和Handler
,并且不构成通用线程框架。理想情况下,AsyncTasks
应该用于短操作(最多几秒钟。)如果需要长时间保持线程运行,
强烈建议您使用AsyncTasks
java.util.concurrent包提供的API,例如Executor,ThreadPoolExecutor和
FutureTask。
2015年5月更新:我发现了一系列精彩的演讲,涵盖了此主题。
这是Google搜索:Douglas Schmidt讲授android并发和同步
这是YouTube上第一次演讲的视频。
这是范德比尔特大学CS 282(2013):Android系统编程的一部分。这是YouTube播放列表
道格拉斯·施密特(Douglas Schmidt)似乎是一位出色的讲师
重要提示:如果您正考虑使用
AsyncTask
解决您的问题,线程问题,您应该首先查看ReactiveX/RxAndroid
以获得更合适的编程模式。例如,学习Android的RxJava 2是获得概述的很好资源。评论
在该系列讲座中,此链接将带您直接进入一些线程示例:youtu.be/4Vue_KuXfCk?t=19m24s
–侵略者
15年8月20日在21:33
#2 楼
如果看一下源代码,我们将看到AsyncTask
和Handler
完全是用Java编写的。 (尽管有一些例外。但这并不是重点。)所以
AsyncTask
或Handler
中没有魔术。这些类使我们的开发人员的生活变得更轻松。例如,如果程序A调用方法A(),则方法A()可以与程序A在不同的线程中运行。代码:Thread t = Thread.currentThread();
int id = t.getId();
为什么对于某些任务我们应该使用新线程?你可以用谷歌搜索它。
因此,
Thread
,AsyncTask
和Handler
之间有什么区别?AsyncTask
和Handler
被写成什么? Java(内部使用的是Thread
),因此使用Handler
或AsyncTask
可以做的一切,也可以使用Thread
来实现。 >最明显的原因是调用者线程和工作线程之间的通信。(调用者线程:调用工作线程执行一些任务的线程。调用者线程不一定是UI线程) 。当然,我们可以通过其他方式在两个线程之间进行通信,但是由于线程安全性,存在许多缺点(和危险)。这就是为什么我们应该使用
Handler
和AsyncTask
的原因。这些类为我们完成了大多数工作,我们只需要知道要覆盖哪些方法即可。Handler
和AsyncTask
之间的区别是:当调用者线程是UI线程时,请使用Handler
。这是android文档所说的:
AsyncTask可以正确方便地使用UI线程。此类
允许执行后台操作并在UI
线程上发布结果,而无需操纵线程和/或处理程序
我想强调两点:
1)易于使用的UI线程(因此,当调用者线程是UI线程时使用)。
2)无需操纵处理程序。 (意味着:您可以使用Handler而不是AsyncTask,但是AsyncTask是一个更简单的选择)。
这篇文章中有很多我还没有说的东西,例如:什么是UI Thread,或者为什么更容易。您必须了解每个类背后的一些方法并使用它们,您将完全理解其原因。
@:当您阅读Android文档时,您将看到:
Handler使您可以发送和处理与线程的MessageQueue相关联的Message和Runnable对象。
起初该描述可能看起来很奇怪。我们只需要了解每个线程都有每个消息队列(如待办事项列表),线程将接收每个消息并执行直到消息队列为空(就像我们完成工作并上床睡觉一样)。因此,当
AsyncTask
进行通信时,它仅向呼叫者线程提供一条消息,它将等待处理。 复杂吗?请记住,
AsyncTask
可以与调用方线程安全地通信。评论
实际上asynctask也基于处理程序和futuretask,请参阅
– Sumit
16年8月23日在6:16
AsyncTask本质上是在Handler和Thread之上构建的帮助器类。 developer.android.com/reference/android/os/AsyncTask.html。看一下文档“ AsyncTask被设计为围绕线程和处理程序的帮助器类”。从API1开始,AsyncTask在API3中发布,而处理程序存在。
– hjchin
16年11月9日,2:09
#3 楼
在深入研究之后,它是直截了当的。AsyncTask
:这是使用线程而又不了解Java线程模型的简单方法。
AsyncTask
提供有关工作线程和主线程的各种回调。用于小的等待操作,如下所示:
从Web服务获取一些数据并显示在布局。
数据库查询。
当您意识到运行操作将永远不会被嵌套。
Handler
:当我们安装应用程序时在android中,然后为该应用程序创建一个名为MAIN UI Thread的线程。所有活动都在该线程内运行。根据android单线程模型规则,我们无法直接访问该活动中定义的另一个线程的UI元素(位图,文本视图等)。
处理程序允许您与UI通讯回来自其他背景线程的线程。这在android中非常有用,因为android不允许其他线程直接与UI线程进行通信。处理程序可以发送和处理与线程的MessageQueue相关的Message和Runnable对象。每个处理程序实例都与一个线程和该线程的消息队列关联。创建新的Handler时,它将绑定到正在创建它的线程的线程/消息队列中。
最适合:
它允许您进行消息排队。
消息调度。
Thread
:现在是时候讨论线程了。
线程是
AsyncTask
和Handler
的父代。它们都在内部使用线程,这意味着您还可以创建自己的线程模型,例如AsyncTask
和Handler
,但这需要对Java的多线程实现有充分的了解。评论
实际上,AsyncTask api是用Futures,Handlers和Executors编写的。请参阅源代码:grepcode.com/file_/repository.grepcode.com/java/ext/…
– IgorGanapolsky
16-10-10在16:39
#4 楼
使用AsyncTask
进行一些后台计算,并将结果发布到UI线程(带有可选的进度更新)。由于您不关心UI,因此Handler
或Thread
似乎更合适。您可以使用
Thread
的Handler
方法生成背景post
并将消息传递回主线程。 br />#5 楼
线程Android支持标准Java线程。您可以使用标准线程和软件包“
java.util.concurrent
”中的工具将操作放入后台。唯一的限制是您不能直接从后台进程更新UI。如果需要从后台任务更新UI,则需要使用某些Android特定的类。您可以为此使用类别“
android.os.Handler
”,也可以使用类别“ AsyncTask
” 处理程序
类别“
Handler
”可以更新UI。句柄提供了用于接收消息和可运行对象的方法。要使用处理程序,您必须将其子类化并重写handleMessage()
来处理消息。要处理Runable
,可以使用post();
方法。您的活动中仅需要一个处理程序实例。您可以通过
sendMessage(Message msg)
或sendEmptyMessage
方法发布消息。 AsyncTask
如果您有
Activity
需要下载内容或执行可以在后台执行的操作,则AsyncTask
允许您维护响应式用户界面并发布进度这些操作将提供给用户。有关更多信息,请查看这些链接。
http://mobisys.in/blog/2012/01/android-线程处理程序和异步任务教程/
http://www.slideshare.net/HoangNgoBuu/android-thread-handler-and-asynctask
#6 楼
Thread
:您可以将新的
Thread
用于长时间运行的后台任务,而不会影响UI线程。从Java Thread,您无法更新UI Thread。 由于普通的Thread对于Android体系结构不是很有用,因此引入了用于线程的帮助程序类。
您可以在“线程性能文档”页面中找到查询的答案。
Handler
允许您发送和处理Message和Runnable
与线程的MessageQueue
关联的对象。每个Handler
实例都与一个线程和该线程的消息队列关联。Handler
有两个主要用途:将消息和可运行程序调度到在将来的某个时刻被执行;
使操作可以在与您自己的线程不同的线程上执行。
AsyncTask:
AsyncTask
可以正确方便地使用UI线程。此类允许您执行后台操作并在UI线程上发布结果,而无需操纵线程和/或处理程序。缺点:
默认情况下,应用会将创建的所有
AsyncTask
对象推送到一个线程中。因此,它们以串行方式执行,并且与主线程一样,特别长的工作包可能会阻塞队列。由于这个原因,请使用AsyncTask处理持续时间少于5毫秒的工作项。AsyncTask
对象也是隐式引用问题的最常见违法者。 AsyncTask
对象也存在与显式引用相关的风险。您可能需要更传统的方法来在长时间运行的线程上执行工作块(与AsyncTask不同,后者应用于5毫秒的工作量),并具有手动管理该工作流程的能力。处理程序线程实际上是一个长期运行的线程,它从队列中获取工作并对其进行操作。此类管理一组线程的创建,设置它们的优先级,并管理工作在这些线程之间的分配方式。随着工作负载的增加或减少,该类将旋转或破坏更多线程以适应工作负载。
如果工作负载更多且单个
HandlerThread
不足够,则可以使用ThreadPoolExecutor
但是我想在服务中运行套接字连接。应该在处理程序或线程中运行,还是在AsyncTask中运行?完全不需要UI交互。
由于不需要UI交互,因此您可能不喜欢
AsyncTask
。普通线程不是很有用,因此HandlerThread
是最佳选择。由于必须维护套接字连接,因此主线程上的Handler根本没有用。创建一个HandlerThread
并从Handler
的循环器中获取HandlerThread
。 HandlerThread handlerThread = new HandlerThread("SocketOperation");
handlerThread.start();
Handler requestHandler = new Handler(handlerThread.getLooper());
requestHandler.post(myRunnable); // where myRunnable is your Runnable object.
如果要与UI线程通信,可以使用一个以上的Handler来处理响应。
final Handler responseHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
//txtView.setText((String) msg.obj);
Toast.makeText(MainActivity.this,
"Foreground task is completed:"+(String)msg.obj,
Toast.LENGTH_LONG)
.show();
}
};
在您的
Runnable
中,您可以添加responseHandler.sendMessage(msg);
有关实现的更多详细信息,请参见:
Android:在线程中吐司
#7 楼
我认为线程并不是进行套接字连接的最有效方法,但是就运行线程而言,它们确实提供了最多的功能。我说这是因为从经验来看,长时间运行线程会导致设备非常热并且占用大量资源。即使是简单的while(true)
也可以在几分钟内加热手机。如果您说UI交互并不重要,那么AsyncTask
也许是不错的选择,因为它们是为长期过程而设计的。这只是我的意见。UPDATE
请忽略我上面的回答!我在2011年回答了这个问题,当时我对Android的经验远不如现在。我上面的回答是误导性的,被认为是错误的。我将其保留在这里,是因为很多人在纠正我的意见下方对此进行了评论,并且我已经吸取了教训。
在该主题上还有其他更好的答案,但我至少会给我更多正确的答案。使用常规Java
Thread
没什么错;但是,您应该非常小心地实现它,因为错误地执行可能会占用大量处理器资源(最明显的症状可能是设备发热)。 AsyncTask
非常适合您要在后台运行的大多数任务(常见示例是磁盘I / O,网络调用和数据库调用)。但是,AsyncTask
不应用于特别长的过程,在用户关闭您的应用程序或将其设备置于待机状态后,可能需要继续此过程。我会说,在大多数情况下,可以在AsyncTask
中处理不属于UI线程的任何内容。评论
谢谢,实际上是我应该使用线程而不是AsyncTasks的原因吗?还是更推荐使用它?
– Alx
2011年8月6日,0:56
@AeroDroid在您的示例中:“一个简单的while(true)”,除非在循环中添加睡眠状态,否则您将在此处固定CPU。任何无限循环都是如此。如果由于此开销而要减少CPU使用率,请在循环结束时将线程休眠几毫秒。
–错误454
2011年9月7日19:34
@错误454-很有意思!如果您必须为睡眠时间选择一个合适的数字,它是否在40-80毫秒之间?
–阿比吉特
2011年11月10日,下午4:20
@Abhijit从我在SDL中所做的游戏工作来看,只需在循环中添加10毫秒的睡眠就足以在空闲状态下将99%的cpu降至〜0。
–错误454
2011年11月11日19:36
实际上,developer.android.com / reference / android / os / AsyncTask.html表示:“理想情况下,AsyncTasks应该用于SHORT操作”。您还应谨慎使用它们,因为它们可能会在不执行的情况下被系统删除!
–type-a1pha
2013年7月12日14:35
#8 楼
AsyncTask
旨在在后台执行不超过几秒钟的操作(不建议从服务器下载兆字节的文件或计算CPU密集型任务,例如文件IO操作)。如果需要执行长时间运行的操作,强烈建议您使用Java本机线程。 Java为您提供了各种与线程相关的类,以执行所需的操作。使用Handler
更新UI线程。#9 楼
public class RequestHandler {
public String sendPostRequest(String requestURL,
HashMap<String, String> postDataParams) {
URL url;
StringBuilder sb = new StringBuilder();
try {
url = new URL(requestURL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(15000);
conn.setConnectTimeout(15000);
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, "UTF-8"));
writer.write(getPostDataString(postDataParams));
writer.flush();
writer.close();
os.close();
int responseCode = conn.getResponseCode();
if (responseCode == HttpsURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
sb = new StringBuilder();
String response;
while ((response = br.readLine()) != null){
sb.append(response);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
private String getPostDataString(HashMap<String, String> params) throws UnsupportedEncodingException {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> entry : params.entrySet()) {
if (first)
first = false;
else
result.append("&");
result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
result.append("=");
result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
return result.toString();
}
}
#10 楼
让我尝试用一个示例来回答这个问题:)-MyImageSearch [请在这里参考主活动屏幕的图像-包含编辑文本/搜索按钮/网格视图]MyImageSearch的描述-用户在编辑文本字段中输入详细信息并单击搜索按钮后,我们将通过flickr提供的网络服务在Internet上搜索图像(您只需在此处注册即可获得键/秘密令牌)-为了进行搜索,我们将包含单个图像的url作为响应发送回HTTP请求和GET JSON数据,然后将其用于加载网格视图。
我的实现-在主要活动中,我将定义一个内部类,该类扩展AsyncTask以在doInBackGround方法中发送HTTP请求并获取JSON响应,并更新FlickrItems的本地ArrayList,我将使用它通过FlickrAdapter来更新GridView(扩展BaseAdapter)并调用adapter.notifyDataSetCha nged()在AsyncTask的onPostExecute()中,以重新加载网格视图。请注意,这里的HTTP请求是一个阻塞调用,因此我已经通过AsyncTask完成了该请求。而且,我可以将这些项目缓存在适配器中以提高性能或将其存储在SDCard中。我将在FlickrAdapter中扩大的网格在我的实现中包含一个进度栏和图像视图。在下面,您可以找到我使用的mainActivity的代码。
现在回答问题-
因此,一旦有了用于获取单个图像的JSON数据,我们就可以实现获取图像的逻辑通过处理程序或线程或AsyncTask在后台运行。我们应该在这里注意,由于我的图像一旦下载就必须显示在UI /主线程上,我们不能简单地按原样使用线程,因为它们无法访问上下文。
在FlickrAdapter中,我可以选择想一想:
选择1:创建一个LooperThread [扩展线程]-并继续
通过保持该线程在一个线程中顺序下载图像
打开[looper.loop()]
选择2:使用线程池并通过myHandler发布可运行对象,该对象包含对我的引用ImageView,但是由于Grid View中的视图已回收,因此再次可能会出现问题,即在索引9中显示索引4中的图像
[下载可能需要更多时间]
选择3 [I使用此]:利用线程池并向myHandler发送一条消息,其中包含与ImageView的索引和
ImageView本身相关的数据,因此在执行handleMessage()时,仅当currentIndex时,我们才会更新
ImageView匹配我们
要下载的图像的索引。
选择4:利用AsyncTask在后台下载
图像,但是在这里我将无法访问线程数想要在
中使用线程池,并且它随Android版本的不同而变化,但是在选择3中,我可以根据设备的大小有意识地决定线程池的大小正在使用e配置。
源代码为:
public class MainActivity extends ActionBarActivity {
GridView imageGridView;
ArrayList<FlickrItem> items = new ArrayList<FlickrItem>();
FlickrAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageGridView = (GridView) findViewById(R.id.gridView1);
adapter = new FlickrAdapter(this, items);
imageGridView.setAdapter(adapter);
}
// To avoid a memory leak on configuration change making it a inner class
class FlickrDownloader extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
FlickrGetter getter = new FlickrGetter();
ArrayList<FlickrItem> newItems = getter.fetchItems();
// clear the existing array
items.clear();
// add the new items to the array
items.addAll(newItems);
// is this correct ? - Wrong rebuilding the list view and should not be done in background
//adapter.notifyDataSetChanged();
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
adapter.notifyDataSetChanged();
}
}
public void search(View view) {
// get the flickr data
FlickrDownloader downloader = new FlickrDownloader();
downloader.execute();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
我希望我的回答很长,虽然有助于理解一些更详细的细节。
评论
我可以知道为什么我以类比为例对我的解释进行否决的原因,以便我也可以从中学习吗?
– akshaymani
2014年8月27日在7:10
首先,感谢您的答复,尽管本主题有些陈旧,但核心概念仍然保持最新。我最初的问题根本没有得到回答,您只是给出一个示例并解释其工作原理,但是这些问题要求在处理程序,asynctask和线程之间存在差异。
– Alx
2014年9月21日在9:07
@ 80leaves好的,我现在明白了,我试图解释如何得出一种选择另一种方法的结论。无论如何,很想听听您/他人对我写的内容是否正确或是否可以进一步改进的看法。
– akshaymani
2014-09-27 6:45
#11 楼
处理程序通常用于从其他线程切换到主线程,而处理程序则附加到一个循环程序,在该循环程序上将其可运行任务发布到队列中。 />因此,如果您已经在其他线程中并切换到主线程,则需要句柄而不是异步任务或其他线程
如果不是在非线程的非主线程中创建的处理程序,则不会在创建线程的句柄时产生错误,该线程需要变钝
AsyncTask用于执行代码几秒钟,该代码在后台线程上运行并将其结果提供给主线程
** * AsyncTask限制
1。异步任务未附加到活动的生命周期中,即使它的活动被破坏,加载器也保持运行,而加载程序没有此限制
2。所有异步任务共享相同的后台线程来执行,这也会影响应用程序性能
线程也用于应用程序中以进行后台工作,但主线程上没有任何回调。
如果要求适合某些线程而不是一个线程,并且需要多次执行任务,则线程池执行器是更好的选择。例如,从多个URL加载图像的要求,例如glide。
#12 楼
线程启动应用程序时,将创建一个过程来执行代码。为了有效地使用计算资源,可以在进程内启动线程,以便可以同时执行多个任务。因此,线程使您可以通过有效利用cpu来构建高效的应用程序,而无需花费空闲时间。 Android系统将任务排队,并在主线程上一个接一个地执行它们。当执行长时间运行的任务时,应用程序将无响应。
为防止这种情况,您可以创建工作线程并运行后台或长时间运行的任务。
处理程序
由于android使用单线程模型,所以UI组件是非线程安全的,这意味着只有它创建的线程才可以访问它们,这意味着UI组件只能在主线程上进行更新。由于UI组件在主线程上运行,因此在工作线程上运行的任务无法修改UI组件。这是Handler出现的地方。借助Looper的处理程序可以连接到新线程或现有线程,并在连接的线程上运行它包含的代码。
处理程序使线程间通信成为可能。使用处理程序,后台线程可以向其发送结果,并且连接到主线程的处理程序可以更新主线程上的UI组件。
AsyncTask
AsyncTask由android提供同时使用线程和处理程序来使在后台运行简单任务以及轻松地将结果从后台线程更新到主线程。
示例请参见android线程,处理程序,asynctask和线程池。
#13 楼
Handler
-是线程之间的通信介质。在android中,它主要用于通过处理程序创建和发送消息来与主线程通信。AsyncTask
-用于在后台线程中执行长时间运行的应用程序。使用n AsyncTask
,您可以在后台线程中执行操作,并在应用程序的主线程中获取结果。 Thread
-是一种轻量级进程,可实现并发和最大的CPU使用率。在android中,您可以使用线程执行不触摸应用程序UI的活动
评论
值得一试:Douglas Schmidt讲授android并发和同步“处理程序是后台线程”-一些最受好评的答案似乎也朝着这个方向发展。但这是一个误解。处理程序不是线程,也不执行任何操作。这只是将消息从一个线程安全地传递到另一个线程的消息队列的一种方法。因此,通常,(至少)必须仍然创建两个线程,然后可以使用处理程序,但是处理程序本身无法执行任何操作。