• 【转】Android开发中Handler的使用


    在Android开发中,我们经常会遇到这样一种情况:在UI界面上进行某项操作后要执行一段很耗时的代码,比如我们在界面上点击了一个”下载“按钮,那么我们需要执行网络请求,这是一个耗时操作,因为不知道什么时候才能完成。为了保证不影响UI线程,所以我们会创建一个新的线程去执行我们的耗时的代码。当我们的耗时操作完成时,我们需要更新UI界面以告知用户操作完成了。所以我们可能会写出如下的代码:

     1 package ispring.com.testhandler;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.view.View;
     6 import android.widget.Button;
     7 import android.widget.TextView;
     8 
     9 
    10 public class MainActivity extends Activity implements Button.OnClickListener {
    11 
    12     private TextView statusTextView = null;
    13 
    14     @Override
    15     protected void onCreate(Bundle savedInstanceState) {
    16         super.onCreate(savedInstanceState);
    17         setContentView(R.layout.activity_main);
    18         statusTextView = (TextView)findViewById(R.id.statusTextView);
    19         Button btnDownload = (Button)findViewById(R.id.btnDownload);
    20         btnDownload.setOnClickListener(this);
    21     }
    22 
    23     @Override
    24     public void onClick(View v) {
    25         DownloadThread downloadThread = new DownloadThread();
    26         downloadThread.start();
    27     }
    28 
    29     class DownloadThread extends Thread{
    30         @Override
    31         public void run() {
    32             try{
    33                 System.out.println("开始下载文件");
    34                 //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
    35                 Thread.sleep(5000);
    36                 System.out.println("文件下载完成");
    37                 //文件下载完成后更新UI
    38                 MainActivity.this.statusTextView.setText("文件下载完成");
    39             }catch (InterruptedException e){
    40                 e.printStackTrace();
    41             }
    42         }
    43     }
    44 }

    上面的代码演示了单击”下载“按钮后会启动一个新的线程去执行实际的下载操作,执行完毕后更新UI界面。但是在实际运行到代码MainActivity.this.statusTextView.setText(“文件下载完成”)时,会报错如下,系统崩溃退出: 

    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 

    错误的意思是只有创建View的原始线程才能更新View。出现这样错误的原因是Android中的View不是线程安全的,在Android应用启动时,会自动创建一个线程,即程序的主线程,主线程负责UI的展示、UI事件消息的派发处理等等,因此主线程也叫做UI线程,statusTextView是在UI线程中创建的,当我们在DownloadThread线程中去更新UI线程中创建的statusTextView时自然会报上面的错误。Android的UI控件是非线程安全的,其实很多平台的UI控件都是非线程安全的,比如C#的.Net Framework中的UI控件也是非线程安全的,所以不仅仅在Android平台中存在从一个新线程中去更新UI线程中创建的UI控件的问题。不同的平台提供了不同的解决方案以实现跨线程跟新UI控件,Android为了解决这种问题引入了Handler机制。

    那么Handler到底是什么呢?Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制。每个Hanlder都关联了一个线程,每个线程内部都维护了一个消息队列MessageQueue,这样Handler实际上也就关联了一个消息队列。可以通过Handler将Message和Runnable对象发送到该Handler所关联线程的MessageQueue(消息队列)中,然后该消息队列一直在循环拿出一个Message,对其进行处理,处理完之后拿出下一个Message,继续进行处理,周而复始。当创建一个Handler的时候,该Handler就绑定了当前创建Hanlder的线程。从这时起,该Hanlder就可以发送Message和Runnable对象到该Handler对应的消息队列中,当从MessageQueue取出某个Message时,会让Handler对其进行处理。

    Handler可以用来在多线程间进行通信,在另一个线程中去更新UI线程中的UI控件只是Handler使用中的一种典型案例,除此之外,Handler可以做很多其他的事情。每个Handler都绑定了一个线程,假设存在两个线程ThreadA和ThreadB,并且HandlerA绑定了 ThreadA,在ThreadB中的代码执行到某处时,出于某些原因,我们需要让ThreadA执行某些代码,此时我们就可以使用Handler,我们可以在ThreadB中向HandlerA中加入某些信息以告知ThreadA中该做某些处理了。由此可以看出,Handler是Thread的代言人,是多线程之间通信的桥梁,通过Handler,我们可以在一个线程中控制另一个线程去做某事。

    Handler提供了两种方式解决我们在本文一开始遇到的问题(在一个新线程中更新主线程中的UI控件),一种是通过post方法,一种是调用sendMessage方法。

    a. 使用post方法,代码如下:

     1 package ispring.com.testhandler;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.view.View;
     7 import android.widget.Button;
     8 import android.widget.TextView;
     9 
    10 
    11 public class MainActivity extends Activity implements Button.OnClickListener {
    12 
    13     private TextView statusTextView = null;
    14 
    15     //uiHandler在主线程中创建,所以自动绑定主线程
    16     private Handler uiHandler = new Handler();
    17 
    18     @Override
    19     protected void onCreate(Bundle savedInstanceState) {
    20         super.onCreate(savedInstanceState);
    21         setContentView(R.layout.activity_main);
    22         statusTextView = (TextView)findViewById(R.id.statusTextView);
    23         Button btnDownload = (Button)findViewById(R.id.btnDownload);
    24         btnDownload.setOnClickListener(this);
    25         System.out.println("Main thread id " + Thread.currentThread().getId());
    26     }
    27 
    28     @Override
    29     public void onClick(View v) {
    30         DownloadThread downloadThread = new DownloadThread();
    31         downloadThread.start();
    32     }
    33 
    34     class DownloadThread extends Thread{
    35         @Override
    36         public void run() {
    37             try{
    38                 System.out.println("DownloadThread id " + Thread.currentThread().getId());
    39                 System.out.println("开始下载文件");
    40                 //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
    41                 Thread.sleep(5000);
    42                 System.out.println("文件下载完成");
    43                 //文件下载完成后更新UI
    44                 Runnable runnable = new Runnable() {
    45                     @Override
    46                     public void run() {
    47                         System.out.println("Runnable thread id " + Thread.currentThread().getId());
    48                         MainActivity.this.statusTextView.setText("文件下载完成");
    49                     }
    50                 };
    51                 uiHandler.post(runnable);
    52             }catch (InterruptedException e){
    53                 e.printStackTrace();
    54             }
    55         }
    56     }
    57 }

    我们在Activity中创建了一个Handler成员变量uiHandler,Handler有个特点,在执行new Handler()的时候,默认情况下Handler会绑定当前代码执行的线程,我们在主线程中实例化了uiHandler,所以uiHandler就自动绑定了主线程,即UI线程。当我们在DownloadThread中执行完耗时代码后,我们将一个Runnable对象通过post方法传入到了Handler中,Handler会在合适的时候让主线程执行Runnable中的代码,这样Runnable就在主线程中执行了,从而正确更新了主线程中的UI。以下是输出结果: 
    这里写图片描述

    通过输出结果可以看出,Runnable中的代码所执行的线程ID与DownloadThread的线程ID不同,而与主线程的线程ID相同,因此我们也由此看出在执行了Handler.post(Runnable)这句代码之后,运行Runnable代码的线程与Handler所绑定的线程是一致的,而与执行Handler.post(Runnable)这句代码的线程(DownloadThread)无关。

    b. 使用sendMessage方法,代码如下:

     1 package ispring.com.testhandler;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.Message;
     7 import android.view.View;
     8 import android.widget.Button;
     9 import android.widget.TextView;
    10 
    11 
    12 public class MainActivity extends Activity implements Button.OnClickListener {
    13 
    14     private TextView statusTextView = null;
    15 
    16     //uiHandler在主线程中创建,所以自动绑定主线程
    17     private Handler uiHandler = new Handler(){
    18         @Override
    19         public void handleMessage(Message msg) {
    20             switch (msg.what){
    21                 case 1:
    22                     System.out.println("handleMessage thread id " + Thread.currentThread().getId());
    23                     System.out.println("msg.arg1:" + msg.arg1);
    24                     System.out.println("msg.arg2:" + msg.arg2);
    25                     MainActivity.this.statusTextView.setText("文件下载完成");
    26                     break;
    27             }
    28         }
    29     };
    30 
    31     @Override
    32     protected void onCreate(Bundle savedInstanceState) {
    33         super.onCreate(savedInstanceState);
    34         setContentView(R.layout.activity_main);
    35         statusTextView = (TextView)findViewById(R.id.statusTextView);
    36         Button btnDownload = (Button)findViewById(R.id.btnDownload);
    37         btnDownload.setOnClickListener(this);
    38         System.out.println("Main thread id " + Thread.currentThread().getId());
    39     }
    40 
    41     @Override
    42     public void onClick(View v) {
    43         DownloadThread downloadThread = new DownloadThread();
    44         downloadThread.start();
    45     }
    46 
    47     class DownloadThread extends Thread{
    48         @Override
    49         public void run() {
    50             try{
    51                 System.out.println("DownloadThread id " + Thread.currentThread().getId());
    52                 System.out.println("开始下载文件");
    53                 //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
    54                 Thread.sleep(5000);
    55                 System.out.println("文件下载完成");
    56                 //文件下载完成后更新UI
    57                 Message msg = new Message();
    58                 //虽然Message的构造函数式public的,我们也可以通过以下两种方式通过循环对象获取Message
    59                 //msg = Message.obtain(uiHandler);
    60                 //msg = uiHandler.obtainMessage();
    61 
    62                 //what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别
    63                 //出不同的Message,以便我们做出不同的处理操作
    64                 msg.what = 1;
    65 
    66                 //我们可以通过arg1和arg2给Message传入简单的数据
    67                 msg.arg1 = 123;
    68                 msg.arg2 = 321;
    69                 //我们也可以通过给obj赋值Object类型传递向Message传入任意数据
    70                 //msg.obj = null;
    71                 //我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据
    72                 //msg.setData(null);
    73                 //Bundle data = msg.getData();
    74 
    75                 //将该Message发送给对应的Handler
    76                 uiHandler.sendMessage(msg);
    77             }catch (InterruptedException e){
    78                 e.printStackTrace();
    79             }
    80         }
    81     }
    82 }

    通过Message与Handler进行通信的步骤是: 
    1. 重写Handler的handleMessage方法,根据Message的what值进行不同的处理操作 
    2. 创建Message对象 
    虽然Message的构造函数式public的,我们还可以通过Message.obtain()或Handler.obtainMessage()来获得一个Message对象(Handler.obtainMessage()内部其实调用了Message.obtain())。 
    3. 设置Message的what值 
    Message.what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别出不同的Message,以便我们做出不同的处理操作。 
    4. 设置Message的所携带的数据,简单数据可以通过两个int类型的field arg1和arg2来赋值,并可以在handleMessage中读取。 
    5. 如果Message需要携带复杂的数据,那么可以设置Message的obj字段,obj是Object类型,可以赋予任意类型的数据。或者可以通过调用Message的setData方法赋值Bundle类型的数据,可以通过getData方法获取该Bundle数据。 
    6. 我们通过Handler.sendMessage(Message)方法将Message传入Handler中让其在handleMessage中对其进行处理。 
    需要说明的是,如果在handleMessage中 不需要判断Message类型,那么就无须设置Message的what值;而且让Message携带数据也不是必须的,只有在需要的时候才需要让其携带数据;如果确实需要让Message携带数据,应该尽量使用arg1或arg2或两者,能用arg1和arg2解决的话就不要用obj,因为用arg1和arg2更高效。 
    程序的运行结果如下: 
    这里写图片描述

    由上我们可以看出,执行handleMessage的线程与创建Handler的线程是同一线程,在本示例中都是主线程。执行handleMessage的线程与执行uiHandler.sendMessage(msg)的线程没有关系。

    原文出处:http://blog.csdn.net/iispring/article/details/47115879

  • 相关阅读:
    如何使员工能力和收入相匹配?
    微软Windows Phone 7新特性详解
    微软MSDN中文网络广播(Webcast)——Visual Studio 2010 & ALM应用实践系列课程预告(2011)
    博客园开发征途新书《我也能做CTO之.程序员职业规划》出版
    架构抉择:享用微软SQL云平台就像吃烤鸭
    微软北京.NET俱乐部免费活动(2010年7月18日)–Visual Studio 2010 敏捷开发与云计算Azure
    WCF与Hprose在微软云计算平台Azure上的对决
    在Visual Studio 2010中实现数据驱动Coded UI Tests
    基于微软Dryad分布式并行计算平台云技术的研究
    微软Visual Studio 2010架构设计功能应用
  • 原文地址:https://www.cnblogs.com/chapter/p/4860946.html
Copyright © 2020-2023  润新知