handler机制学习总结

Posted AC_Jobim

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了handler机制学习总结相关的知识,希望对你有一定的参考价值。

一、Handler介绍

  1. Handler的作用
    在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理

  2. Looper、MessageQueue、Message、Handler的关系

    • Looper
      每一个线程只有一个Looper,每个线程在初始化Looper之后,然后Looper会维护好该线程的消息队列,用来存放Handler发送的Message,并处理消息队列出队的Message。它的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。

      那么就会有人问,为什么我们使用Handler的时候从来都不需要创建Looper呢?这是因为在主线程中,ActivityThread默认会把Looper初始化好,prepare以后,当前线程就会变成一个Looper线程。

    • MessageQueue
      MessageQueue是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。

    • Message
      消息对象,就是MessageQueue里面存放的对象,一个MessageQueue可以包括多个Message。当我们需要发送一个Message时,一般使用Message.obtain()来获取Message实例,因为在Message类里面定义了一个消息池,当消息池里存在未使用的消息时,便返回,如果没有未使用的消息,则通过new的方式创建返回,所以使用Message.obtain()的方式来获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。

    总结:

    当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!

二、Handler的使用

使用Handler发送消息主要有两种,Handler.sendMessage()Handler.post()

Handler的相关方法:

  • void handleMessage(Message msg):处理消息的方法,通常是用于被重写!
  • sendEmptyMessage(int what):发送空消息
  • sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信息
  • sendMessage(Message msg):立即发送信息
  • sendMessageDelayed(Message msg):指定延时多少毫秒后发送信息
  • final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息 如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息
  • removeMessages(int what):移除还未处理的消息,即延迟消息

使用Handler的一般步骤:(以sendMessage()为例)

  1. 创建Handler成员变量对象,并重写其handlerMessage()方法
  2. 在分/主线程创建Message对象
  3. 使用handler对象发送Message
  4. 在handleMessage()中处理消息
//1. 创建Handler成员变量对象, 并重写其handleMessage()
private Handler handler = new Handler() {
   @Override
   public void handleMessage(@NonNull Message msg) {
      if(msg.what == 1){
         //4. 在handleMessage()中处理消息
         String result = (String) msg.obj;
         et_handler1_result.setText(result);
         pb_handler1_loading.setVisibility(View.INVISIBLE);
      }
   }
};
public void getSubmit2(View v) {
   //显示提示视图(ProgressDialog/ProgressBar)
   pb_handler1_loading.setVisibility(View.VISIBLE);
   //2.分线程, 联网请求, 并得到响应数据
   new Thread(){
      @Override
      public void run() {
         String path = "https://www.bing.com/";
         try {
            String result = requestToString(path);
            //3.主/分 线程,使用handler对象发送Message
            //在 分/主 线程创建Message对象
            Message message = Message.obtain();
            message.what = 1;//标识
            message.obj = result;
            //使用handler对象发送Message
            handler.sendMessage(message);

         } catch (Exception e) {
            e.printStackTrace();
         }
      }
   }.start();
}
  • 使用post()方法发送消息也类似,只是不在handleMessage()中进行消息的处理,而是处理时通过调用post()方法传入Runnable对象的run()方法

    public void getSubmit3(View view) {
       //主线程, 显示提示视图(ProgressDialog/ProgressBar)
       pb_handler1_loading.setVisibility(View.VISIBLE);
       //2.分线程, 联网请求, 并得到响应数据
       Handler handler = new Handler();
       new Thread(){
          @Override
          public void run() {
             String path = "https://www.bing.com/";
             try {
                String result = requestToString(path);
                //3.在工作线程中 发送消息到消息队列中
                handler.post(new Runnable() {
                   @Override
                   public void run() {
                      //4.执行需要的UI操作
                      et_handler1_result.setText(result);
                      pb_handler1_loading.setVisibility(View.INVISIBLE);
                   }
                });
             } catch (Exception e) {
                e.printStackTrace();
             }
          }
       }.start();
    }
    

三、源码分析

原理图:

在这里插入图片描述
在这里插入图片描述

3.1 创建循环器对象(Looper) & 消息队列对象(MessageQueue)

  • 创建Looper对象主要通过方法:Looper.prepareMainLooper()、Looper.prepare();
  • 创建消息队列对象(MessageQueue)方法:创建Looper对象时则会自动创建。
private static void prepare(boolean quitAllowed) {
    // 1. 判断sThreadLocal是否为null,否则抛出异常
    //即 Looper.prepare()方法不能被调用两次 = 1个线程中只能对应1个Looper实例
    // 注:sThreadLocal = 1个ThreadLocal对象,用于存储线程的变量
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 2. 若为初次Looper.prepare(),则创建Looper对象 & 存放在ThreadLocal变量中
    // 注:Looper对象是存放在Thread线程里的
    sThreadLocal.set(new Looper(quitAllowed));
}

//Looper的构造方法
private Looper(boolean quitAllowed) {
    // 1. 创建1个消息队列对象(MessageQueue)
    // 即 当创建1个Looper实例时,会自动创建一个与之配对的消息队列对象(MessageQueue)
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

android应用进程启动时,会默认创建1个主线程(ActivityThread,也叫UI线程)创建时,会自动调用ActivityThread的1个静态的main()方法 = 应用程序的入口main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象

/**
 * Looper.prepareMainLooper()
 * 作用:为 主线程(UI线程) 创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue)
 * 注:该方法在主线程(UI线程)创建时自动调用,即 主线程的Looper对象自动生成,不需手动生成
 */
public static void prepareMainLooper() {
  	//调用prepare()方法
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
}

/**
 * 源码分析:main()
 **/
public static void main(String[] args) {
        ... // 仅贴出关键代码

    Looper.prepareMainLooper();
    // 1. 为主线程创建1个Looper对象,同时生成1个消息队列对象(MessageQueue)
    // 方法逻辑类似Looper.prepare()
    // 注:prepare():为子线程中创建1个Looper对象


    ActivityThread thread = new ActivityThread();
    // 2. 创建主线程

    Looper.loop();
    // 3. 自动开启 消息循环 ->>下面将详细分析

}

总结:

  • 创建主线程时,会自动调用ActivityThread的1个静态的main();而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象
  1. 即 主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象则需手动通过Looper.prepare()创建
  2. 在子线程若不手动创建Looper对象 则无法生成Handler对象
  • 根据Handler的作用(在主线程更新UI),Handler实例的创建场景 主要在主线程
  • 生成Looper & MessageQueue对象后,则会自动进入消息循环:Looper.loop(),即又是另外一个隐式操作。

3.2 创建消息对象

/**
 * 源码分析:Message.obtain()
 * 作用:创建消息对象
 * 注:创建Message对象可用关键字new 或 Message.obtain()
 */
public static Message obtain() {

    // Message内部维护了1个Message池,用于Message消息对象的复用
    // 使用obtain()则是直接从池内获取
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
        // 建议:使用obtain()”创建“消息对象,避免每次都使用new重新分配内存
    }
    // 若池内无消息对象可复用,则还是用关键字new创建
    return new Message();
}

3.3 发送消息

发送消息有sendpost的方式:

// 发送消息
Handler handelr = new Handler();
// 发送方式一
handler.sendMessage(msg); // 或者handler.sendEmptyMessage(what);
// 发送方式二
handler.postXXX(runnable);

handler.sendXXX()handler.postXXX()最终都会调用到sendMessageDelayed()方法。

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
    	delayMillis = 0;
    }
    // 注意sendMessageAtTime方法的第二个参数,当前时间加上延时时间
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

然后调用了sendMessageAtTime方法

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
  	// 这里拿到的MessageQueue其实就是创建时的MessageQueue,默认情况是当前线程的Looper对象的MessageQueue
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
  	//将消息添加到消息队列
    return enqueueMessage(queue, msg, uptimeMillis); // uptimeMillis = SystemClock.uptimeMillis() + delayMillis
}

又调用了enqueueMessage()方法

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                               long uptimeMillis) {
    //保存发送消息的handler对象
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //调用消息队列保存消息对象
    return queue.enqueueMessage(msg, uptimeMillis);
}

然后又调用了MessageQueueenqueueMessage()方法:调用消息队列保存消息对象

   boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // 一个Message,只能发送一次
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                msg.recycle();
                return false;
            }
            // 标记Message已经使用了
            msg.markInUse();
            msg.when = when;
            // 得到当前消息队列的头部
            Message p = mMessages;
            boolean needWake;
            // 我们这里when为0,表示立即处理的消息
            if (p == null || when == 0 || when < p.when) {
                // 把消息插入到消息队列的头部
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 根据需要把消息插入到消息队列的合适位置,通常是调用xxxDelay方法,延时发送消息
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 把消息插入到合适位置
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 如果队列阻塞了,则唤醒
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

        首先,判断了Message是否已经使用过了,如果使用过,则直接抛出异常,这是可以理解的,如果MessageQueue中已经存在一个Message,但是还没有得到处理,这时候如果再发送一次该Message,可能会导致处理前一个Message时,出现问题。
        然后,会判断when,它是表示延迟的时间,我们这里没有延时,所以为0,满足if条件。把消息插入到消息队列的头部。如果when不为0,则需要把消息加入到消息队列的合适位置。
        最后会去判断当前线程是否已经阻塞了,如果阻塞了,则需要调用本地方法去唤醒它。

再看一下post系列方法:

public final boolean post(Runnable r) {
  return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
  // 构造一个Message,并让其callback执行传来的Runnable
  Message m = Message.obtain();
  m.callback = r;
  return m;
}

可以看到:post方法只是先调用了getPostMessage方法,将Runnable封装到一个Message,然后就调用了sendMessageDelayed

总结:发送消息就是把Message加入到Handler中的MessageQueue中去

3.4 取出处理消息

此处主要分析的是Looper类中的loop()方法

/** 
  * 源码分析: Looper.loop()
  * 作用:消息循环,即从消息队列中获取消息、分发消息到Handler
  * 特别注意:
  *       a. 主线程的消息循环不允许退出,即无限循环
  *       b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit()
  */
  public static void loop() {
        
        ...// 仅贴出关键代码

        // 1. 获取当前Looper的消息队列
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            // myLooper()作用:返回sThreadLocal存储的Looper实例;若me为null 则抛出异常
            // 即loop()执行前必须执行prepare(),从而创建1个Looper实例
            
            final MessageQueue queue = me.mQueue;
            // 获取Looper实例中的消息队列对象(MessageQueue)

        // 2. 消息循环(通过for循环)
            for (;;) {
            
            // 2.1 从消息队列中取出消息
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
            // next():取出消息队列里的消息
            // 若取出的消息为空,则线程阻塞

            // 2.2 派发消息到对应的Handler
            msg.target.dispatchMessage(msg);
            // 把消息Message派发给消息对象msg的target属性
            // target属性实际是1个handler对象

        // 3. 释放消息占据的资源
        msg.recycle();
        }
}

调用MessageQueue的next()方法,从消息队列中获取消息

/**
 * 定义:属于消息队列类(MessageQueue)中的方法
 * 作用:出队消息,即从 消息队列中 移出该消息
 */
Message next() {

    ...// 仅贴出关键代码

    // 该参数用于确定消息队列中是否还有消息
    // 从而决定消息队列应处于出队消息状态 or 等待状态
    int nextPollTimeoutMillis = 0;

    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        // nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态 
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {

            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            // 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 取出了消息
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {

                // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1
                // 下次循环时,消息队列则处于等待状态
                nextPollTimeoutMillis = -1;
            }

        ......
        }
       .....
    }
}// 回到分析原处

调用handler的dispatchMessage()方法,派发消息到对应的Handler实例

/**
 * 定义:属于处理者类(Handler)中的方法
 * 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
 */
public void dispatchMessage(Message msg) {

    // 1. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息
    // 则执行handleCallback(msg),即回调Runnable对象里复写的run()
    // 上述结论会在讲解使用“post(Runnable r&#x

以上是关于handler机制学习总结的主要内容,如果未能解决你的问题,请参考以下文章

Android学习总结 ———— Handler 的使用

如何高效学习Handler?

安卓 Handler 机制学习

安卓 Handler 机制学习

金蝶handler中 collection 代码片段理解

4/17学习总结:handler