详解Android中Handler的使用方法

在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)的线程没有关系。

本文主要是对Android中Handler的作用于如何使用进行了初步介绍,如果大家想了解Handler的内部实现原理,可以参见下一篇博文《深入源码解析Android中的Handler,Message,MessageQueue,Looper》。

(0)

相关推荐

  • Android开发笔记之:Handler Runnable与Thread的区别详解

    在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口:Thread类是在java.lang包中定义的.一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限.下面看例子: 复制代码 代码如下: package org.thread.demo; class MyThread extends Thread{ private String name; public MyThread(St

  • android Handler详细使用方法实例

    开发环境为android4.1.Handler使用例1这个例子是最简单的介绍handler使用的,是将handler绑定到它所建立的线程中.本次实验完成的功能是:单击Start按钮,程序会开始启动线程,并且线程程序完成后延时1s会继续启动该线程,每次线程的run函数中完成对界面输出nUpdateThread...文字,不停的运行下去,当单击End按钮时,该线程就会停止,如果继续单击Start,则文字又开始输出了. 软件界面如下: 实验主要部分代码和注释: MainActivity.java: 复

  • Android定时器和Handler用法实例分析

    本文实例讲述了Android定时器和Handler用法.分享给大家供大家参考.具体分析如下: 一.环境: 主机:WIN8 开发环境:Android Studio 二.定时器使用示例: 初始化: //定时器 private Timer Timer_Work = new Timer(); //工作间隔,单位:ms private final int INTERVAL_WORK = 5000; 创建定时器线程: /** * 构造函数 */ public Config() { //生成配置信息 gene

  • Android Handler主线程和一般线程通信的应用分析

    Handler的定义:主要接受子线程发送的数据, 并用此数据配合主线程更新UI.解释: 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进行事件分发, 比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作.如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会

  • Android Handler之消息循环的深入解析

    Handler是用于操作线程内部的消息队列的类.这有点绕,没关系,我们慢慢的来讲.前面Looper一篇讲到了Looper是用于给线程创建消息队列用的,也就是说Looper可以让消息队列(MessageQueue)附属在线程之内,并让消息队列循环起来,接收并处理消息.但,我们并不直接的操作消息队列,而是用Handler来操作消息队列,给消息队列发送消息,和从消息队列中取出消息并处理.这就是Handler的职责.Handler,Looper和MessageQueue是属于一个线程内部的数据,但是它提

  • 详解Android中Handler的内部实现原理

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文<详解Android中Handler的使用方法>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功

  • android的消息处理机制(图文+源码分析)—Looper/Handler/Message

    这篇文章写的非常好,深入浅出,关键还是一位大三学生自己剖析的心得.这是我喜欢此文的原因.下面请看正文: 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.android源码中包含了大量的设 计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了.这不,前几天为了了解android的消息处理机 制,我看了Looper,Handler,Message这几个类的源码,结果又

  • android线程消息机制之Handler详解

    android线程消息机制主要由Handler,Looper,Message和MessageQuene四个部分组成.平常在开发中,我们常用来在子线程中通知主线程来更新,其实整个安卓生命周期的驱动都是通过Handler(ActivityThread.H)来实现的. 首先我们先介绍这四个类的作用: Handler:消息的发送者.负责将Message消息发送到MessageQueue中.以及通过Runnable,Callback或者handleMessage()来实现消息的回调处理 Looper:是消

  • android使用handlerthread创建线程示例

    在android开发中,一说起线程的使用,很多人马上想到new Thread(){...}.start()这种方式.这样使用当然可以,但是多次使用这种方式,会创建多个匿名线程.使得程序运行起来越来越慢.因此,可以考虑使用一个Handler来启动一个线程,当该线程不再使用就删除,保证线程不会重复创建.一般会使用Handler handler = new Handler(){...}创建Handler.这样创建的handler是在主线程即UI线程下的Handler,即这个Handler是与UI线程下

  • Handler与Android多线程详解

    下面是一段大家都比较熟悉的代码: 复制代码 代码如下: Handler handler = new Handler(); handler.post(myThread); //使用匿名内部类创建一个线程myThreadRunnable mythread = new Runnable() {public void run() {}}; 一开始,相信很多人都以为myThread中的run()方法会在一个新的线程中运行,但事实并非如此. 上述代码中的handler并没有调用线程myThread的star

随机推荐