对Handler消息机制的理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011706736/article/details/39647525

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对象

没有更多推荐了,返回首页