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

 package ispring.com.testhandler;

 import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity implements Button.OnClickListener { private TextView statusTextView = null; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
} @Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
} class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
MainActivity.this.statusTextView.setText("文件下载完成");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
上面的代码演示了单击”下载“按钮后会启动一个新的线程去执行实际的下载操作,执行完毕后更新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方法,代码如下:

 package ispring.com.testhandler;

 import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity implements Button.OnClickListener { private TextView statusTextView = null; //uiHandler在主线程中创建,所以自动绑定主线程
private Handler uiHandler = new Handler(); @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
} @Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
} class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("DownloadThread id " + Thread.currentThread().getId());
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Runnable thread id " + Thread.currentThread().getId());
MainActivity.this.statusTextView.setText("文件下载完成");
}
};
uiHandler.post(runnable);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}

我们在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方法,代码如下:

 package ispring.com.testhandler;

 import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity implements Button.OnClickListener { private TextView statusTextView = null; //uiHandler在主线程中创建,所以自动绑定主线程
private Handler uiHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
System.out.println("handleMessage thread id " + Thread.currentThread().getId());
System.out.println("msg.arg1:" + msg.arg1);
System.out.println("msg.arg2:" + msg.arg2);
MainActivity.this.statusTextView.setText("文件下载完成");
break;
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
} @Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
} class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("DownloadThread id " + Thread.currentThread().getId());
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
Message msg = new Message();
//虽然Message的构造函数式public的,我们也可以通过以下两种方式通过循环对象获取Message
//msg = Message.obtain(uiHandler);
//msg = uiHandler.obtainMessage(); //what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别
//出不同的Message,以便我们做出不同的处理操作
msg.what = 1; //我们可以通过arg1和arg2给Message传入简单的数据
msg.arg1 = 123;
msg.arg2 = 321;
//我们也可以通过给obj赋值Object类型传递向Message传入任意数据
//msg.obj = null;
//我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据
//msg.setData(null);
//Bundle data = msg.getData(); //将该Message发送给对应的Handler
uiHandler.sendMessage(msg);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}

通过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

【转】Android开发中Handler的使用的更多相关文章

  1. Android开发中Handler的经典总结

    当应用程序启动时,Android首先会开启一个主线程(也就是UI线程),主线程为管理界面中的UI控件,进行事件分发. AD: 一.Handler的定义: 主要接受子线程发送的数据, 并用此数据配合主线 ...

  2. Android开发中Handler的经典总结--转载至网络

    一.Handler的定义: 主要接受子线程发送的数据, 并用此数据配合主线程更新UI. 解释:当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件 ...

  3. Android开发中的问题及相应解决(持续更新)

    最近博客写的少了,以后还得经常更新才行. ------------------------------------------------------------ 1.特定业务需求下try cath ...

  4. android开发中系统自带语音模块的使用

    android开发中系统自带语音模块的使用需求:项目中需要添加语音搜索模块,增加用户体验解决过程:在网上搜到语音搜索例子,参考网上代码,加入到了自己的项目,完成产品要求.这个问题很好解决,网上能找到很 ...

  5. 在Android开发中,定时器一般有以下3种实现方法

    在Android开发中,定时器一般有以下3种实现方法: 原文地址http://www.360doc.com/content/12/0619/13/87000_219180978.shtml 一.采用H ...

  6. 怎样在Android开发中FPS游戏实现的两种方式比较

    怎样在Android开发中FPS游戏实现的两种方式比较 如何用Android平台开发FPS游戏,其实现过程有哪些方法,这些方法又有哪些不同的地方呢?首先让我们先了解下什么是FPS 英文名:FPS (F ...

  7. 5个Android开发中比较常见的内存泄漏问题及解决办法

    android中一个对象已经不需要了,但是其他对象还持有他的引用,导致他不能回收,导致这个对象暂存在内存中,这样内存泄漏就出现了.   内存泄漏出现多了,会是应用占用过多的没存,当占用的内存超过了系统 ...

  8. Dagger2在Android开发中的应用

    世界是普遍联系的,任何事物和个体都直接或间接相互依赖,在时空长河中共同发展.在面向对象的世界中,更是如此,类与类之间的依赖,关联关系,模块(亦或是分层架构中的层)之间的耦合关系,都是我们在软件开发实践 ...

  9. Android开发中常见的设计模式 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

随机推荐

  1. SQLite的37个核心函数

    转载:http://www.feiesoft.com/00012/ abs(X) abs(X)返回 X 的绝对值. Abs(X) returns NULL if X is NULL. Abs(X) r ...

  2. delete数组引发的core分析

    delete [] ptr 引发了singnal 6 abort的core错误,跟踪过程发现写入ptr大量数据,引发内存越界,破坏了new数组的尾部数据保护,导致delete的时候core. 问题分析 ...

  3. Nginx端口的修改

    修改 nginx.conf 文件实现.在 Linux 上该文件的路径为 /usr/local/nginx/conf/nginx.conf,Windows 下 安装目录\conf\nginx.conf. ...

  4. 正则表达式从右往左进行匹配(Regex)

    #匹配最末两位为数字 $x=New-Object regex ('\d{2}','RightToLeft') #$x.RightToLeft $x.Match('abcd22') 结果:

  5. DuiLib(三)——控件消息

    上一篇讲了控件创建,这篇说说控件消息.directui的中心思想是在真实窗口之上画出所有控件,那这些控件是如何获取各自消息的? 通过第一篇的示例可以看到窗口消息的传递过程: CWindowWnd::_ ...

  6. LCD_ILI9320横竖屏方向的问题。

    发现仅仅设置R03H是不能设置方向的,还需要设置R32H,R33H的坐标位置. 比如我现在是 R03H=0x1000H,R20H=239-x,R21H=319-y:竖直正向 R03H=0x1030H, ...

  7. POJ 2420 A Star not a Tree? 爬山算法

    B - A Star not a Tree? Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjudge/co ...

  8. Select模型原理

    Select模型原理 利用select函数,推断套接字上是否存在数据,或者是否能向一个套接字写入数据.目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据, ...

  9. Asp.Net页面(母版页)加载顺序

    ASP.NET 母版页和内容页中的事件 母版页和内容页都可以包含控件的事件处理程序.对于控件而言,事件是在本地处理的,即内容页中的控件在内容页中引发事件,母版页中的控件在母版页中引发事件.控件事件不会 ...

  10. IOS AutoLayout 文章

    开始iOS 7中自动布局教程(一) 开始iOS 7中自动布局教程(二) 代码的方式自动布局 自动布局时计算Cell高度