Handler源码讲解+手写机制

摘自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就不会进行判断,这样它就可以在其它线程中进行刷新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容