关闭

五分钟搞懂Android的消息机制(Handle,Looper,MessageQueue)

标签: HandleMessageQueueandroidLooper心得
365人阅读 评论(0) 收藏 举报
分类:

 引言: 提到消息机制相比各位都应该不是很陌生,日常开发的过长中肯定难免遇到这方面的问题。Handle的运行需要底层的Looper,MessageQueue进行支撑。MessageQueue的中文翻译是消息队列,说白了,他的内部储存了信息,以队列的形式来提供插入和删除的工作。虽说是队列,但是其内部的实现确实单链表。而MessageQueue只提供了对于消息的存储功能,但是如果要对消息进行遍历和处理的时候,就轮到Looper发挥它的作用了,Looper会以无限循环的形式去查询消息队列中是否有消息,如果有的话会拿出来处理,如果没有的话,会一直等待。并且每个线程都会有属于自己的Looper对象,那么如果获取当前线程的Looper对象呢,我们就需要引出ThreadLocal,通过ThreadLocal就可以轻松的获取当前线程的looper对象。非主线程在使用Looper的时候都需要自己创建,而只有主线程会自己进行创建Looper对象,这也就是为什么主线程可以默认使用Handle来进行消息处理而其他线程不行的原因。Handle的使用过程很简单,主要目的是为了通过它轻松的将一个任务切换到Handle所在的线程中去执行。很多人认为Hanldle是用来进行更新UI的操作,是的,这是他的一个特殊的使用场景,但是这也是读者平常所用的最常见的作用。我们通常会在子线程中进行一些耗时或者下载任务的操作,然后把这些内容更新到控件上进行显示,所以大部分读者可能都认为Handle会被经常用来更新UI使用。那么,今天就浅谈一下Android的消息机制————也就是Handle的运行机制。

       在搞清楚Handle运行机制之前,我们可能会经常犯这样一个错误。首先我们来看看代码。

  1. public class MainActivity extends Activity implements View.OnClickListener {  
  2.       
  3.     private TextView stateText;  
  4.     private Button btn;  
  5.       
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.         stateText = (TextView) findViewById(R.id.tv);  
  11.         btn = (Button) findViewById(R.id.btn);  
  12.           
  13.         btn.setOnClickListener(this);  
  14.     }  
  15.   
  16.     @Override  
  17.     public void onClick(View v) {  
  18.         new NewUI().start();  
  19.     }  
  20.       
  21.     //工作线程  
  22.     private class NewUI extends Thread {  
  23.         @Override  
  24.         public void run() {  
  25.             //......处理比较耗时的操作  
  26.               
  27.             //处理完成后改变状态  
  28.             stateText.setText("completed");  
  29.         }  
  30.     }  
  31. }  
    



    我们想在子进程中进行更新UI的操作,但是会报出这么一个错误。
[java] view plain copy
  1. ERROR/AndroidRuntime(421): FATAL EXCEPTION: Thread-8  
  2. ERROR/AndroidRuntime(421): android.view.ViewRoot$CalledFromWrongThreadException:   
  3. Only the original thread that created a view hierarchy can touch its views.  

    提示我们在更新UI操作的时候,不能在非UI线程中进行操作。所谓UI线程就是主线程,ActivityThread。

    可是系统为什么不允许我们在非UI线程中进行更新UI的操作呢:这是因为Android的UI控件不是线程安全。那问题就来了,解决线程安全的问题,给它加个锁问题不就解决了。但是如果这样操作 就会带来致命的缺点。1.上锁机制会导致UI访问的逻辑变得非常的复杂。2.上锁机制会降低UI访问的效率,并且会阻塞某些线程的执行。大大降低了系统运行的效率 
    所以,如果没有Handle,我们就无法将访问UI的工作切换到主线程去执行,导致我们无法更新UI。

   

    Handle的工作原理:
    Handle创建的时候回采用当前线程的Looper来构建内部的消息循环系统。除了主线程之外,其与线程都需要自己创建Looper。通过Handle的post的方法讲一个Runnable投递到Looper对象中进行处理,还有一个send方法也一样。但send的方法最终还是会调用post方法。然后MessageQueue的enqueueMessage方法将这个消息放入消息队列,然后当Looper发现新消息的时候,就会处理这个消息。然后最终Handle的handlemessage方法就会被调用去处理该消息。


    ThreadLocal的工作原理:
    ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后只有指定的线程才可以获取到存储的数据。但是对于其他线程来说却无法获取,通过简单的代码让我们来理解一下ThreadLocal的作用。
private ThreadLocal<Boolean> mThreadLocal=new ThreadLocal<Boolean>;
[java] view plain copy
  1.    mThreadLocal.set(true);    
  2.    system.out.println(mThreadLocal.get());    
  3.         
  4. new Thread(thread1){  
  5.          
  6.          public void run(){  
  7.                
  8.        mThreadLocal.set(false);  
  9.        system.out.println(mThreadLocal.get());  
  10. }}.start();  
  11.   
  12.        new Thread(thread2){  
  13.   
  14.           public void run(){  
  15.                 
  16.               system.out.println(mThreadLocal.get());  
  17.        }  
  18.            }.start();  
 



打印的结果分别为 true false null为什么会有这样子的不同呢。虽然我们是在不同线程中访问的是同一个ThreadLocal对象,但是获取的值却不一样呢。因为每个线程都有自己的ThreadLocal,如果调取了get()方法,那么会从各自的线程中取出一个数组,然后在根据数组从当前的ThreadLocal中找出对应的value值。所以这就是为什么ThreadLocal可以在不同线程中维护一套数据副本并且彼此互不干扰。
1、MessageQueue:
是一种 数据 结构,见名知义,就是一个消息队列,存放消息的地方。每一个线程最多只可以拥有一个MessageQueue数据结构.创建一个线程的时候,并不会自动 创建其MessageQueue。通常使用一个Looper对象对该线程的MessageQueue进行管理。主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个Message Queue。其他非主线程,不会自动创建Looper要需要的时候,通过调用prepare函数来实现。2、Message:消息对象,Message Queue中的存放的对象。一个Message Queue中包含多个Message。 Message实例对象的取得,通常使用Message类里的静态方法obtain(),该方法有多个重载版本可供选择;它的创建并不一定是直接创建一个新的实例,而是先从Message Pool(消息池)中看有没有可用的Message实例,存在则直接取出返回这个实例。如果MessagePool中没有可用的Message实例,则才用给定的参数创建一个Message对象。调用removeMessages()时,将Message从MessageQueue中删除,同时放入到Message Pool中。除了上面这种方式,也可以通过Handler对象的obtainMessage()获取 一个Message实例。

Looper的的工作原理:
如何控制让某个线程成为目标线程呢?这就引出了Looper的概念。Android系统中实现了消息循环机制,Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。Android系统中的通过Looper帮助线程维护着一个消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象。前面提到每个线程都可以有自己的消息队列和消息循环,然而我们自己创建的线程默认是没有消息队列和消息循环的(及Looper),要想让一个线程具有消息处理机制我们应该在线程中先调用Looper.prepare()来创建一个Looper对象,然后调用Looper.loop()进入消息循环。如上面的源码所示。当我们用Handler的构造方法创建Handler对象时,指定handler对象与哪个具有消息处理机制的线程(具有Looper的线程)相关联,这个线程就成了目标线程,可以接受消息和计划任务了。
1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:6180次
    • 积分:93
    • 等级:
    • 排名:千里之外
    • 原创:4篇
    • 转载:1篇
    • 译文:0篇
    • 评论:1条