服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - 详解Android中Handler的使用方法

详解Android中Handler的使用方法

2021-04-22 17:11孙群 Android

这篇文章主要介绍了Android中Handler的使用方法,对Android中Handler的作用于如何使用进行了初步介绍,需要的朋友可以参考下

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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方法,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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。以下是输出结果:

详解Android中Handler的使用方法

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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更高效。
程序的运行结果如下:

详解Android中Handler的使用方法

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

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

延伸 · 阅读

精彩推荐