欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android必备知识点之消息传递机制Handler

程序员文章站 2022-03-08 14:08:51
...

1.最常见的使用场景

Android中常用Handler使用场景?

并不能在子线程中访问UI控件,否则会触发程序异常,这时候需要通过Handler将更新UI的操作切换到主线程进行

系统为什么不允许在子线程中访问UI呢?

UI控件不是线程安全的,多线程并发访问的时候可能会导致UI处于不可预期的状态,如果对UI控件的访问加线程锁,会降低UI线程的访问效率,另外就是逻辑变的复杂。

简要概括:

Handler运行需要底层MessageQueue 和 Looper支撑

MessageQueue:消息队列,内部存储一组消息,一对队列的形式对外提供插入和删除的工作。内部存储结构不是队列,而是单链表

Looper:消息循环,MessageQueue只是消息的存储单元,不能处理消息,Looper则无限循环查询消息,如果有新消息,则处理,否则一直等待。

ThreadLocal:并不是线程,它的作用是在每个线程中存储数据。

Handle创建的时候会才用当前线程的Looper来构造消息循环系统,Handler是怎么获得当前线程的呢?是因为使用了ThreadLocal,它可以在不同的线程中存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。

ThreadLocal详解

ThreadLocal是一个线程内部存储类,通过他可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程则无法获取到数据。

1.使用场景:

①以线程为作用域并且不同的线程具有不同的数据副本。
②复杂逻辑下的对象传递,例如监听器的传递。通过参数传递或者静态全局变量,都有局限性

2.使用

ThreadLocal<T> mtL = new ThreadLocal<T>();
//设置
mtl.set("..");
//获取
mtl.get();

3.源码解析

public void set(T value) {
    Thread currentThrea = Thread.currentThread();
    Values values = values(currentThread);
    if(values == null) {
        values = initializeValues(currentThrea);
    }
    values.put(this,value);
}

通过valuse方法获取当前线程中的ThreadLocal数据,没有则初始化,然后再讲ThreadLocal的值进行存储,数组形式的存储,这里数组排列顺序:所有的线程有一个排序,这个排序,存储数据是当前线程排序下角标加一。具体就不深入了~

Looper详解
new Thread(new Runnable() {
        @Override
        public void run() {
            //为当前线程创建looper
            Looper.prepare();
            Handler handler = new Handler();
            //开启消息循环
            Looper.loop();
        }
    }).start();

①Looper.quit()和Looper.quitSafely()的区别

quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有的消息处理完毕才安全的退出。

解析:

①Looper.prepare()

此方法会调用Looper(boolea quitAllowed)创建Looper,

private Looper(boolea quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

持有当前线程和MessageQueue;

②Looper.loop()

代码过于长就不粘贴了,大概解析一下

....
for(;;){
    Message msg = mQueue.next();
    if(msg == null) {
        return;
    }
    ...
    msg.target.dispatchMessage(msg);
    ...
}

loop方法是个死循环,唯一跳出的方式就是MessageQueue的next方法返回null,即当调用Looper.quit()和Looper.quitSafely(),next为空,消息队列退出。也就是说,如果Looper必须退出吗,否则一直循环。

处理消息:msg.target.dispatchMessage(msg);其中msg.target是发送这条消息的Handler的对象,也就是说,消息最终交给dispatchMessage方法处理

Handler的工作原理

handler.sendMessage(Message msg); –> sendMessageDelayed(Message msg,long 0); –> sendMessageAtTime(Message msg, long delaymillis) –> enqueueMessage(MessageQueue queue, Message msg, long delaymillis)

Handler发送消息是向消息队列中插入一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入处理消息阶段了,调用handleCallBack(msg)

抽象方法

 /**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
Handler和Handler.Callback的handleMessage区别:

如果直接用Handler的handleMessage黄色警报,换成静态的就好了,原因:MessageQueue中的消息队列会一直持有对handler的引用,而作为内部类的handler会一直持有外部类的引用,就会导致外部类不能被GC回收。当我们发延时很长的msg时就容易出现泄漏。所以此处应该设置为static,然后Handler就会跟随类而不是跟随类的对象加载,也就不再持有外部类的对象引用。

handler.post(new Runnable(){
    @Override
    public void run() {
         mTestTV.setText("This is post");//更新UI
    }});

和handler.sendMessage(msg);作用相当