1.Handler的定义:
Handler,即“处理者”,用来发送和接受信息并且按照信息的匹配来实现执行功能代码。
Handler接收子线程发送的数据,并用此数据配合主线程更新UI,跟UI主线程交互用。
由于Handler运行在主线程中(UI线程中),与子线程可以通过Message对象来传递数据。
Handler承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。
(除了用Handler完成界面更新外,还可以使用runOnUiThread()来更新,甚至更高级的事务总线。)
Handler可以分发Message对象和Runnable对象到主线程中,每个Handler实例都会绑定到创建它的线程中(一般是位于主线程)
Handler 作用:
- 安排Message在某个主线程中某个地方执行;
- 安排Runnable在某个主线程中某个地方执行;
- 安排一个动作在不同的线程中执行。
API:Handler
本文大部分内容来自于 http://blog.csdn.net/coder_pig/article/details/46997945 的整理
2.Handler类的引入:
Handler类的引入
3.Handler的执行流程图:
相关名词
- UI线程:主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
- MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue;
- Message:Handler接收与处理的消息对象;
- Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象;
- Looper:每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理。
Looper 类用来为一个线程跑一个消息循环。
线程在默认情况下是没有消息循环与之关联的,Thread类在run()方法中的内容执行完之后就退出了,即线程做完自己的工作之后就结束了,没有循环的概念。
调用Looper类的 prepare() 方法可以为当前线程创建一个消息循环,调用 loop() 方法使之处理信息,直到循环结束。
大多数和消息循环的交互是通过 Handler 类进行的。Handler之所以能够实现对view的处理也是因为使用了Looper。
执行流程:
- 子线程想修改Activity中的UI组件时,新建一个Handler对象,通过这个对象向主线程发送信息;
- Handler给发送的信息msg的target赋值为handler自身,msg会先到主线程的MessageQueue进行等待;
- 由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理,回调相应方法。
Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。
4.Handler的相关方法:
- void handleMessage(Message msg):处理消息的方法,通常是用于被重写
- sendEmptyMessage(int what):发送空消息
- sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信息
- sendMessage(Message msg):立即发送信息
- sendMessageDelayed(Message msg, long delayMillis ):指定延时多少毫秒后发送信息
- final boolean hasMessage(int what):
- 如果是参数为 (int what),检查消息队列中是否包含what属性为指定值的消息
- 如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息
- boolean post(Runnable r): 使Runnable r添加到消息队列
- boolean postAtTime(Runnable r, long uptimeMillis): 在设定的时间再将Runnable r添加到消息队列
- boolean postDelayed(Runnable r, long delayMillis):指定延时多少毫秒后将Runnable r添加到消息队列
5.Handler的使用示例:
1)Handler写在主线程中:传递message
在主线程中,因为系统已经初始化了一个Looper对象,所以直接创建Handler对象,就可以进行信息的发送与处理了。
- 在外面重写Handler以及内部的handleMessag()方法;
- 在特定时刻发出消息:myHandler.sendMessage(msg);
- 根据重写的handleMessage()方法中的定义,在收到对应的message时进行对应的操作;
代码示例:
SplashActivity作为闪屏界面,是程序进入的第一个界面,根据存储的accessToken在此进行判断,下一步是跳转到主界面还是登录界面。
public class SplashActivity extends BaseActivity {
private static final int WHAT_INTENT2LOGIN = 1;
private static final int WHAT_INTENT2MAIN = 2;
private static final long SPLASH_DUR_TIME = 1000;
private Oauth2AccessToken accessToken;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case WHAT_INTENT2LOGIN:
intent2Activity(LoginActivity.class);
finish();
break;
case WHAT_INTENT2MAIN:
intent2Activity(MainActivity.class);
finish();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
accessToken = AccessTokenKeeper.readAccessToken(this);
//判断Token是否有效
if(accessToken.isSessionValid()) {
handler.sendEmptyMessageDelayed(WHAT_INTENT2MAIN, SPLASH_DUR_TIME);
} else {
handler.sendEmptyMessageDelayed(WHAT_INTENT2LOGIN, SPLASH_DUR_TIME);
}
}
}
2)Handler写在主线程中:传递Runnable
通过handler的延时发送message,可以延时处理一些事务的处理。
比如设计某一个应用的启动时有一个欢迎界面,让这个界面停留几秒后再跳转到里一个Activity主页
public class SplashActivity extends Activity{
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
startMainAvtivity();
}
private void startMainAvtivity(){
new Handler().postDelayed(new Runnable(){
@Override
public void run() {
intent = new Intent(SplashActivity.this,MainActivity.class);
startActivity(intent);
SplashActivity.this.finish();
}
}, 3000);
}
}
上述Handler().postDelayed()代码的利用Handler来延迟启动另一个Activity。
值得注意的是:认为上面的代码在主线程里面启动了新线程,然后在新线程里面跳转了Activity,是错误的。
只要记住:java里面启动新线程有两种方法
- 一种是利用一个类去继承Thread,然后实例化后执行start()方法;
- 另一种是实现Runable接口,把Runnable对象作为参数去实例化一个Thread对象再执行start()方法。
3)Handler写在子线程中
如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象。
- 直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创建配套的MessageQueue;
- 创建Handler对象,重写handleMessage( )方法就可以处理来自于其他线程的信息了!
- 调用Looper.loop()方法启动Looper
使用示例:输入一个数,计算后通过Toast输出在这个范围内的所有质数
实现代码:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/etNum"
android:inputType="number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入上限"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="cal"
android:text="计算"/>
</LinearLayout>
public class CalPrime extends Activity{
static final String UPPER_NUM = "upper";
EditText etNum;
CalThread calThread;
// 定义一个线程类
class CalThread extends Thread {
public Handler mHandler;
public void run(){
//创建一个Looper对象,而它的构造器会创建配套的MessageQueue
Looper.prepare();
mHandler = new Handler(){
// 定义处理消息的方法
@Override
public void handleMessage(Message msg){
if(msg.what == 0x123){
int upper = msg.getData().getInt(UPPER_NUM);
List<Integer> nums = new ArrayList<Integer>();
// 计算从2开始、到upper的所有质数
//" outer:"就是一个自己定义的标签名,通过break或continue来跳转到这个位置,一般是用来跳出循环。
//这个语法类似于C语言中goto。
outer:
for (int i = 2 ; i <= upper ; i++){
// 用i处于从2开始、到i的平方根的所有数
for (int j = 2 ; j <= Math.sqrt(i) ; j++){
// 如果可以整除,表明这个数不是质数
if(i != 2 && i % j == 0){
continue outer;
}
}
nums.add(i);
}
// 使用Toast显示统计出来的所有质数
Toast.makeText(CalPrime.this , nums.toString()
, Toast.LENGTH_LONG).show();
}
}
};
//调用Looper.loop()方法启动Looper
Looper.loop();
}
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
etNum = (EditText)findViewById(R.id.etNum);
calThread = new CalThread();
// 启动新线程
calThread.start();
}
// 为按钮的点击事件提供事件处理函数
public void cal(View source){
// 创建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER_NUM ,
Integer.parseInt(etNum.getText().toString()));
msg.setData(bundle);
// 向新线程中的Handler发送消息
calThread.mHandler.sendMessage(msg);
}
}
在这里,Toast能够显示信息,是因为目前并非运行在UI主线程,Toast.show在Looper.prepare和Looper.loop之间,这段代码需要将自己的信息放松到消息队列中才能够被执行,Looper.prepare和Looper.loop的作用就是让Toast消息传入消息队列,起到同在UI线程调用一样的效果。
Handler之所以能够实现对view的处理也是因为使用了Looper。
知识补充之Message怎么包含数据:
- Message对象
- Message对象携带数据,通常它用arg1,arg2来传递消息,当然它还可以有obj参数,可以携带Bundle数据。系统性能消耗非常少。
- 初始化: Message msg=handler.obtainMessage();
- Bundle对象
- Bundle是Android提供的类,可以把它看做是特殊的Map,即键值对的包。
- Bundle 特殊在键和值都必须要是基本数据类型或是基本数据类型的数组(Map的键值要求都是对象),特别的,键要求都是String类型。
- 用Message来携带Bundle数据:
- 放入:msg.setData(Bundle bundle);
- 取出:msg.getData();
Bundle bundle = new Bundle();
bundle.putInt("myage", 21);
bundle.putString("myname", "yummylau");
msg.setData(bundler);
msg.sendToTarget();
Bundle myData = msg.getData();
int myage = myData.getInt("myage");
String myname = myData.getString("myname");
知识补充:不能在子线程中 new Handler
在使用Handler时,在子线程中 new Handler 会出现异常,
Can’t create handle inside thread that ha not called Looper.prepared
在没有调用 Looper.prepare()的时候不能创建 Handler;
当我们在主线程中创建Handler对象的时候没有问题,是因为主线程会自动调用Looper.prepare()方法去
给当前主线程创建并设置一个Looper对象,随意在Handler构造函数中从当前线程的对象身上拿到这个Looper。
但是子线程中并不会自动调用这个方法,所以要想在子线程中创建Handler对象就必须在创建之前手动调用Looper.prepare()方法,否则就会报错。
一个介绍比较详尽的文章:子线程中可以使用Handler吗?