java中手写handler_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、线程间通信:在子线程把需要在另一个线程执行的操作加入到消息队列;

4d8b3eeecfe8

1.png

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

从“handler.sendMessage()”发送消息出发

sendMessage(msg)

---->sendMessageDelay(msg,0)

---->sendMessageAtTime(msg,SysMillis()+delay)

---->enqueueMessage(queue,msg,uptimeMillis);

---->queue.enqueueMessage(msg,uptimeMillis);

enqueueMessage()往MessageQueue发送消息

其他流程图

4d8b3eeecfe8

2.png

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

MessageQueue.java

4d8b3eeecfe8

3.png

4d8b3eeecfe8

4.png

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

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

4d8b3eeecfe8

5.png

消息的处理

Handler mHandler = new Handler(){

public void handlerMessage(Message msg){

super.handlerMessage(msg);

}

}

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

4d8b3eeecfe8

6.png

又有一个for循环

4d8b3eeecfe8

7.png

messageQueue.next()返还、销毁队列里的消息

.next()是谁调用的?--->Looper.java

4d8b3eeecfe8

8.png

4d8b3eeecfe8

9.png

for循环一直在运行

Loop在被谁调用?---》ActivityThread.java

4d8b3eeecfe8

10.png

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

ActivityThread让Loop跑起来的。

Handler框架手写

定义4个工具类

4d8b3eeecfe8

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 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 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---线程ID

Value---Looper对象

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

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

4d8b3eeecfe8

12.png

4d8b3eeecfe8

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值