美文网首页
Handler源码讲解+手写机制

Handler源码讲解+手写机制

作者: flynnny | 来源:发表于2021-02-27 00:54 被阅读0次

    摘自BAT面试宝典视频

    问题引入:点击后更新TextView
    重点:
    1 、不能在子线程更新UI
    2、OOM:HAndler使用不当可能引起内存泄漏
    3、Message的优化:要用Handler。obtainMessage()而不是new,会消耗内存。
    4、在子线程创建Handler,要准备Looper:Looper.prepare()。
    5、空指针异常:Handler消息处理完了单页面销毁了,就会抛出异常。

    Handler整体架构 (4个关键类基本关系幕后类Thread)

    handler能做什么?
    1、处理延时任务:推送将来的Message或Runnable到消息队列;
    2、线程间通信:在子线程把需要在另一个线程执行的操作加入到消息队列;

    1.png

    源码分析(线程如何跨越、生产者消费者设计模式、ThreadLocal原理)

    从“handler.sendMessage()”发送消息出发
    sendMessage(msg)
    ---->sendMessageDelay(msg,0)
    ---->sendMessageAtTime(msg,SysMillis()+delay)
    ---->enqueueMessage(queue,msg,uptimeMillis);
    ---->queue.enqueueMessage(msg,uptimeMillis);
    enqueueMessage()往MessageQueue发送消息

    其他流程图

    2.png

    所有的send和post都是MessageQueue.enqueueMessage()!

    MessageQueue.java

    3.png 4.png

    用一个for循环不断地.next找消息

    是一个链表队列,新消息来时比较时间后插入相应位置

    5.png

    消息的处理

    Handler mHandler = new Handler(){
      public void handlerMessage(Message msg){
        super.handlerMessage(msg);
      }
    }
    

    如何handlerMessage?从MessageQueue里找next()方法

    6.png

    又有一个for循环

    7.png

    messageQueue.next()返还、销毁队列里的消息
    .next()是谁调用的?--->Looper.java

    8.png 9.png

    for循环一直在运行
    Loop在被谁调用?---》ActivityThread.java

    10.png

    *如上图主线程里不需要准备Loop

    ActivityThread让Loop跑起来的。

    Handler框架手写

    定义4个工具类

    11.png

    Handler

    public class Handler{
     final Looper mLooper;
     final MessageQueue mQueue;
    
     public Handler(){
        mLooper = Looper.myLooper();//1
        mQueue = mLooper.mQueue;//1为什么不是new
      }
    //发送消息
     public void sendMessage(Message msg){
      enqueueMessage(msg);
     }
     public void enqueueMessage(Message msg){
        msg.target = this;//人跟着箱子去了传送带
       mQueue.enqueueMessage(msg);
     }
    //分发消息
      public void dispachMessage(Message msg){
        handlerMessage(msg);
    }
    //处理消息
      public void handlerMessage(Message 
     msg){
        
      }
    }
    
    public class Looper{
      public MessageQueue mQueue;
      public static ThreadLoca<Looper> sThreadLocal= new ThreadLoca<>();
    
      private Looper(){
        mQueue = new MessageQueue();
      }
     
      public static prepare(){
        //看下面ThreadLocal解释
        if(sThreadLocal.get()!=null){
          throw new RuntimeException("Only one Looper may ...")
        }
        sThreadLocal.set(new Looper(.));
      }
    
      public static Looper myLooper(){
        return ThreadLocal.get();
     }
    //启动looper 让MQ run
      public void loop(){
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for(;;){
          Message msg = queue .next();
          if(msg!=null){
            msg.target.dispachMessage(msg);
          }
        }
      }
    
    }
    
    public class  MessageQueue{
    
    BlockingQueue <Message> queue ;//实现仓库的阻塞队列
    private static final int MAXCOUNT = 10;//仓库大小
    public MessageQueue(){
      queue= new ArrayBlockingQueue<>(MAXCOUNT );
    }
    //往队列里添加消息
     public void enqueueMessage(Message msg){
        try{
          queue.put(msg)
        }catch(){
          
        }
     }
    //往队列里取消息
     public Message next(){
        Message msg = null;
        try{
          msg =queue.take();
        }catch(InterruptException e){
          
        }
       return msg;
     }
    }
    
    public class  Message{
      Handler target;
      Object obj;
    
      public Message(){
      }
      public String toString(){
        return obj.toString();
      }
    }
    

    测试代码

    public calss HandlerMain{
      public static void main(String [] args){
        Looper.prepare();
        Handler handler = new Handler(){
          public void handleMessage(Message msg){
            System.out.println("Thread ID "+Thread.currentThread().getName()+" received msg: "+msg.toSting())
          };
        };
      
      new Thread(new Runnable(){
         public void run(){
            while(true){
              Message msg = new Message();
              msg.obj = UUID.randomUUID().toString();
              System.out.println(Thread.currentThread().);
              handler.sendMessage(msg);
              try{
                Thread.sleep(500);
              }catch(InterruptedException e){
                e.printStackTrace();
              }
            }
          } 
       })
       Looper.loop();
      }
    }
    

    一个线程只有一个Looper!
    *ThreadLocal线程隔离工具类
    类似HashMap<Key,Value>
    Key---线程ID
    Value---Looper对象

    通过ThreadLocal确保Looper唯一,通过Looper确保MessageQueue唯一

    子线程和主线程的通信(MQ由子线程写入在主线程读取)是借助内存实现的

    12.png 13.png

    Message.obtion()运用了享原设计模式 ,复用了Message。

    子线程中真的不能更新UI吗?
    可以
    1、

    //在oncreate里
    new Thread(newRunnable(){
      public void run (){
        button.setText("可以正常更新不报异常");
      }
    }).run();
    

    原理在ActivityManagerService.java里
    在onCreate没到onResume时,是不会检测是在子线程还是在主线程的

    ViewRootImpl的创建在onResume方法回调之后,而我们是在onCreate方法中创建了子线程并访问UI,在那个时刻,ViewRootImpl是没有创建的,无法检测当前线程是否是UI线程,所以程序没有崩溃一样能跑起来,而之后修改了程序,让线程休眠了200毫秒后,程序就崩了。很明显200毫秒后ViewRootImpl已经创建了,可以执行checkThread方法检查当前线程。

    2、Surface可以在子线程中更新UI
    SurfaceView与View的刷新方法都是一样的,通过lockCanvas和unlockCanvasAndPost方法来进行画的,但SurfaceView能在UI线程中刷新,也能在其它线程中刷新,而View只能在UI线程中刷新,View的刷新有一个checkThread(在ViewRootImp.java中)的判断,如果不是在UI线程中就会抛异常, 这是google人为这样设计的,不让其它线程刷新View,SurfaceView就不会进行判断,这样它就可以在其它线程中进行刷新。

    相关文章

      网友评论

          本文标题:Handler源码讲解+手写机制

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