Handler消息机制中无非都是围绕着:Handler,MessageQueue,Looper来做文章的,那么这三者的概念就首先你要清楚它是什么,为什么,怎么办.围绕这三个问题那么问题就不断的被一层一层的深入,最后也就解开了问题的谜题了,对于初学者来说Handler是一个神奇的东西,鸡肋吧,有人说我只要会用sendMessage()就好啦,其实不然
handler:生成,发送,处理消息
Message:数据载体
MessageQueue:Message的队列,FIFO
Looper:轮询器,用来轮询MessageQueue,调用Message中的数据
Android消息机制的概述
在刚刚自学android的时候,对android的消息机制相信和很多的刚入门同学一样就知道一个sendMessage()和handlerMessage();但是它的内部又是怎么去实现呢?自己在网上查了很多资料也看了很多csdn大牛的文章,但是有句话不是说嘛"书上得来终觉浅,觉知此时要躬行" ,最后决定还是要把我对android消息机制的艰(zuo)辛(si)历程记录下来.
首先要学一样东西,对这个东西的定义是最为重要的,试问你都不知道其为何物,那你还谈什么去深入呢?
要想知道你对消息机制的知识到底掌握的怎么样,只要你能回答下面的几个问题那么就算你掌握了:
1.:一个handler是不是只能对应一个looper?
2.:一个looper是不是只能对应一个handler?
3.:MessageQueue和looper是一对一对应的,looper也可以在子线程中用?
问题的答案嘛,只要你读完这篇博客你就懂了心里有答案了,到博文的最后一处作死的小编也会把答案贴出来,所以同学们不妨在阅读下文的时候,带这三个问题,我相信你会在看着看着的时候就跟我一样恍然大悟的.
在Android4.0以后不能在UI线程访问网络,子线程也不能更新UI.举个实际的例子,就拿下载东西的进度来说,下载任务是一个耗时的操作,好那么把下载任务放在子线程,如果将耗时操作放在UI线程的话会造成"卡顿"现象,那么就把下载的耗时操作放在子线程,那么问题又来了下载任务的ui又要怎么去更新呢?在子线程可不能够更新ui,google的开发者是聪明的,他们就研制出一种"Handler消息机制"的药,那么问题就解决了.
Handler消息消息机制主要包括4个关键对象: Message,MessageQueue,Looper,Handler
Message:
数据载体
Message是在线程间消息传递的载体,Message的what字段可以携带整型信息,obj字段可以携带一个对象
MessageQueue:Message队列,采用FIFO(先进先出,Ps:虽然是采用先进先出的原理,但是它的具体实现不是用队列而是单链表实现的)
Looper:
轮询器,用来轮询MessageQueue,调用Message中的数据
Looper是直接对每个MessageQueue负责的,当Looper调用loop()方法后,就会进入一个无限循环的过程,当发现有存在一条消息的时候Looper就会将MessageQueue的消息取出,并且将其传递到Handler的handlerMessage()方法中去.还有一点需要提醒的是每个线程中只会有一个looper对象,在ActivityThread中会默认的开启一个Looper,在子线程中需要Looper.loop()手动开启 ,
不断从
MessageQueue
中抽取
Message
执行。因此,一个线程中的
MessageQueue
需要一个
Looper
进行管理。
Looper
是当前线程创建的时候产生的(
UI Thread
即主线程是系统帮忙创建的
Looper
,而如果在子线程中,需要手动在创建线程后立即创建
Looper[
调用
Looper.prepare()
方法
]
)。也就是说,会在当前线程上绑定一个
Looper
对象。
Handler
:生成,发送,处理消息流程图
图解:Handler消息处理需要在UI线程中创建一个Handler对象.在子线程中通过调用Handler的sendMessage()方法,将消息存放到 UI线程 的消息队列中,通过Looper对象将消息取出,最后再分发会Handler的handlerMessage()中;
Ps:Handler不仅只有handlerMessage()它还有一个很常用的方法post()方法,在做图片的轮播的时候就要用到这个方法,这个本来是打算到后面讲Handler消息机制应用的时候给同学们讲的
接下再把Handler消息自己的时序图贴上来,心中围绕着这两个图想问题
首先Handler会生成Message.然后通过sendMessage()方法将Message发送到消息队列MessageQueue中去;还有一点需要说明的是,轮询器Looper是一直在轮询状态的,一直对消息队列MessageQueue进行轮询,如果一旦发现有Message,将Message返回;然后通过Message中Target拿到Handler实力,进行调用dispatchMessage() 将Looper拿到的message分发出去,最后Handler拿到消息,执行handlerMessage()方法
接下来通过解读源码告诉大家,为什么在子线程中跟新UI会抛异常
void checkThread(){
if(mThread != Thread.currentThread()){
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can
touch its views");
}
}
相信同学们刚刚接触android的时候对这句异常"
"
Only
the original thread that created a view hierarchy can
touch its views
"
"一点也不陌生吧?怎么现在是不是变得越来越有意思了呢
针对checkThread抛出的异常,会导致程序出现ANR异常,那么怎么解决这个问题呢?就引进Handler机制了,这也是系统之所以会提供Handler,主要就是为了解决子线程中无法访问UI,那大家有没想过为什么子线程不让访问ui呢?原因很简单,会导致系统出现很多无法预知的状态.那么为甚不对线程加锁呢?加锁不就完事了吗?加锁的话会又会出现:降低了UI的访问效率,也就是我们常说的卡顿,还有就是加锁后程序的逻辑又会变得复杂.那么为何不用Handler切换一些UI就完事了呢?
2.Android消息机制的内部实现原理(Handler,MessageQueue,Looper的协调工作)
当Handler创建完后,它内部的Looper和MessageQueue就可以和Handler协同工作,再通过Handler的post方法将一个Runnable放到Handler内部的Looper中处理,.当然啦你也可以通过send方法去发送一个消息.post方法最终也是通过调用send方法来实现的,Looper是运行在Handler所在的线程中的
这里需要强调一点的就是,如果你是在主线程中处理message,那么在主线程中创建handler对象,系统的Looper已经准备好了,且MessageQueue也初始化了,并且轮询器的轮询方法loop默认是开启的;但是,如果你是在子线程中创建handler,那么就需要显示的调用Looper的prepaer()和loop()方法,来进行初始化Looper和开启轮询器
下面给一个实例代码
public class MainActivity2 extends Activity implements OnClickListener{
private Button send;
private TextView recieve;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
recieve.setText((String) msg.obj);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
send = (Button) findViewById(R.id.bt_send);
recieve = (TextView) findViewById(R.id.tv_recieve);
send.setOnClickListener(this);
recieve.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_send:
new Thread(){
public void run() {
Message msg = new Message();
msg.obj = "您有新的消息,请注意查收"+ System.currentTimeMillis();
handler.sendMessage(msg);
}
}.start();
break;
}
}
}
这里头有两道很典型的题目,解答出来,Handler机制基本就了解;
Android中Looper的实现原理,为什么调用Looper.prepare()就在当前线程关联了一个Loopepr对象,它是如何实现的?
1.线程的通信机制
首先,looper handler MessageQueue 三者共同实现了android系统中线程通信的机制.假如在A B 两个线程中要进行消息传递,首先给每个子线程绑定一套handler looper MessageQueue机制,然后这撒个对象都与其所属的对象对应.然后A线程通过调用B线程的Handler对象,发送消息.这个消息会被Handler发送到B中的MessageQueue,而B线程中的Looper对象一直在for循环里无限遍历.MessageQueue一旦发现消息队列里头接收到新的消息,就会的消息进行处理,处理的过程中会回调自身Handler的HandlerMessage方法,从而实现了不同线程间的通信.
2.Looper实现通信的原理
首先Looper类里头包含一个消息队列和一个线程对象.当创建Looper时,会自动的创建一个消息队列,通信将内部线程对象指向创建Looper的线程.当开启looper后(looper.loop()),会自动进入无限for循环中,不断的遍历消息队列,如果没有消息阻塞,有消息则会回调handler的handlerMessage()方法进行处理消息.
3.Looper.prepare(),是一个绑定的过程,将Handler创建的looper对象的消息队列和空线程指向当前线程中
首先,要使用Looper机制一般都会在当前线程中创建Handler对象,里面会自动创建一个looper对象和消息队列,这里的消息队列属于当前线程空间中,但此时的looper还不会去遍历,也没有绑定到当前的线程中.期中looper对象内部也包含一个空消息队列对象和空线程.通过looper.prepare()方法,先让消息队列指向当前线程的消息队列,让空线程也指向当前线程.从而实现绑定
android是如何处理UI与耗时操作的通信?
主要有三种方法,一为Handler,二为AsyncTask,三为自己开子线程执行耗时操作,然后调用Activity的runOnUiThread()方法更新ui;
handler机制是,在主线程中创建handler对象,
当执行耗时操作时,新建一个线程,在这个线程中执行耗时操作,通过调用handler的sendMessage,post等方法,更新ui界面;
AsyncTask本质上是一个线程池,所有的异步任务都会在这个线程池中的工作线程中执行,当需要操作ui界面时,会和工作线程通过handler传递消息。
自己开子线程执行耗时操作,然后调用Activity的runOnUiThread()方法更新ui,这种方法需要把context对象强制转换成activity后使用
handler机制的优点是 结构清晰,功能明确,但是代码过多;
asyncTask简单,快捷,但是可能会新开大量线程,消耗系统资源,造成FC
第三种方法最好用,代码也非常简单,只是需要传递context对象