虽然是国庆佳节,但也不能停止学习的脚步,我选择在教研室为祖国母亲默默地庆生。
关于Android的多线程知识,请参考本人之前的一篇博客:Android 多线程----AsyncTask异步任务详解
在Android当中,提供了异步消息处理机制的两种方式来解决线程之间的通信问题,一种是今天要讲的Handler的机制,还有一种就是之前讲过的 AsyncTask 机制。
一、handler的引入:
我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。具体实现代码如下:
复制代码
1 package com.example.androidthreadtest;
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.view.View.OnClickListener;
9 import android.widget.Button;
10 import android.widget.TextView;
11 
12 public class MainActivity extends Activity implements OnClickListener {
13 
14     public static final int UPDATE_TEXT = 1;
15     private TextView text;
16     private Button changeText;
17     private Handler handler = new Handler() {
18         public void handleMessage(Message msg) {
19             switch (msg.what) {
20             case UPDATE_TEXT:
21                 text.setText("Nice to meet you");
22                 break;
23             default:
24                 break;
25             }
26         }
27     };
28 
29     @Override
30     protected void onCreate(Bundle savedInstanceState) {
31         super.onCreate(savedInstanceState);
32         setContentView(R.layout.activity_main);
33         text = (TextView) findViewById(R.id.text);
34         changeText = (Button) findViewById(R.id.change_text);
35         changeText.setOnClickListener(this);
36     }
37 
38     @Override
39     public void onClick(View v) {
40         switch (v.getId()) {
41         case R.id.change_text:
42             new Thread(new Runnable() {
43                 @Override
44                 public void run() {
45                     Message message = new Message();
46                     message.what = UPDATE_TEXT;
47                     handler.sendMessage(message);
48                 }
49             }).start();
50             break;
51         default:
52             break;
53         }
54     }
55 }
复制代码
上方第45行代码也可以换成:
Message msg = handler.obtainMessage(); 
上面的代码中,我们并没有在子线程中直接进行UI操作,而是创建了一个Message对象,并将它的what字段的值指定为了一个整形常量UPDATE_TEXT,用于表示更新TextView这个动作。然后调用Handler的sendMessage()方法将这条Message发送出去。很快,Handler就会收到这条Message,并在handleMessage()方法,在这里对具体的Message进行处理(需要注意的是,此时handleMessage()方法中的代码是在主线程中运行的)。如果发现Message的what字段的值等于UPDATE_TEXT,就将TextView显示的内容更新。运行程序后,点击按钮,TextView就会显示出更新的内容。
二、异步消息处理机制:
Handler是Android类库提供的用于接受、传递和处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。整个异步消息处理流程的示意图如下图所示:
根据上面的图片,我们现在来解析一下异步消息处理机制:
Message:消息体,用于装载需要发送的对象。
handler:它直接继承自Object。作用是:在子线程中发送Message或者Runnable对象到MessageQueue中;在UI线程中接收、处理从MessageQueue分发出来的Message或者Runnable对象。发送消息一般使用Handler的sendMessage()方法,而发出去的消息经过处理后最终会传递到Handler的handlerMessage()方法中。
MessageQueue:用于存放Message或Runnable对象的消息队列。它由对应的Looper对象创建,并由Looper对象管理。每个线程中都只会有一个MessageQueue对象。
Looper:是每个线程中的MessageQueue的管家,循环不断地管理MessageQueue接收和分发Message或Runnable的工作。调用Looper的loop()方法后,就会进入到一个无限循环中然后每当发现MessageQueue中存在一条消息,就会将它取出,并调用Handler的handlerMessage()方法。每个线程中也只会有一个Looper对象。
了解这些之后,我们在来看一下他们之间的联系:
首先要明白的是,Handler和Looper对象是属于线程内部的数据,不过也提供与外部线程的访问接口,Handler就是公开给外部线程的接口,用于线程间的通信。Looper是由系统支持的用于创建和管理MessageQueue的依附于一个线程的循环处理对象,而Handler是用于操作线程内部的消息队列的,所以Handler也必须依附一个线程,而且只能是一个线程。
我们再来对异步消息处理的整个流程梳理一遍:
当应用程序开启时,系统会自动为UI线程创建一个MessageQueue(消息队列)和Looper循环处理对象。首先需要在主线程中创建一个Handler对象,并重写handlerMessage()方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息就会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,并找到与消息对象对应的Handler对象,然后调用Handler的handleMessage()方法。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。
通俗地来讲,一般我们在实际的开发过程中用的比较多一种情况的就是主线程的Handler将子线程中处理过的耗时操作的结果封装成Message(消息),并将该Message(利用主线程里的MessageQueue和Looper)传递到主线程中,最后主线程再根据传递过来的结果进行相关的UI元素的更新,从而实现任务的异步加载和处理,并达到线程间的通信。
通过上一小节对Handler的一个初步认识后,我们可以很容易总结出Handler的主要用途,下面是Android官网总结的关于Handler类的两个主要用途:
(1)线程间的通信:
在执行较为耗时的操作时,Handler负责将子线程中执行的操作的结果传递到UI线程,然后UI线程再根据传递过来的结果进行相关UI元素的更新。(上面已有说明)
(2)执行定时任务:
指定任务时间,在某个具体时间或某个时间段后执行特定的任务操作,例如使用Handler提供的postDelayed(Runnable r,long delayMillis)方法指定在多久后执行某项操作,比如当当、淘宝、京东和微信等手机客户端的开启界面功能,都是通过Handler定时任务来完成的。
我们接下来讲一下post。
三、post:
对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。
Post允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。详细解释如下:
boolean post(Runnable r):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,立即执行。
boolean postAtTime(Runnable r,long uptimeMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,在特定的时间执行。
boolean postDelayed(Runnable r,long delayMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,延迟delayMills秒执行
void removeCallbacks(Runnable r):从消息队列中移除一个Runnable对象。
下面通过一个Demo,讲解如何通过Handler的post方式在新启动的线程中修改UI组件的属性:
复制代码
1 package com.example.m03_threadtest01;
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;
10 public class MainActivity extends Activity {
11     private Button btnMes1,btnMes2;
12     private TextView tvMessage;
13     // 声明一个Handler对象
14     private static Handler handler=new Handler();
15     
16     @Override
17     protected void onCreate(Bundle savedInstanceState) {
18         super.onCreate(savedInstanceState);
19         setContentView(R.layout.activity_main);        
20         
21         btnMes1=(Button)findViewById(R.id.button1);
22         btnMes2=(Button)findViewById(R.id.button2);
23         tvMessage=(TextView)findViewById(R.id.TextView1);
24         btnMes1.setOnClickListener(new View.OnClickListener() {
25             
26             @Override
27             public void onClick(View v) {
28                 // 新启动一个子线程
29                 new Thread(new Runnable() {                    
30                     @Override
31                     public void run() {
32                         // tvMessage.setText("...");
33                         // 以上操作会报错,无法再子线程中访问UI组件,UI组件的属性必须在UI线程中访问
34                         // 使用post方式修改UI组件tvMessage的Text属性
35                         handler.post(new Runnable() {                    
36                             @Override
37                             public void run() {
38                                 tvMessage.setText("使用Handler.post在工作线程中发送一段执行到消息队列中,在主线程中执行。");                        
39                             }
40                         });                                
41                     }
42                 }).start();
43             }
44         });
45         
46         btnMes2.setOnClickListener(new View.OnClickListener() {
47             
48             @Override
49             public void onClick(View v) {
50                 new Thread(new Runnable() {                    
51                     @Override
52                     public void run() {
53                         // 使用postDelayed方式修改UI组件tvMessage的Text属性值
54                         // 并且延迟3S执行
55                         handler.postDelayed(new Runnable() {
56                             
57                             @Override
58                             public void run() {
59                                 tvMessage.setText("使用Handler.postDelayed在工作线程中发送一段执行到消息队列中,在主线程中延迟3S执行。");    
60                                 
61                             }
62                         }, 3000);                        
63                     }
64                 }).start();
65                 
66             }
67         });
68     }
69     
70 }
复制代码
点击按钮,运行结果如下:
有一点值得注意的是,对于Post方式而言,它其中Runnable对象的run()方法的代码,均执行在UI线程上(虽然是写在子线程当中的),所以对于这段代码而言,不能执行在UI线程上的操作,一样无法使用post方式执行,比如说访问网络。 
四、Message:
Handler如果使用sendMessage的方式把消息入队到消息队列中,需要传递一个Message对象,而在Handler中,需要重写handleMessage()方法,用于获取工作线程传递过来的消息,此方法运行在UI线程上。
对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取。Message.obtain()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。Handler.obtainMessage()具有多个重载方法,如果查看源码,会发现其实Handler.obtainMessage()在内部也是调用的Message.obtain()。
Handler中,与Message发送消息相关的方法有:
Message obtainMessage():获取一个Message对象。
boolean sendMessage():发送一个Message对象到消息队列中,并在UI线程取到消息后,立即执行。
boolean sendMessageDelayed():发送一个Message对象到消息队列中,在UI线程取到消息后,延迟执行。
boolean sendEmptyMessage(int what):发送一个空的Message对象到队列中,并在UI线程取到消息后,立即执行。
boolean sendEmptyMessageDelayed(int what,long delayMillis):发送一个空Message对象到消息队列中,在UI线程取到消息后,延迟执行。
void removeMessage():从消息队列中移除一个未响应的消息。
五、通过Handler实现线程间通信:
1、在Worker Thread发送消息,在MainThread中接收消息:
【实例】点击反扭,将下方的TextView的内容修改为“从网络中获取的数据”
【实际意义】点击按钮时,程序访问服务器,服务器接到请求之后,会返回字符串结果,然后更新到程序。
完整版代码如下:
XML布局文件代码如下:
复制代码
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/TextViewId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="数据" />   
<Button
android:id="@+id/ButtonId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送消息" 
android:layout_below="@id/TextViewId"/>
</RelativeLayout> 
复制代码
MainActivity.java代码如下:
复制代码
1 package com.example.test0207_handler;
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.os.Message;
7 import android.view.Menu;
8 import android.view.View;
9 import android.view.View.OnClickListener;
10 import android.widget.Button;
11 import android.widget.TextView;
12 
13 public class MainActivity extends Activity {
14 
15     private TextView textView ; 
16     private Button button ;
17     private Handler handler ;
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22         
23         textView = (TextView)findViewById(R.id.TextViewId) ;
24         button = (Button)findViewById(R.id.ButtonId) ;    
25         
26         handler = new MyHandler() ;
27         
28         button.setOnClickListener(new ButtonListener()) ;
29         
30     }
31     //在MainAthread线程中接收数据,从而修改TextView的值
32     class MyHandler extends Handler {
33         @Override
34         public void handleMessage(Message msg) {
35             System.out.println("handleMessage--->"+Thread.currentThread().getName()) ;//得到当前线程的名字
36             String s = (String)msg.obj ;
37             textView.setText(s) ;
38         }
39         
40     }
41     //生成线程对象,让NetworkThread线程启动
42     class ButtonListener implements OnClickListener {
43         @Override        
44         public void onClick(View arg0) {
45             Thread t = new NetworkThread() ;
46             t.start();
47         }
48         
49     }
50     
51     //在Worker Thread线程中发送数据
52     class NetworkThread extends Thread {
53         @Override 
54         public void run(){
55             
56             System.out.println("network--->"+Thread.currentThread().getName()) ;//得到当前线程的名字
57             
58             //模拟访问网络:当线程运行时,首先休眠2秒钟
59             try {
60                 Thread.sleep(2*1000) ;
61             } catch (InterruptedException e) {
62                 e.printStackTrace();
63             }
64             //变量s的值,模拟从网络当中获取的数据
65             String s = "从网络中获取的数据" ;
66             //textView.setText(s) ; //这种做法是错误的,只有在Mainthread中才能操作UI            
67             
68             //开始发送消息
69             Message msg = handler.obtainMessage() ;    
70             msg.obj = s ;
71             handler.sendMessage(msg) ;//sendMessage()方法,在主线程或者Worker Thread线程中发送,都是可以的,都可以被取到
72         }
73     }    
74     
75     @Override
76     public boolean onCreateOptionsMenu(Menu menu) {
77         // Inflate the menu; this adds items to the action bar if it is present.
78         getMenuInflater().inflate(R.menu.main, menu);
79         return true;
80     }
81     
82 }
复制代码
这段代码的结构,和最上面的第一章节是一样的。
上方代码中,我们在子线程中休眠2秒来模拟访问网络的操作。
65行:用字符串s表示从网络中获取的数据;70行:然后我们把这个字符串放在Message的obj属性当中发送出去,并在主线程中接收(36行)。
运行后结果如下:
点击按钮后结果如下:
点击按钮后,后台输出结果如下:
可以看到,子线程的名字是:Thread-1118,主线程的名字是:main。
2、在MainThread中发送消息,在Worker Thread中接收消息:
【实例】点击按钮,在在MainThread中发送消息,在Worker Thread中接收消息,并在后台打印输出。
【代码】完整版代码如下:
XML布局文件代码如下:
复制代码
<RelativeLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/ButtonId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="在主线程中发送消息" />
</RelativeLayout> 
复制代码
MainActivity.java代码如下:
复制代码
1 package com.example.m03_handle01;
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.os.Looper;
7 import android.os.Message;
8 import android.util.Log;
9 import android.view.Menu;
10 import android.view.View;
11 import android.view.View.OnClickListener;
12 import android.widget.Button;
13 public class MainActivity extends Activity {
14     private Button button ;
15     private Handler handler ;
16     @Override
17     protected void onCreate(Bundle savedInstanceState) {
18         super.onCreate(savedInstanceState);
19         setContentView(R.layout.activity_main);
20         
21         button = (Button)findViewById(R.id.ButtonId) ;
22         
23         //当用户点击按钮时,发送Message的对象msg
24         button.setOnClickListener(new OnClickListener() {  //使用匿名内部类为button绑定监听器
25             
26             @Override
27             public void onClick(View v) {
28                 Log.i("onClick:", Thread.currentThread().getName());
29                 Message msg = handler.obtainMessage() ;
30                 handler.sendMessage(msg) ;
31             }            
32         }) ;
33         
34         WorkerThread wt = new WorkerThread() ;
35         wt.start() ;
36     }
37 
38     //在WorkerThread生成handler
39     class WorkerThread extends  Thread {
40         @Override
41         public void run() {
42             //准备Looper对象
43             Looper.prepare() ;
44             //在WorkerThread当中生成一个Handler对象
45             handler = new Handler() {
46                 @Override
47                 public void handleMessage(Message msg) {
48                     Log.i("handleMessage:", Thread.currentThread().getName());
49                     Log.i("后台输出", "收到了消息对象");
50                 }
51             };
52             //调用Looper的loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象
53             //如果消息队列中没有对象,则该线程阻塞
54             Looper.loop() ;   //通过Looper对象将消息取出来
55         }
56         
57     }
58     
59     
60     @Override
61     public boolean onCreateOptionsMenu(Menu menu) {
62         // Inflate the menu; this adds items to the action bar if it is present.
63         getMenuInflater().inflate(R.menu.main, menu);
64         return true;
65     }
66     
67 } 
复制代码
上方的第42行至54行代码:这是MainThread中发送消息,在Worker Thread中接收消息的固定写法。上面的三个步骤再重复一下:
准备Looper对象
在WorkerThread当中生成一个Handler对象
调用Looper的loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象;如果消息队列中没有对象,则该线程阻塞
注意,此时handleMessage()方法是在Worker Thread中运行的。

Android多线程----异步消息处理机制之Handler相关推荐

  1. Android之多线程----异步消息处理机制之Handler详解

    一.handler的引入: 我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟 ...

  2. Android之异步消息处理机制Handler源码解析

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/76083113 本文出自:[顾林海的博客] 个人开发的微信小程序,目前功 ...

  3. Android 异步消息处理机制(Handler 、 Looper 、MessageQueue)源码解析

    1.Handler的由来 当程序第一次启动的时候,Android会同时启动一条主线程( Main Thread)来负责处理与UI相关的事件,我们叫做UI线程. Android的UI操作并不是线程安全的 ...

  4. 以下未发布-Android的多线程以及异步消息处理机制,android移动开发基础案例教程源码

    1.Message: Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间进行数据交换.除了 what 字段,还可以使用 arg1 和 arg2 来携带整型数据,使用 ...

  5. Android异步消息处理机制 全解析

    Android异步消息处理机制主要是指Handler的运行机制以及Hanlder所附带的MessageQueue和Looper的工作过程. 本文将通过分析源码(api-28)的形式,全面解析Handl ...

  6. Android Handler 异步消息处理机制的妙用 创建强大的图片载入类

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自[张鸿洋的博客] 近期创建了一个群.方便大家交流,群号: ...

  7. Android线程之异步消息处理机制(二)——Message、Handler、MessageQueue和Looper

    异步消息处理机制解析 Android中的异步消息处理主要有四个部分组成,Message.Handler.MessageQueue和Looper. 1.Message Message是在线程之间传递的消 ...

  8. Android异步消息处理机制 深入理解Looper、Handler、Message三者关系

    转载子:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自[张鸿洋的博客] 很多人面试肯定都被问到过,请问Android中 ...

  9. Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

    很多人面试肯定都被问到过,请问Android中的Looper , Handler , Message有什么关系?本篇博客目的首先为大家从源码角度介绍3者关系,然后给出一个容易记忆的结论. 1. 概述 ...

最新文章

  1. Win XP等多个版本操作系统技术支持将到期
  2. Swift_类型选择
  3. oracle 视图使用rownum,Oracle数据对象--视图
  4. XDocReport 的简单使用 操作word 替换变量,动态图片,指定操作指令(程序)扩展(转自:http://www.cnblogs.com/fish-in-sky/p/4973237.html)
  5. vue+node实现中间层同步调用接口
  6. linux挂载VMFS硬盘,linux – ESX Guest中的硬件磁盘错误,在vmfs支持的驱动器上……这怎么可能?...
  7. Inception-ResNet-v1网络结构
  8. matlab实现7种滤波
  9. 2022华为软件精英挑战赛比赛经历
  10. 阿里云服务器如何隐藏真实ip
  11. 数字信号处理(1)- 频谱分析
  12. 硬盘服务器哪个好用吗,服务器用固态硬盘好还是机械硬盘好
  13. 计算机网络管理公开课观后感,青年网络公开课观后感
  14. android 玩pc游戏,如何在您的Android设备上玩经典PC游戏 | MOS86
  15. java销毁servlet_销毁servlet
  16. 课堂秩序难管理?这个方法造福师生
  17. 编写操作norflash的裸机程序
  18. python 报 KeyError: ('coupon_type', 'occurred at index act_code')故障
  19. 这四个重要的用研技能
  20. kingbase人大金仓常用sql命令(自用总结)

热门文章

  1. 【五线谱】音高表示 ( 低音谱号 | C1 36 音符音高表示 | C2 48 音符音高表示 | C3 60 音符音高表示 )
  2. 【开发环境】安装 Visual Studio Community 2013 版本 ( 安装软件 | 通过 GitHub 账号登录软件 | 启动软件 | 安装中文语言包 )
  3. 【C 语言】二级指针内存模型 ( 指针数组 | 二维数组 | 自定义二级指针 | 将 一、二 模型数据拷贝到 三 模型中 并 排序 )
  4. 【Windows 逆向】CE 地址遍历工具 ( CE 结构剖析工具 | 尝试进行瞬移操作 | 尝试查找飞天漏洞 )
  5. 【错误记录】Android Studio 编译信息输出乱码
  6. 【DBMS 数据库管理系统】数据库 体系化环境 ( 数据库体系化环境简介 | 四层体系化环境 | 数据集市 )
  7. 【Kotlin】Kotlin 类的继承 二 ( 属性覆盖 | 属性覆盖的四种情况 | 常量 / 变量 属性覆盖 | 子类初始化与属性覆盖 )
  8. 关闭子页面刷新父页面,不需要弹出确认窗口
  9. 台哥原创:java 扫雷源码
  10. salt常用命令(一)