zoukankan      html  css  js  c++  java
  • Android的消息机制

    一、简介

    ①、我们不能在子线程中去访问UI空控件,这是时候只能通过Handler将更新UI的操作放到主线程中去执行

    ②、Handler的组成:messageQueue和Looper的支持

    ③、MessageQueue:作用:存储了一组消息,以队列的形式对外提供插入和删除的工作。实际上是运用单链表的数据结构来存储消息列表的。

    ④、Looper 作用:由于MesageQueue无法处理消息,Looper填补了这一个功能,Looper会无限循环的形式去查找是否有新消息,如果有就处理消息,没有的话就一直等待

    ⑤、Looper的特殊概念ThreadLocal:并不是线程,但是可以在线程中存储数据。

    举例:当Handler创建的时候默认会采用当前线程的Looper来构造其循环系统。那么如何获取当前线程的Looper,或者指定线程的Looper呢?

    这就要使用到ThreadLocal,ThreadLocal能够在不同线程中互不干扰地存储提取数据。所以通过ThreadLocal能够轻松获取每个线程的Looper。

    注意:线程是默认没有Looper的,需要使用Handler就必须先为线程创建Handler

    class LooperThread extends Thread
    
    {
          public Handler mHandler;
    
          public void run() 
    
          {
    
                 Looper.prepare();
    
                 mHandler = new Handler() 
    
                 {
    
                          public void handleMessage(Message msg) 
    
                         {
    
                                 // process incoming messages here
    
                         }
    
                 };
                Looper.loop();
          }
    }
    线程中创建Looper

    为什么主线程就不需要呢?

    因为在ActivityThread被创建是就已经初始化了Looper

    二、消息机制概述

    Android的消息机制:主要指Handler的运行机制和Handler所附带了MessageQueue和Looper的工作过程。

    Handler的主要作用:讲一个任务切换到某个指定的线程中去执行,

    问:Android为什么要有这个功能呢?

    Android规定访问UI只能在主线程中执行:

    在ViewRootImpl中对UI验证做了判断(P373 ①)。

    如果没有Handler,我们在子线程中进行一些数据处理,之后要根据数据修改UI,如果没有Handler我们就无法将访问UI的工作切换到主线程中去。

    问:为什么规定UI只能在主线程中执行?

    因为如果多线程中并发访问可能会导致UI控件处于不可预期状态。

    Handler的运行机制

    1.当Handler创建完毕,说明其内部的Looper和MessageQueue可以和Handler协同工作了

    2.通过Handler.post()方法将一个Runnable投递到Handler内部的Looper中去处理,或者使用send()方法。因为post()方法最终也是通过send方法来完成的。

    3.send()方法的工作流程:当Handler的send()方法被调用时,它会调用MessageQueue的enqueueMessage()方法,将消息放入消息队列中,然后Looper发现有新消息来到的时候就会处理这条消息,最终消息中的Runnable()或者Handler的handler的handleMessage()方法就会被调用。

    调用图:

    三、Android的消息机制分析

    ThreadLocal的工作原理

    使用情景:当某些数据是以线程为作用域并且不同线程具有不同数据副本的时候。

    举例:

    private static final String TAG = "MainActivity";
    private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();
    //在mainThread中放入true类型的数据
    mBooleanThreadLocal.set(true);
    Log.d(TAG,"MainActivity"+mBooleanThreadLocal.get());
    
    new Thread("Thread#1"){
      public void run(){
        mBooleanThreadLocal.set(false);
        Log.d(TAG,"Thread#1"+mBooleanThreadLocal(.get());  
      }
    }
    
    new Thread("Thread#2"){
      publci void run(){
          Log.d(TAG,"Thread#1"+mBooleanThreadLocal(.get());  
      }
    }
    MainActivity

    返回结果:

    MainActivity:true      Thread#1:false       Thread#2:null

    说明:虽然不同线程访问的是同一个ThreadLocal但是根据线程不同,返回的数据是根据在对应线程中设置的数据。(就好像有副本一样)

    工作原理:

    1.查看ThreadLocal的set()方法(P377 ①)

    ①、首先获取当前线程

    ②、根据当前线程调用value()方法返回当前线程的value对象(其中存储了了该Thread的数据)。

    Value类:Thread内部专门存储ThreadLocal的数据  (表示当前线程中存储了ThreadLocal的数据,而不是在ThreadLocal中存储了线程的数据)

    ③、将数据放入value.table这个数据结构中。

    table数据结构:数组private Object[] table,ThreadLocal的值就存储在其中。

    2.查看ThreadLocal的get()方法(P379 ①)

    同样是取出当前线程的value.table()然后,根据索引获取数据

    消息队列的工作原理(MessageQueue)

    ①、MessageQueue的操作

    插入(enqueueMessage())和读取(next())(读取操作会伴随着删除操作)

    ②、enqueueMessage()的操作:简单的单链表的插入操作  

    next():寻找未被锁的Message,并返回给调用者,发现next()是一个无线循环的方法,如果存在Message则return跳出循环,不存在Message则进入阻塞

    Looper的工作原理

    作用:不同的调用MessageQueue的next()方法查看是否有新的信消息,如果有新的消息就会立即处理,如果没有就一直阻塞在那。

    构造方法(P383 ①)

    1.保存当前Thread对象。2.创建一个MessageQueue

    线程中创建Looper:(P383 ②)

    小提示:Looper提供了getMainLooper()方法,通过它可以在任何地方获取到主线程的Looper。

    Looper的退出:

    quit():直接退出Looper

    quitSafely:是在消息队列中设定一个标记,当消息队列中的所有消息处理完毕之后才安全退出。

    退出后,Handler的send()会返回false。

    :如果手动为子线程创建Looper,那么在所有的事情完成后,应该调用quit()方法终止消息循环,否则子线程会一直处于等待状态。如果退出Looper这个线程就会立刻终止。

    分析Looper的loop()(P384 ①)

    1、loop方法是一个死循环

    2、再循环内通过调用MessageQueue.next();方法获取Message,如果next()中没有方法的时候,loop()方法会随next()方法一同阻塞。当next()方法返回null的时候,loop就会自动终止。

    3、所以说调用Looper.quit(),就是调用MessageQueue.quit()或quitSafely()。

    4、当获得msg之后,利用msg.target获取发送该msg的Handler,然后调用该handler的dispatchMessage(msg)方法。

    Handler的工作原理

    作用:发送消息和接收消息

    主要方法:post一系列方法和send一系列方法,post一系列方法是通过send()一系列方法实现的;

    分析send系列方法(P385 ①)

    内部都是通过向MessageQueue添加一条msg

    分析处理消息方法

    1、首先检查msg对象的callback是否为null,callback就是Runnable对象(Handler.post()传递的Runnable对象)。不为null就交给handleCallback(msg)处理

    2、其次检查mCallback是否为null,不为null,则调用mCallback的handleMessage()。

    mCallback是一个接口,可以通过Callback创建Handler对象:Handler mHandler = new Handler(callback);

    这样就可以在不用派生子类的情况下创建Handler。

    3、最后调用Handler的handleMessage来处理消息

    流程图:

     Handler特殊的构造方法(P388 ①)

     通过特定的Looper构造Handler

    public Handler(Looper looper){

      this(looper,null,false);

    }

    主线程的消息循环(P389 ①)

     主线程的main()方法入口,通过Looper.parepareMainLooper()创建主线程的Looper和MessageQueue,通过Loop.loop()消息循环。之后通过ActivityThread.H这个Handler与消息队列交互。

    AcitivtyThread通过ApplicationThread和AMS进行进程通信,AMS完成ActivityThread请求后会回调ApplicationThread的Binder方法,然后向Activity.H发送消息,将Binder线程池环境切换到主线程环境。

  • 相关阅读:
    解决span中的内容不换行
    javascript中apply、call和bind的区别
    vuex及其属性应用
    55.动态加载Html
    58.圆角图片
    57.动态添加子View(Java/XML两种方式)
    56.Java与js交互
    59.仿微信的图片浏览器
    64.判断当前线程是否是主线程
    61.自定义Indicator
  • 原文地址:https://www.cnblogs.com/rookiechen/p/5468376.html
Copyright © 2011-2022 走看看