美文网首页
从面试问题中学习hanlder

从面试问题中学习hanlder

作者: 王多鱼2 | 来源:发表于2019-08-17 01:52 被阅读0次

如何更了解hanlder从这些面试问题中更容易掌握;
handler作用,更新UI,延时任务;

一,说下 handler 机制?

答:它主要依靠一下四个类的:
- Handler:用来发送消息和接收消息;
- Message:是容纳任意数据的容器;
- MessageQueue: 无界的, 单向链表的消息体对象 ;它按照时序将消息插入队列,最小的时间
戳(开机时间多久执行时间)将会被首先处理;
- Looper:轮询器,它会调用Looper.loop();无限循环取消息;
流程

  1. 消息如何加入队列的,handler.sendMessage(msg),最终由MessageQueue中的queue.enqueueMessage(msg)加入队列;
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  1. 主线程会Looper.prepareMainLooper().Looper.loop()执行死循环去获取消息;
//App的打开是从ActivityThread的Main方法开始的
 public static void main(String[] args) {
        ···
        //创建Loop对象
        Looper.prepareMainLooper();
        //Looper走你!
        Looper.loop()
        ···
    }
  for (;;) {
            Message msg = queue.next(); // might block
  }

3,消息获取,没有消息的时候根据nextPollTimeoutMillis时间进行休眠(这里可以理解为休眠),让出cpu;

  Message next() {
       ...代码省略..
        int nextPollTimeoutMillis = 0;
        for (;;) {
              ...代码省略..
      //  nextPollTimeoutMillis为休眠时间, 没有消息时为-1为无限时间;
        nativePollOnce(ptr, nextPollTimeoutMillis);
}

二,假设发送的消息是一个小时,几分钟后,我再次发送一个消息无延时消息,他都进入阻塞了岂不是发不出去了?

答:发送新消息时,在加入队列过程中, 会调用nativeWake()进行唤醒;从而从新计算休眠阻塞时间;

    boolean enqueueMessage(Message msg, long when) {
    
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

三,Looper 通过 MessageQueue 取消息,消息队列是先进先出模式,那我延迟发两个消息,第一个消息延迟2个小时,第二个消息延迟1个小时,那么第二个消息需要等3个小时才能取到吗?

答:MessageQueue 消息入队的方式会有一个when时间,它是时间戳+延时时间组成;然后会根据时间大小链式结构排序;取消息的时候,每次都是会取队头 Message时间处理;

时间延时多久是SystemClock.uptimeMillis() + delayMillis;

  • SystemClock.uptimeMillis()是指从开机到现在的毫秒数
     public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
       // 这里是发送消息最后的时间确定方法
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  • 队头的消息大于目标消息,排对头
  • 遍历messgeque找到插入的位置;
   if (p == null || when == 0 || when < p.when) {   
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

取消息逻辑

 Message next() {
      synchronized (this) {
                final long now = SystemClock.uptimeMillis();

                if (msg != null) {
                    if (now < msg.when) {  // 时间未到,计算执行nextPollTimeoutMillis 时间
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        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 {
                    // No more messages.
                    // 没有消息,无限阻塞
                    nextPollTimeoutMillis = -1;
                }
}

四,handler 是死循环的,为什么不会卡死?

nativePollOnce(ptr, nextPollTimeoutMillis);时,进行阻塞,可以理解为休眠,linux底层实现,让出CPU;

  • 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。

  • 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。

  • 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。

五,Handler的post和sendMessage的区别?

handler.post和handler.sendMessage本质上是没有区别的,都是发送一个消息到消息队列中,而且消息队列和handler都是依赖于同一个线程的。
为什么没有区别呢?看源码

 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

最终他们都是调的同一个方法;只是它把runnable转化成了一个Message;
最后消息分发的时候,判断是否存在callback然后直接调用run方法执行了;

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
   private static void handleCallback(Message message) {
        message.callback.run();
    }

六,在子线程中如何使用Looper 和 Handler?

  Handler mHandler;
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//Looper初始化
                //Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
                mHandler = new Handler(Looper.myLooper());
                Looper.loop();//死循环
                //注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
            }
        }).start();
    mHandler.getLooper().quit();//终止 Looper.looper() 死循环, 执行 quit后Handler机制将失效, 执行时如
    //果MessageQueue中还有Message未执行, 将不会执行未执行Message, 直接退出, 调用quit后将不能发消息给Handler

    mHandler.getLooper().quitSafely();//Android4.3, API Level 18 后加入的方法, 作用同 quit(), 但终止
    //Looper之前会先执行完消息队列中所有非延时任务, 调用quit后将不能发消息给Handler

七,Handler引起的内存泄漏以及解决办法?

在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用,静态内部类则不会持有外部类的引用。当 Activity finish 时,Handler可能并未执行完,从而引起 Activity 的内存泄漏;

  • 如果你需要在Handler内部调用外部Activity的方法,你可以让这个Handler持有这个Activity的弱引用,这样便不会出现内存泄漏的问题了
public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   * 弱引用的方式
   */
    private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;
    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
         //to Something
      }
    }
 }
  • 对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。
    //定义成static的,因为静态内部类不会持有外部类的引用
  private final MyHandler mHandler = new MyHandler(this);
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() {//to something}
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
    finish();
  }
}

这篇文章的这道题能更好的明白(里面有一小点的代码错误)标题

八, MessageQueue是什么时候创建的?

主线程创建的时候,调用prepareMainLooper,创建Looper时同时会创建 MessageQueue;

九,ThreadLocal在Handler机制中的作用?

保证Looper的唯一,在Looper准备的时候,它是以ThreadLocal保存的,ThreadLocal有是key,value保存的,key是指它所在的线程。

   private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

十,MessageQueue里面的数据结构是什么类型的,为什么不是Map或者其他什么类型的?

十一,说说handle用到的享元模式?

我们去获取Message对象时,是从消息池中取的,消息使用完成后回收到消息池中;

 public static Message obtain() {
    synchronized (sPoolSync) {
          /**请注意!我们可以看到Message中有一个next字段指向下一个Message,这里就明白了,Message消息池中
             没有使用Map这样的容器,而是使用的链表!
          */
          if (sPool != null) {
              Message m = sPool;
              sPool = m.next;
              m.next = null;
              m.flags = 0; // clear in-use flag
              sPoolSize--;
              return m;
          }
      }
      return new Message();
}

这遍文章讲的很详细了
https://blog.csdn.net/qq_33768280/article/details/81160502

相关文章

网友评论

      本文标题:从面试问题中学习hanlder

      本文链接:https://www.haomeiwen.com/subject/xrnqsctx.html