Handler vs AsyncTask vs Thread

时间:2011-08-06 00:37:45

标签: android multithreading android-asynctask android-handler

我对Android中HandlersAsyncTaskThreads之间的差异感到有些困惑。我在stackoverflow中阅读了很多博客和问题。

Handler是后台线程,可让您与UI进行通信。例如,更新进度条应该通过Handler完成。使用处理程序,您具有MessagingQueues的优势,因此,如果您想安排消息或更新多个UI元素或重复任务。

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

然而,

Thread无法与用户界面互动,提供更多“基本”线程,您错过了AsyncTask的所有抽象。

但是我希望在服务中运行套接字连接。这应该在处理程序或线程中运行,还是在AsyncTask运行?根本不需要UI交互。它在我使用的性能方面有所不同吗?

与此同时,documentation已得到重大改善。

13 个答案:

答案 0 :(得分:348)

如果查看AsyncTaskHandler的源代码,您将看到他们的代码纯粹是用Java编写的。 (当然,也有一些例外,但这不是重点。)

因此AsyncTaskHandler中没有魔法。它们只是让您的工作变得更容易作为开发人员。

例如:如果程序A调用方法A(),方法A()可以在程序A的不同线程中运行。您可以使用以下方法轻松验证它:

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

为什么要使用新线程?你可以google它。原因很多。

那么,ThreadAsyncTaskHandler之间有什么区别?

AsyncTaskHandler是用Java编写的(内部使用的是Thread),因此您可以使用HandlerAsyncTask执行所有操作,使用Thread也可以实现。

HandlerAsyncTask可以为您提供哪些帮助?

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

这就是您应该使用HandlerAsyncTask的原因。他们为您完成大部分工作,您只需知道要覆盖哪些方法。

HandlerAsyncTask之间的区别是:当来电线程 UI线程时,请使用AsyncTask。 这就是android文档所说的:

  

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

我想强调两点:

1)轻松使用UI线程(因此,当调用者线程是UI线程时使用)。

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

这篇文章中有很多内容我还没有说过,例如:什么是UI线程,或者为什么它更容易。你必须知道每种方法背后的一些方法并使用它,你将完全理解为什么......

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

  

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

相关联

起初他们可能看起来很奇怪。只需了解每个线程都有每个消息队列(如待办事项列表),线程将接收每条消息并执行它直到消息队列为空(就像你完成工作并上床睡觉一样)。因此,当Handler进行通信时,它只是向调用者线程发出一条消息,它将等待处理。复杂?请记住Handler可以安全地与调用者线程通信。

答案 1 :(得分:52)

正如Vogella网站上Android background processing with Handlers, AsyncTask and Loaders的教程所说:

Handler类可用于注册一个线程,并提供一个简单的通道来向该线程发送数据。

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

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

  

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

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

关于AsyncTask,正如Android Developer's Reference所说的那样:

  

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

     

AsyncTask旨在成为围绕ThreadHandler的助手类   并不构成通用的线程框架。 AsyncTasks   理想情况下应该用于短期操作(几秒钟的时间)   大多数。)如果你需要保持线程长时间运行,   强烈建议您使用提供的各种API   java.util.concurrent包,如Executor,ThreadPoolExecutor和   FutureTask。

2015年5月更新:我发现了一个优秀系列讲座,涵盖了这一主题。

  

这是Google搜索:Douglas Schmidt lecture android concurrency and synchronisation

     

这是first lecture on YouTube

的视频      

所有这些都是来自范德比尔特大学 CS 282(2013):Android的系统编程的一部分。这是YouTube Playlist

     道格拉斯施密特似乎是一位出色的讲师

重要提示:如果您正考虑使用AsyncTask解决线程问题,则应首先结帐ReactiveX/RxAndroid 可能更合适的编程模式。获得概述的非常好的资源是Learning RxJava 2 for Android by example

答案 2 :(得分:51)

经过深入研究,它是直截了当的。

AsyncTask

这是一种使用线程的简单方法,而不了解任何有关java线程模型的内容AsyncTask给出了与工作线程和主线程相对应的各种回调。

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

  1. 从网络服务中获取一些数据并在布局上显示。
  2. 数据库查询。
  3. 当你意识到永远不会嵌套运行操作时。
  4. <强> Handler

    当我们在android中安装应用程序时,它会为该应用程序创建一个名为MAIN UI Thread的线程。所有活动都在该线程内运行。通过android单线程模型规则,我们无法直接访问该元素内部定义的另一个线程的UI元素(位图,textview等)。

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

    它最适合:

    1. 它允许您进行消息排队。
    2. 消息安排。
    3. <强> Thread

      现在是时候谈论线程了。

      线程是AsyncTaskHandler的父级。它们都在内部使用线程,这意味着你也可以创建自己的线程模型,如AsyncTaskHandler,但这需要很好地了解 Java&#39;多线程实施

答案 3 :(得分:21)

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

您可以使用Thread的{​​{1}}方法生成背景Handler并将消息传回主线程。

答案 4 :(得分:9)

Thread

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

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

Handler

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

您的帖子可以通过方法sendMessage(Message msg)sendEmptyMessage发布消息。

AsyncTask

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

有关详细信息,请查看这些链接。

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

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

答案 5 :(得分:6)

Thread

您可以使用新的Thread来执行长时间运行的后台任务,而不会影响UI线程。从java Thread,您无法更新UI线程。

由于普通Thread对Android架构没有多大帮助,因此引入了用于线程的辅助类。

您可以在Threading performance文档页面中找到查询的答案。

Handler

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

Handler有两个主要用途:

  1. 安排将来执行的消息和runnables作为某个点;

  2. 将要在不同于自己的线程上执行的操作排入队列。

  3. AsyncTask

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

    <强>缺点:

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

    2. AsyncTask个对象也是隐式参考问题最常见的违规者。 AsyncTask个对象也存在与显式引用相关的风险。

    3. HandlerThread

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

      ThreadPoolExecutor

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

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

        

      但是我希望在服务中运行套接字连接。它应该在处理程序或线程中运行,还是在AsyncTask中运行?根本不需要UI交互。它在我使用的性能方面有所不同吗?

      由于不需要用户界面互动,因此您可能不会选择AsyncTask。普通线程没有多大用处,因此HandlerThread是最佳选择。由于必须维护套接字连接,因此主线程上的Handler根本没用。创建HandlerThread并从Handler的looper获取HandlerThread

       HandlerThread handlerThread = new HandlerThread("SocketOperation");
       handlerThread.start();
       Handler requestHandler = new Handler(handlerThread.getLooper());
       requestHandler.post(myRunnable); // where myRunnable is your Runnable object. 
      

      如果要与UI线程进行通信,可以再使用一个处理程序来处理响应。

      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: Toast in a thread

答案 6 :(得分:5)

在我看来,线程不是最有效的套接字连接方式,但它们确实提供了运行线程方面最多的功能。我这样说是因为从经验来看,长时间运行线程会导致设备非常热和资源密集。即使是简单的while(true)也能在几分钟内加热手机。如果你说UI交互并不重要,那么AsyncTask可能是好的,因为它们是为长期流程设计的。这只是我对它的看法。

<强>更新

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

在这个帖子上有更好的其他答案,但我至少会给我更恰当的答案。使用常规Java Thread没有错;但是,你应该非常小心如何实现它,因为做错了可能会非常耗费处理器(最值得注意的症状可能是你的设备升温)。 AsyncTask对于您希望在后台运行的大多数任务非常理想(常见的例子是磁盘I / O,网络调用和数据库调用)。但是,AsyncTask s不应用于特别长的进程,可能需要在用户关闭应用程序或将其设备置于待机状态后继续进行。我想说在大多数情况下,任何不属于UI线程的东西都可以在AsyncTask中处理。

答案 7 :(得分:5)

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

答案 8 :(得分: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();
    }

}

答案 9 :(得分:1)

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

MyImageSearch

MyImageSearch的说明 - 用户在编辑文本字段中输入详细信息并点击搜索按钮后,我们将通过flickr提供的网络服务在互联网上搜索图像(您只需要在那里注册以获取密钥/秘密令牌) - 对于搜索,我们发送一个HTTP请求和GET JSON数据作为响应,包含我们将用于加载网格视图的各个图像的URL。 < / p>

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

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

  • 选择1:创建一个LooperThread [扩展线程] - 并继续 通过保留此线程,在一个线程中顺序下载图像 打开[looper.loop()]
  • 选择2:使用线程池并通过myHandler发布runnable 包含对我的ImageView的引用,但是自Grid View中的视图以来 再循环,问题可能出现在索引4处的图像 显示在索引9 [下载可能需要更多时间]
  • 选择3 [我用过]:使用线程池并向myHandler发送消息,其中包含与ImageView索引相关的数据 ImageView本身,所以在执行handleMessage()时我们会更新 ImageView仅当currentIndex匹配Image的索引时 试图下载。
  • 选择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);
    }
}

我希望我的回答虽然很长,但有助于理解一些更精细的细节。

答案 10 :(得分:1)

要根据要求选择哪个

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

如果在非线程的非主线程中创建的Handler在创建该线程的句柄时不会给出错误,则该线程需要变小

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

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

答案 11 :(得分:0)

Handler - 是线程之间的通信媒介。在android中,它主要用于通过处理程序

创建和发送消息与主线程进行通信

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

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

答案 12 :(得分:0)

主题

启动应用程序时,会创建一个进程来执行代码。为了有效地使用计算资源,可以在进程内启动线程,以便可以在此时执行多个任务。因此,线程允许您在没有空闲时间的情况下有效地利用cpu来构建高效的应用程序。

在Android中,所有组件都在一个被调用的主线程上执行。 Android系统队列任务并在主线程上逐个执行。执行长时间运行的任务时,应用程序将无法响应。

为了防止这种情况,您可以创建工作线程并运行后台或长时间运行的任务。

<强>处理程序

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

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

<强>的AsyncTask

android提供的AsyncTask使用线程和处理程序在后台运行简单的任务,并将后台线程的结果更新为主线程。

请参阅android thread, handler, asynctask and thread pools以获取示例。