Handler消息传送机制总结
一.线程通信相关知识
这里的消息传送其实就是不同线程的消息通信。
在Android中子线程是不能直接改变UI界面的,这是Android的运行机制里面规定的,比如你在子线程改变主线程界面的文本(tv.setText(“XXX”)),程序会马上蹦掉。
所以在子线程做完某些事情后,要改变主页面就要通过数据的通信,让主线程接收到信息后自己改变UI界面。
Android中Handler能满足线程间的通信。
(一)线程通信中相关的类
1.Handler先进先出原则。
2.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange)。
3.Message类用来保存数据。
(二)线程通信的过程
1.Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。
2.Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。
3. Message Queue(消息队列):用来存放线程放入的消息。
4.线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
(三)理解
看完上面的概念,对于初学者来说,脑袋其实还是一片混乱的。对Handler的消息传送机制还是不懂。其实最好的理解方法还是先学会它的简单使用,在进一步研究它的机制。
这里简单描述一下它的原理:
Handler用到了监听事件和回调方法的思想。
整个原理的大概步骤如下:
1.在类中创建Handler对象,用来接收和处理消息
2.然后再创建一个Loop对象,用来管理MessageQueue
3.MessageQueue来接收和保存子线程发过来的消息
4.上面只是做好接收消息的准备,做好相关准备后,才会让子线程发送消息
5.子线程直接调用Handler对象,通过Handler对象的SendMessage方法来对主线程发送数据
6.消息是保存在MessageQueue对象中的
7.Loop控制MessageQueue传递消息给Handler对象,这里就要注意了,虽然概念上说的是Handler能对子线程的数据进行接收和处理。但实际上它是接收MessageQueue里面的数据,然后进行处理的,MessageQueue里面可以接收很多很多的数据,它们以队列的形式排列,当Handler处理完一个数据后,MessageQueue就会再传递下一个数据给Handler。
8.上面是要重点理解的机制过程,MessageQueue对象内存放很多子线程发来的信息,有序的保存下来,并不做处理。而Handler一次只接收MessageQueue对象传来的一个数据,并进行处理。
9.这是最后一步了,Handler对象对传来的信息进行判断,并作相应的行为。
二.Handler的使用
(一)在子线程改变UI界面
主线程创建Handler对象,等待子线程发来信息,然后做相应的处理。
1.布局文件的简单设计
<LinearLayout 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:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="start" android:text="开始" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="stop" android:text="停止" /> LinearLayout> <TextView android:id="@+id/main_tv_showmessage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="message" />LinearLayout>
2.java代码的设计
package com.example.handlerchangeui;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.TextView;public class MainActivity extends Activity { // 定义布局里面的控件 static TextView tv_message; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化TextView tv_message = (TextView) findViewById(R.id.main_tv_showmessage); } // 创建Handler对象,匿名类的方式实现handleMessage方法,这里是子线程 Handler handler = new Handler() { /** * 接收Message信息, 只要Handler对象执行了SendMessage方法, 这个方法就会触发 */ @Override public void handleMessage(Message msg) { // 获取Message的what数值 int index = msg.what; // 获取Message里面的复杂数据 Bundle date = new Bundle(); date = msg.getData(); String name = date.getString("name"); int age = date.getInt("age"); String sex = date.getString("sex"); // 这里是主线程,可以直接对页面进行修改 String line = name + age + sex + "line" + index + "......"; tv_message.setText(line); } }; // 线程是否继续执行的布尔值 boolean continueRun = true; // 创建子线程的对象,匿名类的方式实现run方法 Runnable runable = new Runnable() { @Override public void run() { int index = 0; while (continueRun) { index++; // 这里的SendEmptyMessage只能发送的是数字 // handler.sendEmptyMessage(index); // 在子线程中利用Handler对象的SendMessage发送复杂的消息 // 先创建Message对象 Message msg = Message.obtain();// =Message.obtain(); // 和new Message();是一个意思 msg.what = index; // Message对象保存的数据是Bundle类型的 Bundle data = new Bundle(); data.putString("name", "李文志"); data.putInt("age", 18); data.putString("sex", "男"); // 把数据保存到Message对象中 msg.setData(data); // 使用Handler对象发送消息 handler.sendMessage(msg); // 让线程休眠一秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; // 开始按钮的监听事件 public void start(View v) { // 启动线程 continueRun = true; new Thread(runable).start(); } // 停止按钮的监听事件 public void stop(View v) { // 关闭线程 continueRun = false; }}
本示例只是简单的实现了Handler的使用,这里也展示了复杂数据的发送和接收。
运行程序之后,如图所示:
点击开始按钮后,页面的TextView改变,如图所示:
这里其实可以设计成一个简单的定时器。
上面的Handler对象是主线程中创建的,所以它可以直接对UI界面进行修改。
线程中通信如果只是用来发送数值的信息,可以在子线程中使用handler.sendEmptyMessage(what);这里的what是int类型的数值。这时不需要创建Message对象了。
(二)Handler的特殊例子
主线程给子线程发送一个数字,然后让子线程算出该数字以内的所有质数。然后以吐司的形式显示出来。
思路:这里在子线程创建Handler对象,主线程调用子线程的Handler对象来给子线程发送信息,子线程接收信息后做相应处理。
1.布局文件的简单设计
<LinearLayout 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:orientation="horizontal" > <EditText android:id="@+id/main_et_prime" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="getPrime" android:text="计算质数" />LinearLayout>
2.java代码的设计
package com.example.handlerforprime;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.text.TextUtils;import android.view.View;import android.widget.EditText;import android.widget.Toast;public class MainActivity extends Activity { // 定义布局控件 EditText et_prime; // 定义一个子线程类 的对象 PrimeThread thread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化控件 et_prime = (EditText) findViewById(R.id.main_et_prime); // 实例化子线程 thread = new PrimeThread(); // 启动子线程 thread.start(); // 上面是要先创建handler实例才能进行下面的handler发送信息 // 所有要让子线程先执行 } // 按钮的监听事件 public void getPrime(View v) { // 获取输入框内的数字 String prime = et_prime.getText().toString(); // 判断非空 if (TextUtils.isEmpty(prime)) { Toast.makeText(this, "你还没有数据输入数据", Toast.LENGTH_SHORT).show(); } // 防止非法数据 try { // 获取字符串数值 int primeNum = Integer.parseInt(prime); // 使用Handler对象发送数据 thread.handler.sendEmptyMessage(primeNum); } catch (Exception e) { Toast.makeText(this, "你输入的数据不合法", Toast.LENGTH_SHORT).show(); } } // 定义一个线程类 class PrimeThread extends Thread { // 定义Handler对象 public Handler handler; // 重写run方法 // 在线程里面创建Handler对象 @Override public void run() { super.run(); // 创建Loop对象,系统会自动创建MessageQueue Looper.prepare(); // 实例化Handler对象 handler = new Handler() { // 重写handlerMessage方法 // 只要是通过同一个Handler对象发送的数据肯定会执行这个方法 @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 接收what值的数据数据 int prime = msg.what; // 创建一个集合保存质数 List list = new ArrayList(); // 计算0到what之间的质数 outer: for (int i = 2; i <= prime; i++) { // 如果i对2到i的平方根的值求余都不等于零,那么这个数是质数 // 如果期间有一个值为零,就不是质数 for (int j = 2; j <= Math.sqrt(i); j++) { // 这里设置i!=2是为了让2保存到集合中去 if (i != 2 && i % j == 0) { // 跳到下一个i的值,如果直接使用continue是跳到下一个j的值; continue outer; } } // 把符合条件的i的值添加到集合中 list.add(i); } // 显示结合的元素,就是所有的质数的值 Toast.makeText(MainActivity.this, list.toString(), Toast.LENGTH_LONG).show(); } }; // 让Loop一直进行工作,即让handMessage一直在等待消息 Looper.loop(); } }}
程序运行结果,如图所示:
通过上面的俩个例子,应该能简单的理解Handler的使用方法。
对比这两个例子,我们发现示例中使用的Handler对象是同一个,一边是创建者,那么它就一直通过handlerMessage方法来在监听等待消息;另一边是调用者,使用handler.sendMessage(msg);或使用handler.sendEmptyMessage(what)来发送信息。最后创建者接收到信息并进行处理。
还有一个值得我们注意的是,上面的例子中在子线程创建Handler后,要调用Loop的方法Looper.prepare();和Looper.loop();其中第一个方法是让Loop对象创建,第二个方法是让Loop对象一直处于工作状态。正是因为有第二个方法的执行才能让handlerMessage方法内接收到数据。
但是在主线程主创建Handler就不需要调用Looper.prepare();和Looper.loop();因为系统已经在主线程加载了这里个方法。
其实上面只是Handler一些比较基础的用法和原理。下面在介绍一下复杂的原理知识。
三.Android消息处理的核心类
android的消息处理有三个核心类:Looper,Handler和Message。其实还有一Message Queue(MQ消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,所以它不算是个核心类。
1. 消息类:Message类
android.os.Message的主要功能是进行消息的封装,同时可以指定消息的操作形式,Message类定义的变量和常用方法如下:
(1)public int what:变量,用于定义此Message属于何种操作
(2)public Object obj:变量,用于定义此Message传递的信息数据,通过它传递信息
(3)public int arg1:变量,传递一些整型数据时使用
(4)public int arg2:变量,传递一些整型数据时使用
(5)public Handler getTarget():普通方法,取得操作此消息的Handler对象。
在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,但是有这么几点需要注意:
1)尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
2)如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
3)擅用message.what来标识信息,以便用不同方式处理message。
2. 消息通道:Looper
在使用Handler处理Message时,需要Looper(通道)来完成。在一个Activity中,系统会自动帮用户启动Looper对象,而在一个用户自定义的类中,则需要用户手工调用Looper类中的方法,然后才可以正常启动Looper对象。Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:
public class LooperThread extends Thread { @Override public void run() {//将当前线程初始化为Looper线程 Looper.prepare(); // ...其他处理,如实例化handler // 开始循环处理消息队列 Looper.loop(); } }
这是在子线程中创建Handler的情况,如果在主线程中创建Handler是不需要调用Looper.prepare(); 和 Looper.loop(); 方法。
关于这两个方法在系统底层做了什么事情:
Looper.prepare();创建了Loop对象,Loop对象是用来管理MessageQueue对象的,MessageQueue是帮助Handler保存数据的。
Looper.loop();是在底层保证线程的一直运行状态,只要调用者调用handler.SendMessage(–);方法,创建者就可以接收到数据。
Looper有以下几个要点:
1)每个线程有且只能有一个Looper对象,它是一个ThreadLocal
2)Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行
3)Looper使一个线程变成Looper线程。
那么,我们如何操作Message Queue上的消息呢?这就是Handler的用处了
3. 消息操作类:Handler类
Message对象封装了所有的消息,而这些消息的操作需要android.os.Handler类完成。什么是handler?handler起到了处理MQ上的消息的作用(只处理由自己发出的消息,所有Handler都是同一个对象来的),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。
还有一个要注意的:一个线程可以有多个Handler,但是只能有一个Looper!对应的Handler对象只接收自己对象发送的信息。
创建Handler实例化对象时,可以重写的回调方法:
void handlerMessage(Message msg);
有了handler之后,我们就可以使用Handler发送消息的所有方法:
post(Runnable)
postAtTime(Runnable, long)
postDelayed(Runnable, long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message, long)
sendMessageDelayed(Message, long)
上面就是Handler核心类的详细介绍,如果想要详细理解Handler来,要去理解核心类的系统后台的相关处理。。这里不做详细解释。
在实际应用中,子线程肯定使用来做耗时的操作的,比如:下载东西,遍历寻找文件,或计算很复杂的运算等等,在结果出来之后就要在主线程中显示出来。这里就需要在主线程中创建Handler对象,当子线程的工作任务完成后,调用Handler对象的方法来给主线程发送数据,主线程接收到数据后,进行简单处理就显示在UI界面上。
四.最后的总结:
(一)Handler类的主要作用:
1.在子线程中发送数据。
2.在主线程中接收数据,处理数据。
(二)Handler创建和使用的简化过程:
1.在主线程创建Handler对象,重写handlerMessage(msg)方法,用来随时接收数据
2.在子线程调用主线程创建的Handler对象,来给主线程发送信息。
3.主线程接收到子线程发送来的信息,进行处理,可以直接显示在UI界面上。
(三)其他的
对于基本概念我们都是要记住的:UI线程就是我们说的主线程。
还有就是Handler能在不同线程之间进行数据传递,并不局限于子线程和主线程,也可以是多个线程的数据传递,但是要注意的是,Handler对象只接收自己对象发送的数据。比如说,多个子线程利用主线程创建的Handler对象给主线程发送数据也是可以的,子线程发送的数据都会保存到MessageQueue里面,然后Handler对象对MessageQueue的里面的数据进行逐个的接收和处理。
上面的两个示例使用的数据传递,尽量不要像上面一样,一般的Message.what不是直接拿来使用的数据,而是用来判断某种行为的数据值,然后创建者做相应的行为,就像Intent数据传递的resultCode请求码的作用是一样的。
上面就是个人对Handler机制的理解,有些方面说的并不准确,MessageQueue的理解可能比较片面话和个人化,因为它都是系统的底层运转机制,在实际调用中并没有设计。
Handler机制相对来说也是一个比较复杂的过程,本文中如有误笔也请大家及时纠正。
更多相关文章
- Android线程通信机制-Handler(Java层)
- [原]Android应用程序线程消息循环模型分析
- android系统中的多线程(一): 关于在android中启动线程以及线程间
- Android中消息系统模型和Handler Looper
- Android开发实践:基于命令模式的异步任务线程