简介: Handler
主要用于消息的处理,是Android SDK来处理异步消息的核心类,子线程与主线程通过Handler来进行通信,子线程可以通过Handler来通知主线程进行UI更新。
第一节
使用Handler实现异步更新UI
1.创建Handler
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
//收到消息后,将会调用此方法
//可以在这里进行UI更新操作
}
};
2.然后只要在子线程发送一条空消息即可
new Thread(new Runnable(){
@Override
public void run(){
handler.sendEmptyMessage(1);
}
}).start();
哈哈哈,然后就实现了异步更新UI,是不是觉得很简单啊!但其实本质上还是在主线程更新UI,而Handler的作用就是去通知主线程去
更新UI,因为Android是单线程模式
的,所以我们不能在子线程更新UI
,只能通过Handler去通知主线程更新UI。
那么问题来了,如果我们在多个地方发送了消息,而且每一个地方又要进行不同的UI更新操作,那么我们又要怎么区分呢?哈哈哈…其实这个我们完全不用担心,因为其实Handler早就为我们考虑到了,不信,我们再往回看看!
从这里,我们可以看到,这里还携带了一个参数“1”而这个1
就是这条消息的标识
,也可以理解为这条消息的id,通过这个我们就能区分消息了!
handler.sendEmptyMessage(1);
然后…创建Handler就变成了这样
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
switch(msg.what){
case 1:
{
//收到标识为1的消息后,将会调用这里
//可以在这里进行UI更新操作
}
}
}
};
扩展: 如果你认为Handler只能发送空消息,那么你就大错特错了,因为Handler还支持发送一个Message对象
,不过呢!在看代码之前,还是先看看这个吧!
参数 | 解析 |
---|---|
arg1 | 参数是一个int类型,可以传递一个int类型的消息 |
arg2 | 同arg1一样 |
obj | 这个就比较强了,参数是一个Object类型,可以传递一个Object类型的消息。 |
what | 一条消息的标识,也可以理解为一条消息的id,主要用于区分消息。 |
发送一个Message对象
Message对象是可以带参数的
,参数已经解析过了,这里就不做讲解了,哈哈哈…
Message message =new Message();
message.arg1=1;
message.arg2=2;
message.what=3;
message.obj="测试";
handler.sendMessage(message);
哈哈哈,怎么样呀!是不是感觉瞬间高大了很多呢,有没有感觉到啊?哈哈哈…然后Handler的代码就变成了这样。
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg)
{
super.handleMessage(msg);
switch (msg.what){
case 1:
{
//收到标识为1的消息后,将会调用这里
//可以在这里进行UI更新操作
}
break;
case 3:
{
//收到标识为3的消息后,将会调用这里
Log.d("arg1", msg.arg1 + "");
Log.d("arg2", msg.arg2 + "");
Log.d("obj", msg.obj + "");
}
}
}
};
运行一下。
Message优化:官方建议
我们将
Message msg =new Message();
改为
Message msg = handler.obtainMessage();
或者
Message msg = Message.obtain();
优点: 这样的话,可以由系统负责message对象的创建与销毁
↓
↓
↓
↓
↓
↓继续深入学习
↓
↓
↓
↓
↓
第二节
细心的朋友可能会注意到Handler对象还有一个post
的方法,用于发送一个Runnable对象,我们需要这个Runnable对象中,重写run()方法。
这里要注意的是:使用post发送Runnable对象 虽然看起来像是在子线程中执行,但其实它是在主线程中执行的!
由于我们每次与子线程打交道时,都会与Runnable接触,很容易就会误以为Runnable就是子线程,但其实能创建子线程的只有一个,那就是Thread,这下你应该明白了吧!这个完全是可以通过Log输出当前线程,进行验证的,不信你可以试试。
因为使用post发送的消息不会被Handler对象接收
,所以这里需要注意一下,不要把它们两个搞混了,所有的操作都只要在Runnable对象里写好就行了。
一般用法:
new Thread(new Runnable(){
@Override
public void run()
{
handler.post(new Runnable()
{
@Override
public void run()
{
//UI更新操作
//这里的所有操作都会在主线程中执行
}
});
}
}).start();
当然,你也可以这样写
private class myRunnable implements Runnable{
@Override
public void run()
{
// UI更新操作
// 这里的所有操作都会在主线程中执行
}
}
然后…
myRunnable myRunnable=new myRunnable();
handler.post(myRunnable);
当然,你也可以在创建Handler时做好相关操作,然后你只要在你想要调用的地方发出一条消息即可。
嗯呐…Hadler的基本用法,就这些了,现在赶紧去试试吧!哈哈哈…
↓
↓
↓
↓
↓
↓继续深入学习
↓
↓
↓
↓
↓
第三节:
HandlerThread
如果我们想要在Handler对象中执行一些比较耗时的操作的话, 我们一般会想到的是直接在把它丢到子线程中去执行,所以直接在Handler中新建一个子线程不就完事了吗?我想,大多数人的反应大概都是这个样子的吧!
Handler Handler=new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
new Thread(new Runnable(){
@Override
public void run(){
//在这里执行耗时操作
}
}).start();
}
};
嗯呐,这样确实实现了异步执行,但是如果这个Handler对象接收到的消息很多呢?那么又会怎样呢?
为了让效果更加明显,我在Handler对象中写了一条日志输出,输出的内容是当前的线程,并且向它发送了三条消息,运行结果如下:
同样的,我又在Looper线程中做了同样的操作,运行结果如下:
分析:
现象 | 结论 |
---|---|
普通的Handler对象创建了三条线程 | 每接收到一条消息,就会创建一条新的线程,这个方法对线程的开销很大,所以不推荐使用。 |
Looper线程只有一条线程 | 无论接收到多少条消息,它都只会创建一条线程 |
那么现在你应该知道Looper线程的作用了吧!
注意:这里主要是想告诉大家什么是Looper线程,并不是让大家去写。官方已经封装好了Looper线程,名叫HandlerThread,这个…我们后面再讲。
不过在学习Looper线程之前呢,还是先了解一下这个吧!
词义 | 解释 |
---|---|
Looper | 一个线程只能有一个Looper,一个Looper只能有一个MessageQueue,Looper负责消息的循环,它会一直从MessageQueue里面取出 Message,并交给相应的 Handler 进行处理。 |
MessageQueue | 消息队列,用于存放Handler发送来的消息 |
Message | 消息体,用于装载需要发送的对象,存放于MessageQueue中 |
Handler | 这个…我想…已经不用介绍了吧! |
Looper线程
如果我们想要在子线程中使用Handler的话,我们还需要在线程里面创建一个Looper对象,才能够使消息能够进行循环。
Looper线程其实就是一个拥有Looper的子线程
那么问题来了,为什么我们在主线程中使用的Handler时,不用创建Looper对象呢?因为主线程默认是创建Looper对象的
,所以就不需要我们再去创建了。
创建Looper线程
具体代码如下:
private class myThread extends Thread{
public Handler Handler;
@Override
public void run()
{
Looper.prepare();
Handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("测试2", Thread.currentThread() + "");
//异步任务
//可执行一些比较耗时的操作
//不能执行UI更新操作
}
};
Looper.loop();
}
}
使用方法与上面有些不一样
具体请看代码:
例示:向子线程发送一条消息
myThread myThread=new myThread();
myThread.start();
try
{
myThread.sleep(500);
}
catch (InterruptedException e)
myThread.Handler.sendEmptyMessage(0);
运行一下。
通过对比,我们可以很清楚的看出,它是在子线程里面执行的,所以不能执行UI更新操作,可用于一些比较耗时的操作
。
测试1,是我在主线程里的Handler对象里面写的,输出的是当前的线程,仅仅只是为了做一下对比。
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg)
{
super.handleMessage(msg);
Log.d("测试1", Thread.currentThread() + "");
//UI更新操作
}
};
handler.sendEmptyMessage(0);
继续学习Looper线程
在了解完上面的东西后。我们现在在主线程中创建一个Handler对象,并且为它指定一个Looper对象,就把它指定为Looper线程中的Looper对象吧!
具体执行步骤如下:
private class myThread extends Thread{
public Looper looper;
@Override
public void run()
{
looper.prepare();
looper=looper.myLooper();
looper.loop();
}
}
这里解释一下,上面的
Looper.myLooper();
用于获取当前线程的Looper对象
记得在Activity的onDestroy中写上
myThread.looper.quit();
用于终止Looper线程的Looper循环
然后在主线程创建Handler对象时指定这个Looper对象
myThread myThread=new myThread();
myThread.start();
Handler handler=new Handler(myThread.looper){
@Override
public void handleMessage(Message msg)
{
super.handleMessage(msg);
Log.d("嘿嘿",Thread.currentThread()+"");
//异步任务
//可执行一些比较耗时的操作
//不能执行UI更新操作
}
};
handler.sendEmptyMessage(1);
然后。。。
程序崩溃了
这是为什么呢?
通过程序运行日志,我们可以看到
myThread.looper
这里出现了空值问题,因为当我们的程序创建Handler对象时,子线程中的Looper对象还没有来得及创建,所以才导致了程序崩溃,那么我们又应该去怎么解决呢?
解决方法:
在启动Looper线程后,先让主线程先休眠500ms,再去创建Handler对象。这样就解决了空值问题。
myThread myThread=new myThread();
myThread.start();
try
{
Thread.sleep(500);
}catch (InterruptedException e){}
Handler handler=new Handler(myThread.looper){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
//异步任务
//可执行一些比较耗时的操作
//不能执行UI更新操作
}
};
Message msg= Message.obtain();
handler.sendMessage(msg);
或者
myThread myThread=new myThread();
myThread.start();
try{
myThread.sleep(500);
}
catch (InterruptedException e){}
Handler handler=new Handler(myThread.looper){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
//异步任务
//可执行一些比较耗时的操作
//不能执行UI更新操作
}
};
Message msg= Message.obtain();
handler.sendMessage(msg);
这时,我们在Handler对象里输出一条日志,内容为当前线程,我们会看到它输出的是子线程,而不是主线程
所以 ,我们的写在主线程的Handler也己经成功实现了异步执行,然后就可以在里面执行一些比较耗时的操作
了,因为它是在子线程中执行的,所以我们不能执行UI更新操作
。
其实这里和之前的在子线程写一个Handler对象是一样的,只不过是分开写了罢了。
Google官方
不推荐我们去自己自定义Looper线程,推荐我们直接使用己经封装好的Looper线程HandlerThread
,它能为我们解决多线程并发问题,而且简单,易用。
HandlerThread是什么?
相信你现在应该已经知道了HandlerThread是什么了,HandlerThread其实就是Googler官方封装的Looper线程,可执行一些比较耗时的操作,同样,这个也不能执行UI更新操作,那么我们到底要怎么使用HandlerThread呢?请看下面:
HandlerThread使用方法:
HandlerThread HandlerThread=new HandlerThread("这个随便写");
HandlerThread.start();
Handler handler=new Handler(HandlerThread.getLooper()){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
Log.d("嘿嘿",Thread.currentThread()+"");
//异步任务
//可执行一些比较耗时的操作
//不能执行UI更新操作
}
};
handler.sendEmptyMessage(1);
运行一下:
怎么样?学会了吗?使用HandlerThread还是很简单的。
写博客真是不容易啊!实在是心累呀!不过也加深了我对Handler的理解,所以不亏…哈哈哈。