我们知道在android中是不能在子线程中更新UI的,为此官方提供了Handler机制和AsyncTask 异步任务。当然我个人现在一般使用AsyncTask。两者的区别与介绍我在另外一篇博客里面也做了介绍l连接为 Android中同步与异步的问题。 但是学习Handler机制也是很有必要的。有助于我们理解线程之间通讯问题等等。而且在面试当中也会经常提到这个问题。
为什么要使用Handler
Handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它处理消息;例如在我们的Activity的生命周期中的底层的实现方法,很多都是使用Handler机制来完成的。
如何使用Handler
(一)简单认识Handler——更新UI
我们知道Handler可以在子线程中来更新UI的。那么如何来更新Ui呢?先看一个简单实例,下面再一步步由浅入深的认识Handler
实例:我们在Acyivity中的Oncreate方法中创建一个子线程,用于更新一个TextView。
public class MainActivity extends Activity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=(TextView) findViewById(R.id.textView);
//定义一个子线程更新UI
new Thread(){
public void run() {
try {
Thread.sleep(1000);
textView.setText("我是新的TextView");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
}
运行一下我们就会看到我们的程序崩溃了,而且会在后台显示如下信息。意思就是只能在主线程中更新UI。
但是如果我们使用了Handler会怎么样呢?
new Thread(){
public void run() {
try {
Thread.sleep(1000);
hanlder.post(new Runnable() {
@Override
public void run() {
textView.setText("我是新的TextView");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
好了,添加了Hanlder之后就可以更新UI了。当然你也可以通过Handler实现其他的效果,我最常用的就是使用Handler去进行界面跳转。也就是在我们欢迎界面2秒钟后进入登录界面。先看一下效果吧;
代码那就很简单了;在我们的Activity中的OnCreate添加以下代码就可以了
handler.postDelayed(new Runnable() {
@Override
public void run() {
Intent intent=new Intent(SplashActivity.this,MainActivity.class);
startActivity(intent);
overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right);
}
}, 2000);
Handler中还有很多的方法,这里给出Api,自己可以看看点击打开链接
(二)使用Handler在线程之间传递消息
我们首先定义一个子线程在里面发送消息。
//定义子线程,向主线程发送信息
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Message msg=new Message();//与Message msg=hanlder.obtainMessage()这句话等价
msg.arg1=1;//设置发送的消息
msg.arg2=2;
hanlder.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
接下来在我们的主线程中去接受消息,然后处理消息,我们在上面讲到处理消息时就可以直接更新UI了。
//这里是主线程,处理子线程发过来的信息
private Handler hanlder=new Handler(){
//此方法处理子线程中发过来的信息
public void handleMessage(Message msg) {
int a=msg.arg1;//接收发过来的信息
int b=msg.arg2;//接收发过来的信息
textView.setText("接收的参数a="+a+" 参数b="+b);
};
};
这里我们发现我们发送的只有俩个int类型的整数,有时候我们会从网络里面接收很多信息,例如JSON,文件等等各种信息,我们如何去处理呢?其实我们的msg也可以发送对象Object。我们可以定义一个对象去发送,发送过程和上面的一样只不过把int换成了我们要发送的对象。
既然Handler能够发送消息那肯定也能移除消息。 hanlder.removeCallbacks(runnable);将我们的发送消息的Runnable对象移除就好了。这个移除整个发送消息的方法,我们也可以移除单个消息 hanlder.removeMessage(what);
Handler还可以拦截消息。 拦截的话我们需要实现一个回调方法去拦截。下面代码演示一下;
第一步:发送一个消息 和上面的一样,在子线程中发送消息。
第二步:拦截消息;
//这里是主线程,处理子线程发过来的信息
private Handler hanlder=new Handler(new Callback() {
//在这个方法里面去截获信息。
@Override
public boolean handleMessage(Message msg) {
int a=msg.arg1;//接收发过来的信息
int b=msg.arg2;//接收发过来的信息
textView.setText("截获接收的参数a="+a+" 参数b="+b);
Log.i("info", "截获 a="+a+" : b="+b);
return false;
}
}){
//此方法处理子线程中发过来的信息
public void handleMessage(Message msg) {
int a=msg.arg1;//接收发过来的信息
int b=msg.arg2;//接收发过来的信息
textView.setText("处理参数a="+a+" 参数b="+b);
Log.i("info", "处理 a="+a+" : b="+b);
}
};
这里面有一个细节需要注意;在创建callback的时候。我们导入的包是 import android.os.Handler.Callback;
在这里我们重写了一个handleMessage方法。只不过这个方法返回一个boolean值。当返回的为true的时候,我们就成功的截获,那么下面的返回值为Void的方法将不会调用。返回为false下面的方法将会调用。
(三)Handler、Looper、MessageQueue之间的关系
先给出一张图,比较好理解,边看图边解释

我们可以看到,我们的Handler封装了消息的发送与接收,我们的Looper相当于一个消息的载体。里面有一个消息队列。这个消息队列就相当于是Handler发送过来的多条消息的容器。Handler在发送消息时候需要找到Looper,只要找到了Looper也就找到了我们的消息队列。Looper.loop相当于是一个死循环,不断地把消息取出来。
注意:虽然我们的Handler可以更新UI,但是尽量不要做一些好事操作,否则会出现卡死的状态。
handlerThread
为什么Handler需要和Looper关联,是因为Handler需要往Looper中的mQeueu里插入Message。所以,如果主线程需要和子线程之间通信,那有两个方法:
1. 主线程拥有子线程的Handler(注意:子线程的Handler需要关联自己(主线程)的Looper),通过该Handler发送消息即可。
2. 主线程创建一个Handler,但是将子线程的Looper传递给Hander,这样Handler也是往子线程Looper对象的mQueue里插入msg,子线程Looper.loop自然可以拿到主线程消息了。
但是,这两种情况都有弊端。就是线程并发的时候,不能保证子线程的Handler或者Looper对象已经被初始化了。什么意思呢?举个例子吧,现在有A和B,A需要使用到B创建的一个资源。他们并发执行,执行到某一地步的时候现在A需要B中的资源了,但是呢B还没有创建出来,这个时候就会出现一些问题了。所以才需要用到HandlerThread。
如何去使用呢?
public class SecondActivity extends Activity {
public Handler handler;
private HandlerThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_second);
super.onCreate(savedInstanceState);
//创建HandlerThread
thread=new HandlerThread("我是HandlerThread");
thread.start();
//创建Handler 相当于在HandlerThread的Hanlder处理消息。
handler=new Handler(thread.getLooper()){
@Override
public void handleMessage(Message msg) {
Log.i("info", "当前线程是"+Thread.currentThread());
super.handleMessage(msg);
}
};
handler.sendEmptyMessage(1);//在main线程发送消息
}
}
HandlerThread的用处
创建Handler的时指定的looper,可以是别的线程创建的。所以Handler中MessageQueue的轮询不一定非要是创建Handler的线程进行,还可以在别的线程中进行。
这个时候我们就需要使用HandlerThread这个类来创建这个Looper了,这样消息的处理就在新创建的HandlerThread中进行。
mThread = new HandlerThread("Handler Thread");
mHandler = new Handler(mThread.getLooper()){
public void handleMessage(android.os.Message msg) {... };
};
同样需要注意:在新创建的ThreadThread中不能有更新UI的操作。因为其是子线程。
主线程与子线程之间的通讯
我们前面提到了HandlerThread,就可以使用handler向主线程发送消息。也可以接受主线程发来的消息。
然后我们在Main线程中定义一个Handler用于处理子线程发送给过来的msg。
public class SecondActivity extends Activity {
public Handler handler;//子线程的Handler
private HandlerThread thread; //子线程
//主线程处理子线程发送过来的消息
private Handler hanlderMain=new Handler(){
public void handleMessage(Message msg) {
Log.i("main", "当前线程是"+Thread.currentThread());
Log.i("main", ""+msg.toString());
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_second);
super.onCreate(savedInstanceState);
//创建HandlerThread
thread=new HandlerThread("我是HandlerThread");
thread.start();
//相当于是子线程的Handler
handler=new Handler(thread.getLooper()){
@Override
public void handleMessage(Message msg) {
Log.i("info", "当前线程是"+Thread.currentThread());
Log.i("info", ""+msg.toString());
//子线程向主线程发送消息
hanlderMain.sendEmptyMessage(2);//这里使用主线程的Handler发送消息
super.handleMessage(msg);
}
};
//主线程向子线程发送消息
handler.sendEmptyMessage(1);//这里使用子线程的handler发送消息
}
}
我们定handle一个一个HandlerTread在里面使用主线程的handlerMain向主线程发送消息。然后在下面使用子线程的handler向我们的主线程发送消息。输出日至。
看到信息都已经打印出来。
runOnUiThread来更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView tv=(TextView) findViewById(R.id.second_tv);
tv.setText("使用runOnUiThread来更新UI");
}
});
总结:
1.不能直接在非UI线程直接更新UI(大多数时候)。 2.每次创建Handler时需要给它绑定一个looper,如果是主线程不给定具体的looper则会绑定默认的looper。 3.子线程运行时一定要调用start()方法。 4.在某些特殊情况下在非UI线程是可以更新UI的(不推荐使用)
(当刚启动Activity即onCreate里面此时onResume方法还没有执行的时候可以,因为在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的Thread是否是UIThread,若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。ViewRootImp是在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),就可以逃避掉checkThread()的检查进而更新UI。)
总之一句话:Handler机制很麻烦,建议使用异步任务。