这里的消息传送其实就是不同线程的消息通信。
在Android中子线程是不能直接改变UI界面的,这是Android的运行机制里面规定的,比如你在子线程改变主线程界面的文本(tv.setText(“XXX”)),程序会马上蹦掉。
所以在子线程做完某些事情后,要改变主页面就要通过数据的通信,让主线程接收到信息后自己改变UI界面。
Android中Handler能满足线程间的通信。
看完上面的概念,对于初学者来说,脑袋其实还是一片混乱的。对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对象,等待子线程发来信息,然后做相应的处理。
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对象来给子线程发送信息,子线程接收信息后做相应处理。
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<Integer> list = new ArrayList<Integer>();
// 计算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的消息处理有三个核心类:Looper,Handler和Message。其实还有一Message Queue(MQ消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,所以它不算是个核心类。
android.os.Message的主要功能是进行消息的封装,同时可以指定消息的操作形式,Message类定义的变量和常用方法如下:
在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,但是有这么几点需要注意:
1)尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
2)如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
3)擅用message.what来标识信息,以便用不同方式处理message。
在使用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的用处了
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界面上。
对于基本概念我们都是要记住的:UI线程就是我们说的主线程。
还有就是Handler能在不同线程之间进行数据传递,并不局限于子线程和主线程,也可以是多个线程的数据传递,但是要注意的是,Handler对象只接收自己对象发送的数据。比如说,多个子线程利用主线程创建的Handler对象给主线程发送数据也是可以的,子线程发送的数据都会保存到MessageQueue里面,然后Handler对象对MessageQueue的里面的数据进行逐个的接收和处理。
上面的两个示例使用的数据传递,尽量不要像上面一样,一般的Message.what不是直接拿来使用的数据,而是用来判断某种行为的数据值,然后创建者做相应的行为,就像Intent数据传递的resultCode请求码的作用是一样的。
上面就是个人对Handler机制的理解,有些方面说的并不准确,MessageQueue的理解可能比较片面话和个人化,因为它都是系统的底层运转机制,在实际调用中并没有设计。
Handler机制相对来说也是一个比较复杂的过程,本文中如有误笔也请大家及时纠正。