5.1源码解析Handler

为什么要使用Handler

将工作线程需操作UI的消息 传递 到主线程,使得主线程可根据工作线程的需求 更新UI从而避免线程操作不安全的问题

首先正常Handler写法

1,创建Handler对象

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case:
        breank;
}

}

2,创建消息对象

Message msg = mHandler.obtainMessage(SYNC_CALL_LOG);
mHandler.sendMessage(msg);

第一步源码解析,Handler是如何创建的

/**
 * Default constructor associates this handler with the {@link Looper} for the
 * current thread.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 */

/** * 源码分析:Handler的构造方法

* 作用:初始化Handler对象 & 绑定线程

* 注:

* a. Handler需绑定 线程才能使用;绑定后,Handler的消息处理会在绑定的线程中执行

* b. 绑定方式 = 先指定Looper对象,从而绑定了 Looper对象所绑定的线程(因为Looper对象本已绑定了对应线程)

* c. 即:指定了Handler对象的 Looper对象 = 绑定到了Looper对象所在的线程 */

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {  
// 如果打开了FIND_POTENTIAL_LEAKS开关 
// 会判断是否是非静态匿名内部类、非静态成员内部类或非静态局部内部类 
// 如果是,可能发生内存泄漏,发出警告
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    //指定Looper对象
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
 // Looper.myLooper()作用:获取当前线程的Looper对象;若线程无Looper对象则抛出异常
                // 即 :若线程中无创建Looper对象,则也无法创建Handler对象
                // 故 若需在子线程中创建Handler对象,则需先创建Looper对象
                // 注:可通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象

  mQueue = mLooper.mQueue; //绑定消息队列(MessageQueue)
    mCallback = callback; 
    mAsynchronous = async;
}

由上所述:Handler的创建需要创建Looper对象,接下来研究Looper是如何创建的,消息队列(MessageQueue)又是怎么创建的

 /** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
//该方法表示去创建一个Looper
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
 //判断ThreadLocal不为null调用该方法的话就会抛出异常,通常面试题会问一个线程有几个Looper,由此可知,一个线程只有一个Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed); //创建了一个消息队列
    mThread = Thread.currentThread();   //并且使用的线程是当前的线程
}

首先我再主线程中创建Handler之前执行Looper.prepare()发现报错了

java.lang.RuntimeException: Only one Looper may be created per thread

说明主线程创建Handler就已经创建了Looper 那么主线程的loop是如何创建的

发现在创建主线程的时候,之前会调用Looper.prepareMainLooper();

public static void prepareMainLooper() {
    prepare(false);  //在这就创建了一个Looper
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

由此可知,主线程创建的时候就已经创建了Looper 无需再创建Looper,子线程创建Handler是需要重新去创建Looper

(具体创建子线程ThreadLocal.get()为何反馈null后续会进一步了解ThreadLocal源码解析)

第二步创建完Looper之后,当前Looper是怎么运作的

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    //获取Looper的消息队列,通过ThreadLocal存储的Looper实例
    final Looper me = myLooper();
    if (me == null) {
    //当前的Looper为null抛出该异常,所以在执行loop()方法之前,一定要先执行looper.prepare()
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
   //获取looper消息队列的消息对象
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,确保这个线程的标识是本地进程的标识,
    // and keep track of what that identity token actually is. 并记录下身份标识到底是什么
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
     
    //死循环
    for (;;) {
        Message msg = queue.next(); // might block 取出消息队列的消息,可能会被阻塞
        if (msg == null) {
        //如果当前的消息是空的话就会被阻塞
            // No message indicates that the message queue is quitting.
            return;
        }
        //派发消息到对应的Handler
        msg.target.dispatchMessage(msg);

        // Make sure that during the course of dispatching the  在调度过程中,保证调度过程的顺利进行
        // identity of the thread wasn't corrupted. 线程的标识没有损坏
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }
        //释放消息所占用的资源
        msg.recycleUnchecked();
    }
}
/** 
  * 分析1:queue.next()
  * 定义:属于消息队列类(MessageQueue)中的方法
  * 作用:出队消息,即从 消息队列中 移出该消息
  */
  Message next() {

        ...// 仅贴出关键代码

        // 该参数用于确定消息队列中是否还有消息
        // 从而决定消息队列应处于出队消息状态 or 等待状态
        int nextPollTimeoutMillis = 0;

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

        // nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态 
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
     
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            // 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 取出了消息
                    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 {

                // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1
                // 下次循环时,消息队列则处于等待状态
                nextPollTimeoutMillis = -1;
            }

            ......
        }
           .....
       }
}// 回到分析原处

/** 
  * 分析2:dispatchMessage(msg)
  * 定义:属于处理者类(Handler)中的方法
  * 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
  */
  public void dispatchMessage(Message msg) {

    // 1. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息
    // 则执行handleCallback(msg),即回调Runnable对象里复写的run()
    // 上述结论会在讲解使用“post(Runnable r)”方式时讲解
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }

            // 2. 若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息(即此处需讨论的)
            // 则执行handleMessage(msg),即回调复写的handleMessage(msg) ->> 分析3
            handleMessage(msg);

        }
    }

  /** 
   * 分析3:handleMessage(msg)
   * 注:该方法 = 空方法,在创建Handler实例时复写 = 自定义消息处理方式
   **/
   public void handleMessage(Message msg) {  
          ... // 创建Handler实例时复写
   } 

(借鉴:https://www.jianshu.com/p/b4d745c7ff7a

由上可知,Looper做了一个死循环处理,如果当前消息队列有消息的话,就会根据创建消息的时间顺序去发送消息,就会发送给对应的Handler去处理,如果消息没有的话,就会处于等待状态。

(到这我有两个问题,居然已经一直等待,会不会引起ANR?如果不会又是怎么唤醒的呢?)

1.只有主线程才会产生ANR,主线程就是UI线程;

2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;

3.上述事件响应超时,不同的context规定的上限时间不同

a.主线程对输入事件5秒内没有处理完毕

b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕

c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。
(借鉴:https://blog.csdn.net/github_37130188/article/details/89609419)

其实是通过epoll机制来唤醒的:连接网址(https://www.cnblogs.com/broadview/archive/2013/01/29/2881284.html

当触发点击事件的时候也会唤醒Looper,将looper内的资源释放掉,所以不会引起ANR
 

第三步,上面已经初始化完成,开始创建消息对象

public static Message obtain() {
    //内部创建了一个Message池,用来复用Message消息
    synchronized (sPoolSync) {
        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();
}

由上可知,Message消息是个链表,如果每次New消息的话都需要重新分配内存,还是这种方式最好,复用消息

第四步,从工作线程中发送消息,到主线程中

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    //发现发送消息的顺序是根据当前时间来发送的
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    //获取对应的消息队列的对象
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    //标记当前Handler,目的是为了将消息发送给对应的Handler
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
* 定义:属于消息队列类(MessageQueue)的方法
          * 作用:入队,即 将消息 根据时间 放入到消息队列中(Message ->> MessageQueue)
          * 采用单链表实现:提高插入消息、删除消息的效率
          */
          boolean enqueueMessage(Message msg, long when) {

                ...// 仅贴出关键代码

                synchronized (this) {

                    msg.markInUse();
                    msg.when = when;
                    Message p = mMessages;
                    boolean needWake;

                    // 判断消息队列里有无消息
                        // a. 若无,则将当前插入的消息 作为队头 & 若此时消息队列处于等待状态,则唤醒
                        if (p == null || when == 0 || when < p.when) {
                            msg.next = p;
                            mMessages = msg;
                            needWake = mBlocked;
                        } else {
                            needWake = mBlocked && p.target == null && msg.isAsynchronous();
                            Message prev;

                        // b. 判断消息队列里有消息,则根据 消息(Message)创建的时间 插入到队列中
                            for (;;) {
                                prev = p;
                                p = p.next;
                                if (p == null || when < p.when) {
                                    break;
                                }
                                if (needWake && p.isAsynchronous()) {
                                    needWake = false;
                                }
                            }

                            msg.next = p; 
                            prev.next = msg;
                        }

                        if (needWake) {
                            nativeWake(mPtr);
                        }
                    }
                    return true;
            }

// 之后,随着Looper对象的无限消息循环
// 不断从消息队列中取出Handler发送的消息 & 分发到对应Handler
// 最终回调Handler.handleMessage()处理消息
  •  

借鉴:https://www.jianshu.com/p/b4d745c7ff7a

由上可知先查看消息队列是否有消息,如果没有的话,插入该消息,之后随着Looper对象消息循环,从消息队列中取出Handler发送的消息,然后分配给对应的Handler,最终回调给Handler.handleMessage()处理消息

如果当前消息队列有消息的话,就会根据创建的时间,插入到队列中。

 

总结:初始化创建Handler 查看本地是否有Looper,如果没有Looper,需要创建(looper.prepare()),并且Looper本身会绑定当前线程,并创建消息队列,之后调用Looper.loop()去运作准备处理消息,准备工作完成之后,只需要等待消息,通过message.obtain()创建消息,sendMessage()发送消息,如果消息队列里没有消息的话就插入消息,如果消息队列还有消息的话,就按照时间的顺序插入消息链表中,等待looper.loop遍历消息,通过dispatchMessage发送给相应的Handler,最终回调给Handler.handleMessage()处理消息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万子开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值