处理程序与AsyncTask与线程[关闭]


382

我得到了稍微感到困惑之间的差异HandlersAsyncTaskThreads在Android中。我已经在StackOverflow上阅读了很多博客和问题。

Handler是提供您与UI进行通信的后台线程。例如,更新进度条应通过进行Handler。使用处理程序具有的优势MessagingQueues,因此,如果您要计划消息或更新多个UI元素或具有重复任务。

AsyncTask相似,实际上,它们使用Handler,但没有在UI线程中运行,因此对于获取数据(例如获取Web服务)非常有用。稍后,您可以与UI进行交互。

Thread但是不能与UI交互,提供更多的“基本”线程,您会错过的所有抽象AsyncTask

但是,我想在服务中运行套接字连接。应该在处理程序,线程还是什至是线程中运行它AsyncTask吗?完全不需要UI交互。我使用的性能是否有所不同?

同时,文档已得到重大改进。



9
“处理程序是后台线程”-一些最受好评的答案似乎也朝着这个方向发展。但这是一个误解。A Handler不是线程,也不执行任何操作。它只是一种手段,以安全地从一个传递消息线程到另一个消息队列的线程。因此,通常,(至少)仍然必须创建两个线程,然后可以使用处理程序,但是处理程序本身无法执行任何操作。
JimmyB

Answers:


57

正如使用Handlers进行Android后台处理的教程中一样,Vogella网站上的AsyncTask和Loaders指出:

Handler类可用于注册到一个线程并提供了一种简单的信道将数据发送到该线程。

所述AsyncTask类封装后台进程的创建,并与主线程同步。它还支持报告正在运行的任务的进度。

并且a Thread基本上是开发人员可以使用的多线程的核心元素,具有以下缺点:

如果使用Java线程,则必须在自己的代码中满足以下要求:

  • 如果将结果回传到用户界面,则与主线程同步
  • 没有默认的取消线程
  • 没有默认的线程池
  • 没有默认值来处理Android中的配置更改

关于AsyncTask,正如Android开发者参考中所述

AsyncTask可以正确,轻松地使用UI线程。此类允许执行后台操作并在UI线程上发布结果,而无需操纵线程和/或处理程序。

AsyncTask被设计为辅助类ThreadHandler 并且不构成通用线程框架。理想情况下,应将AsyncTasks用于较短的操作(最多几秒钟)。如果需要使线程长时间运行,则强烈建议您使用java.util.concurrent软件包提供的各种API,例如执行程序,ThreadPoolExecutor和FutureTask。

2015年5月更新:我发现了一系列关于该主题的精彩演讲

这是Google搜索:Douglas Schmidt讲授android并发和同步

这是YouTube上第一次演讲的视频

这一切是部分CS 282(2013):系统为Android编程来自范德比尔特大学。这是YouTube播放列表

道格拉斯·施密特(Douglas Schmidt)似乎是一位出色的讲师

重要:如果您正考虑使用它AsyncTask来解决线程问题,则应该首先检查一下ReactiveX/RxAndroid可能更合适的编程模式。例如,学习RxJava 2 for Android是获得概述的一个很好的资源。


4
在该系列讲座中,此链接将带您直接进入一些线程示例:youtu.be/4Vue_KuXfCk?t=19m24s
Aggressor

353

如果我们查看源代码,我们将看到AsyncTask并且Handler纯粹是用Java编写的。(尽管有一些例外。但这并不重要)

因此,AsyncTask或中没有魔术Handler。这些课程使我们作为开发人员的生活更加轻松。

例如:如果程序A调用方法A(),则方法A()可以与程序A在不同的线程中运行。我们可以通过以下代码轻松地进行验证:

Thread t = Thread.currentThread();    
int id = t.getId();

我们为什么要对某些任务使用新线程?你可以用谷歌搜索它。原因很多,例如:繁重,长期运行。

那么,什么是之间的差异ThreadAsyncTask以及Handler

AsyncTask并且Handler是用Java编写的(内部使用Thread),因此我们可以使用Handler或进行的所有操作AsyncTask也可以使用来实现Thread

有什么可以HandlerAsyncTask真正的帮助?

最明显的原因是调用者线程和工作线程之间的通信。(Caller Thread:一个调用Worker线程执行某些任务的线程。Caller线程不必一定是UI线程)。当然,我们可以通过其他方式在两个线程之间进行通信,但是由于线程安全性,存在许多缺点(和危险)。

这就是为什么我们应该使用HandlerAsyncTask。这些类为我们完成了大部分工作,我们只需要知道要覆盖哪些方法即可。

Handler和之间的区别AsyncTask是:AsyncTask调用者线程UI线程时使用。这就是android文档所说的:

通过AsyncTask,可以正确,轻松地使用UI线程。此类允许执行后台操作并在UI线程上发布结果,而无需操纵线程和/或处理程序

我想强调两点:

1)易于使用的UI线程(因此,在调用者线程为UI线程时使用)。

2)无需操纵处理程序。(意味着:您可以使用Handler而不是AsyncTask,但是AsyncTask是一个更简单的选项)。

这篇文章中有很多事情我还没有说,例如:什么是UI Thread,或者为什么它更容易。您必须了解每个类背后的一些方法并使用它们,您将完全理解其原因。

@:阅读Android文档时,您将看到:

处理程序允许您发送和处理与线程的MessageQueue相关的Message和Runnable对象

起初,这种描述可能看起来很奇怪。我们只需要了解每个线程都有每个消息队列(如待办事项列表),线程就会接收每个消息并执行直到消息队列为空(就像我们完成工作并上床睡觉一样)。因此,在进行Handler通信时,它仅向呼叫者线程提供一条消息,它将等待处理。

复杂?请记住,Handler可以安全地与调用方线程进行通信。


4
实际上asynctask也基于处理程序和futuretask,请参见
Sumit

AsyncTask本质上是一个在Handler和Thread之上构建的帮助器类。developer.android.com/reference/android/os/AsyncTask.html。查看文档“ AsyncTask被设计为围绕线程和处理程序的帮助器类”。自API1开始,AsyncTask在API3中发布,而处理程序存在。
hjchin

52

在深入研究之后,这很简单。

AsyncTask

这是使用线程的简单方法,无需了解Java线程模型AsyncTask给工作线程和主线程分别提供各种回调。

用于小型等待操作,如下所示:

  1. 从Web服务中获取一些数据并在布局上显示。
  2. 数据库查询。
  3. 当您意识到运行操作将永远不会被嵌套。

Handler

当我们在android中安装应用程序时,它会为该应用程序创建一个线程,称为MAIN UI Thread。所有活动都在该线程内运行。根据android单线程模型规则,我们不能直接为该活动内定义的另一个线程访问UI元素(位图,文本视图等)。

处理程序使您可以从其他后台线程与UI线程进行通讯。这在android中很有用,因为android不允许其他线程直接与UI线程通信。处理程序可以发送和处理与线程的MessageQueue关联的Message和Runnable对象。每个Handler实例都与一个线程和该线程的消息队列关联。创建新的处理程序后,它将绑定到正在创建它的线程的线程/消息队列。

最适合:

  1. 它允许您进行消息排队。
  2. 消息调度。

Thread

现在是时候讨论线程了。

线程是双方的父母AsyncTaskHandler。它们都在内部使用线程,这意味着您还可以AsyncTask和一样创建自己的线程模型Handler,但这需要对Java的多线程实现有所了解。


1
实际上,AsyncTask api是用Futures,Handlers和Executors编写的。参见源代码:grepcode.com/file_/repository.grepcode.com/java/ext/…–
IgorGanapolsky

22

An AsyncTask用于执行一些后台计算并将结果发布到UI线程(带有可选的进度更新)。由于您不关心UI,因此HandlerThread似乎更合适。

您可以生成一个背景Thread和传递消息通过回你的主线程 Handlerpost方法。


9

线

Android支持标准Java 线程。您可以使用标准线程和包“ java.util.concurrent”中的工具将操作置于后台。唯一的限制是您不能从后台进程直接更新UI。

如果需要从后台任务更新UI,则需要使用一些Android特定的类。您可以android.os.Handler为此使用“ ”类或“ AsyncTask” 类

处理程序

类“ Handler”可以更新UI。句柄提供了用于接收消息和可运行对象的方法。要使用处理程序,您必须将其子类化并重写handleMessage()以处理消息。要进行处理Runable,可以使用方法post();您的活动中仅需要一个处理程序实例。

您可以通过方法sendMessage(Message msg)或来发布消息sendEmptyMessage

异步任务

如果您有Activity需要下载内容或执行可以在后台完成的操作的操作,则可以AsyncTask维护响应型用户界面,并将这些操作的进度发布给用户。

有关更多信息,请查看这些链接。

http://mobisys.in/blog/2012/01/android-threads-handlers-and-asynctask-tutorial/

http://www.slideshare.net/HoangNgoBuu/android-thread-handler-and-asynctask


6

Thread

您可以将新功能Thread用于长时间运行的后台任务,而不会影响UI线程。从Java Thread,您无法更新UI Thread。

由于普通Thread对于Android体系结构不是很有用,因此引入了用于线程的帮助程序类。

您可以在“ 线程性能”文档页面中找到查询的答案。

处理程序

A Handler允许您发送和处理Runnable与线程关联的Message和对象MessageQueue。每个Handler实例都与一个线程和该线程的消息队列关联。

的主要用途有两个Handler

  1. 计划消息和可运行对象在将来的某个时刻执行;

  2. 排队要在与您自己不同的线程上执行的操作。

AsyncTask

AsyncTask可以正确,轻松地使用UI线程。此类允许您执行后台操作并在UI线程上发布结果,而无需操纵线程和/或处理程序。

缺点:

  1. 默认情况下,应用将AsyncTask其创建的所有对象推送到单个线程中。因此,它们以串行方式执行,并且与主线程一样,特别长的工作包可能会阻塞队列。由于这个原因,请使用AsyncTask处理持续时间少于5 毫秒的工作项。

  2. AsyncTask对象也是隐式引用问题的最常见违法者。AsyncTask对象也存在与显式引用有关的风险。

HandlerThread

您可能需要更传统的方法来在长时间运行的线程上执行工作块(与AsyncTask不同,后者应用于5ms工作负载),并需要具有手动管理该工作流的能力。处理程序线程实际上是一个长期运行的线程,它从队列中获取工作并对其进行操作。

ThreadPoolExecutor

此类管理一组线程的创建,设置它们的优先级,并管理工作在这些线程之间的分配方式。随着工作负载的增加或减少,该类启动或销毁更多线程以适应工作负载。

如果工作量更多而HandlerThread单靠工作还不够,您可以选择ThreadPoolExecutor

但是我想在服务中运行套接字连接。应该在处理程序或线程中运行,还是在AsyncTask中运行?完全不需要UI交互。我使用的性能是否有所不同?

由于不需要进行UI交互,因此您不可以使用AsyncTask。普通线程不是很有用,因此HandlerThread是最佳选择。由于必须维护套接字连接,因此主线程上的Handler根本没有用。创建一个HandlerThreadHandler从的弯针中获取一个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:在线程中吐司


5

在我看来,线程并不是进行套接字连接的最有效方法,但是就运行线程而言,它们确实提供了最多的功能。我说这是因为根据经验,长时间运行线程会导致设备非常热并且占用大量资源。即使是简单的设备while(true)也可以在几分钟内加热手机。如果您说UI交互并不重要,那也许AsyncTask是件好事,因为它们是为长期流程而设计的。这只是我的看法。

更新

请忽略我上面的答案!我在2011年回答了这个问题,当时我对Android的经验远不如现在。我上面的回答是误导性的,被认为是错误的。我把它留在那里,是因为很多人在纠正我的情况下对此发表了评论,并且我已经吸取了教训。

在该线程上还有其他更好的答案,但是我至少会给我更合适的答案。使用常规Java没有错Thread; 但是,您应该非常小心地实现它,因为错误地执行可能会占用大量处理器资源(最明显的症状可能是设备发热)。AsyncTask对于大多数要在后台运行的任务,s是非常理想的(常见的示例是磁盘I / O,网络调用和数据库调用)。但是,AsyncTask不应将s用于特别长的过程,而在用户关闭您的应用程序或将其设备置于待机状态后,可能需要继续执行s。我想说,在大多数情况下,不属于UI线程的任何事情都可以在中处理AsyncTask


谢谢,实际上是我应该使用线程而不是AsyncTasks的原因吗?还是更推荐使用它?
Alx

9
@AeroDroid在您的示例中:“一个简单的while(true)”,除非在循环中添加睡眠状态,否则将在此处固定CPU。任何无限循环都是如此。如果由于此开销而要减少CPU使用率,请在循环结束时将线程休眠几毫秒。
错误454

1
@错误454-很有意思!如果您必须为睡眠时间选择一个合适的数字,它是否在40-80毫秒之间?
阿比吉特(Abhijit)

6
@Abhijit从我在SDL中完成的游戏工作来看,只需在循环中添加10 ms睡眠就足以在空闲状态下将99%的cpu降至〜0。
错误454

15
实际上,developer.android.com / reference / android / os / AsyncTask.html表示:“理想情况下,AsyncTasks应该用于SHORT操作”。您还应谨慎使用它们,因为它们可能会在不执行的情况下被系统删除!
type-a1pha

5

AsyncTask旨在在后台执行不超过几秒钟的操作(不建议从服务器下载兆字节的文件或执行计算CPU密集型任务(例如文件IO操作)。如果需要执行长时间运行的操作,强烈建议您使用Java本机线程。Java为您提供了各种与线程相关的类,以执行所需的操作。使用Handler更新UI线程。


2
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();
    }

}

1

让我尝试用一​​个示例来回答这个问题:)-MyImageSearch [请在这里参考主活动屏幕的图像-包含编辑文本/搜索按钮/网格视图]

我的图片搜索

MyImageSearch的描述 - 用户在编辑文本字段中输入详细信息并单击搜索按钮后,我们将通过flickr提供的网络服务在Internet上搜索图像(您只需在此处注册即可获取密钥/秘密令牌) -为了进行搜索,我们将包含单个图像的URL作为响应发送回HTTP请求和GET JSON数据,然后将其用于加载网格视图。

我的实现 - 在主要活动中,我将定义一个内部类,该类将扩展AsyncTask以在doInBackGround方法中发送HTTP请求并获取JSON响应,并更新FlickrItems的本地ArrayList,我将使用它通过FlickrAdapter来更新GridView (扩展BaseAdapter)并在AsyncTask的onPostExecute()中调用adapter.notifyDataSetChanged()以重新加载网格视图。请注意,这里的HTTP请求是一个阻塞调用,因为它是通过AsyncTask进行的。而且,我可以将项目缓存在适配器中以提高性能或将其存储在SDCard上。我将在FlickrAdapter中扩大的网格在我的实现中包含一个进度栏和图像视图。在下面,您可以找到我使用的mainActivity的代码。

立即回答问题 -因此,一旦有了用于获取单个图像的JSON数据,我们就可以实现通过Handlers或Threads或AsyncTask在后台获取图像的逻辑。我们在这里应该注意,由于我的图片一旦下载就必须显示在UI /主线程上,我们不能简单地按原样使用线程,因为它们无法访问上下文。在FlickrAdapter中,我能想到的选择是:

  • 选择1:创建一个LooperThread [扩展线程]-并保持打开状态,继续在一个线程中依次下载图像[looper.loop()]
  • 选择2:利用线程池并通过包含对我的ImageView的引用的myHandler来发布可运行对象,但是由于Grid View中的视图被回收,因此再次出现问题,即索引4处的图像显示在索引9 [下载可能需要更多时间]
  • 选择3 [我使用了此方法]:利用线程池,并向myHandler发送一条消息,该消息包含与ImageView的索引和ImageView本身相关的数据,因此,在执行handleMessage()时,仅当currentIndex与该索引匹配时才更新ImageView。我们尝试下载的图片。
  • 选择4:利用AsyncTask在后台下载图像,但是在这里我将无法访问线程池中所需的线程数,并且它随Android版本的不同而不同,但是在选择3中,我可以做出明智的决定线程池的大小取决于所使用的设备配置。

这里的源代码:

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年

2
首先,感谢您的答复,尽管本主题有些陈旧,但核心概念仍然保持最新。我最初的问题根本没有得到回答,您只是给出一个示例并解释其工作原理,但是这些问题要求处理程序,asynctask和线程之间存在差异。
Alx

@ 80leaves好的,我现在明白了,我试图解释我如何得出选择一种方法而不是另一种方法的结论。无论如何,很想听听您/他人对我写的内容是否正确或是否可以进一步改善的看法。
akshaymani 2014年

1

根据需求选择哪一个

Handler通常用于从其他线程切换到主线程,Handler附加到一个循环程序,在该循环程序上将其可运行任务发布到队列中。因此,如果您已经在其他线程中并切换到主线程,则需要句柄而不是异步任务或其他线程

如果在非主线程而不是非循环程序中创建的Handler在创建线程的句柄时不会给出错误,则需要将该线程变为lopper

AsyncTask用于执行在后台线程上运行的代码几秒钟,并将其结果提供给主线程** * AsyncTask局限性 1.异步任务未附加到活动的生命周期中,即使它的活动被破坏,加载器也不会继续运行,即使它的活动被破坏没有此限制。2.所有异步任务共享同一后台线程来执行,这也会影响应用程序性能

线程在应用程序中也用于后台工作,但在主线程上没有任何回调。如果要求适合某些线程而不是一个线程,并且需要多次执行任务,那么线程池执行器是更好的选择。例如,从多个URL加载图像的要求,例如glide。


0

线

启动应用程序时,将创建一个过程来执行代码。为了有效地使用计算资源,可以在进程内启动线程,以便可以同时执行多个任务。因此,线程使您可以通过有效利用cpu来构建高效的应用程序,而无需空闲时间。

在Android中,所有组件都在一个称为主线程的线程上执行。Android系统将任务排队并在主线程上一个接一个地执行它们。当执行长时间运行的任务时,应用程序将无响应。

为避免这种情况,您可以创建工作线程并运行后台或长期运行的任务。

处理程序

由于android使用单线程模型,因此UI组件被创建为非线程安全的,这意味着只有其创建的线程才可以访问它们,这意味着UI组件应仅在主线程上进行更新。由于UI组件在主线程上运行,因此在工作线程上运行的任务无法修改UI组件。这是Handler出现的地方。借助Looper的处理程序可以连接到新线程或现有线程,并在连接的线程上运行它包含的代码。

处理程序使线程间通信成为可能。使用处理程序,后台线程可以向其发送结果,并且连接到主线程的处理程序可以更新主线程上的UI组件。

异步任务

android提供的AsyncTask使用线程和处理程序,使在后台运行简单任务以及将后台线程的结果更新到主线程变得容易。

请参阅android线程,处理程序,asynctask和线程池的示例。


-1

Handler-是线程之间的通信介质。在android中,它通常用于通过处理程序创建和发送消息来与主线程通信

AsyncTask-用于在后台线程中执行长时间运行的应用程序。使用n,AsyncTask您可以在后台线程中执行操作,并在应用程序的主线程中获取结果。

Thread-是一个轻量级的过程,可以实现并发和最大的cpu利用率。在android中,您可以使用线程执行不触摸应用程序UI的活动

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.