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

Android Handler那些事儿(二)——几个关键类之间的关系

程序员文章站 2022-05-23 17:36:18
...
Looper是什么?

Looper是android.os包里的一个类,看名字就知道和os相关。它和handler等配合完成android的消息机制。Looper完成线程中的消息循环,即不断地读取MessageQueue中的消息。
但是呢,在Thread中默认是没有Looper的,所以想要使用Handler,就得获取一个Looper;该类提供了静态方法Looper.prepare()来获得Looper,并通过Looper.loop()无限循环获取和分发MessageQueue中的消息。

在Android中主线程在ActivityThread中已经调用了,所以在主线程使用Handler不用显示地调用Looper的方法,但是在子线程中使用Handler是需要显示调用的。

Looper怎么用?
class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();
        // Step 1: 创建Handler
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                //处理即将发送过来的消息

            }
        };

        Looper.loop();
    }
}

为什么需要Looper?

我们看啊,在上面的代码中,在子线程中创建了Handler并重写了接收message的方法,但是如果没有一个无限循环的话,线程不是应该立马就结束了吗,而创建的Handler随之也应该被回收,那如何还能给子线程的Handler传递消息?

那我们写个while吧让线程不退出,这样试试。
结果是。。。。

2020-05-27 11:27:07.965 9366-9383/com.sensetime.myapplication E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.sensetime.myapplication, PID: 9366
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)
        at com.sensetime.myapplication.MainActivity$LooperThread$1.<init>(MainActivity.java:728)
        at com.sensetime.myapplication.MainActivity$LooperThread.run(MainActivity.java:728)

还没发消息就直接崩,跟是不是无限循环没半毛钱关系,Handler在创建的时候就会检查Looper是否创建过,不然的话就直接抛异常不干了。

    public Handler(Callback callback, boolean async) {
        if (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();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

进而还可以做实验:

1、如果不执行Loop.prepare(),直接崩溃,报错和上面一样的。说明Loop.prepare是创建Looper的方法

2、如果不执行Looper.loop(),啥也收不到,说明要能收消息,必须得开启loop循环。

现在我们可以达成一致结论,不用Looper不行。下面看一下Looper的源码,看它到底有什么玄机。

分析Looper的运作机制?怎么和Handler绑定起来的?

首先要接受,一个线程对应一个looper的结论,这个在后面的代码分析中是显而易见的。
Android Handler那些事儿(二)——几个关键类之间的关系

我们先看一下Looper里面有哪些重要成员变量。首先是sThreadLocal,它是一个静态对象,我们都知道静态对象是属于类的,那么不管哪个线程的Looper,都能够访问到同一个sThreadLocal,那岂不是就乱套了吗?接下来再看它的奇妙之处。

ThreadLocal.java

先说一下ThreadLocal的特点,然后再用一个例子来说明。ThreadLocal是一个容器,那么是容器就能存储数据对吧,这是第一点;第二,这个容器有什么特殊之处呢?它能实现以线程为作用域的存储,线程之间数据隔离。也就是说,如果使用threadLocal.set分别在两个线程存储不同的东西(注意操作的是同一个对象threadlocal),再用threadlocal.get取出数据来;取出来的数据与对应线程存储的数据一致。也就是在A线程调用get,会得到在A线程set的数据,在B线程调用get,会得到在B线程set的数据,是不是很神奇。看一看set和get的源码

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

那么从set原理就看出来了,首先通过Thread.currentThread()获取当前的线程,再获得Thread这个类里持有的threadLocals(ThreadLocalMap,不过创建还是由ThreadLocal的createMap完成的),所以能够限制访问域为线程,因为数据相当于存储到Thread类自身中的。

Thread.java

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

threadLocals对象是由ThreadLocal类定义类型和分配空间,但它并不持有;真正持有的是Thread类,所以就能解释为什么能够做到限制访问域为线程。

Looper.java

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

Looper在创建的时候就会和当前线程绑定,所以印证了刚才说的一个线程对应一个Looper,并且创建一个消息队列实例,这个实例,其实我们在上面Handler的构造就已经见到了,Handler里面也有一个消息列队,只不过是一个引用,同时还有一个looper的引用,这里我们就看到了Handler是怎么和Looper绑定起来的了。另外,Looper也需要分发消息给Handler,这个怎么做到的接下来再分析。

    //必须先执行Looper.prepare(),才能获取Looper对象,否则为null.
    mLooper = Looper.myLooper();  //从当前线程的TLS中获取Looper对象
    if (mLooper == null) {
        throw new RuntimeException("");
    }
    mQueue = mLooper.mQueue; //消息队列,来自Looper对象

下面再看一下Looper中另一个比较重要的静态方法myLooper

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

这个方法有什么用呢?从本地的sThreadLocal里get出一个东东,下面看一下ThreadLocal的get方法

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

和上面分析的set方法如出一辙,都是获取当前线程后从线程的map中取出数据,key正是threadLocal这个对象本身,而这个对象是谁创建的呢?正是Looper

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

下面终于要说要离用户更近的方法了,看看Looper.prepare到底干了什么事?

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

首先,代码上可以看出来为什么Looper.prepare不能调用两次。然后new一个Looper对象放进ThreadLocal里,这样把Looper和当前线程给绑定在一起了。再看一下loop方法,必须要调用了looper方法后才能开启消息循环。

下图是各个类之间的关联关系,比较清晰地反应了是怎样联系起来的。
Android Handler那些事儿(二)——几个关键类之间的关系

Looper并没有直接关联上Handler,但是,Message本身就包含了一个Handler的引用,成员变量target,所以Looper才能把相应的Message传给对应的Handler。
下面总结一下各个类主要作用:

  • Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
  • MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
  • Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
  • Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。

消息入队与分发处理流程

刚才分析完了Handler、Looper、Message等的关系,现在我们来捋一下,当我们调用Handler的sendMessage发生了什么,最终又怎样传到了Handler的handMessage回调中。

首先从sendMessage聊起,调用栈如下所示。

Handler.java

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }    

整体流程下来比较简单,但是需要注意几个地方。

  • 1、到了sendMessageAtTime的时候,就把关联的Looper中的MessageQueue联系起来了。
  • 2、到了enqueueMessage的时候把该条Message的target设置成了Handler自身,这样把该条Message和对应的Handler绑定起来了。
  • 3、最后调用的是MessageQueue的enqueueMessage方法,作用是添加一条消息进Looper的消息队列我们来看一下。
boolean enqueueMessage(Message msg, long when) {
    // 每一个普通Message必须有一个target,指向对应Handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        if (mQuitting) {  //正在退出时,回收msg,加入到消息池
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支并且插队到最前面
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //当阻塞时需要唤醒
        } else {
            //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
            //消息队头存在barrier,并且同时Message是队列中最早的异步消息。
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            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;
        }
        //消息没有退出,我们认为此时mPtr != 0
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

现在消息已经到了Looper的消息队列中了,我们下一步就是看loop到底干了什么事以及怎么分发到Handler的HandMessage中。

public static void loop() {
    final Looper me = myLooper();  //获取TLS存储的Looper对象
    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列

    Binder.clearCallingIdentity();
    //确保在权限检查时基于本地进程,而不是调用进程。
    final long ident = Binder.clearCallingIdentity();

    for (;;) { //进入loop的主循环方法
        Message msg = queue.next(); //可能会阻塞 
        if (msg == null) { //没有消息,则退出循环
            return;
        }

        //默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
        Printer logging = me.mLogging;  
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg); //用于分发Message 
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        //恢复调用者信息
        final long newIdent = Binder.clearCallingIdentity();
        msg.recycleUnchecked();  //将Message放入消息池 
    }
}

可以看到,其实最重要的就是 msg.target.dispatchMessage(msg)这句话,它取出了Message对应的的Handler并且调用它的dispatchMessage方法,看一下。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        //当Message存在回调方法,回调msg.callback.run()方法;
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //当Handler存在Callback成员变量时,回调方法handleMessage();
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //Handler自身的回调方法handleMessage()
        handleMessage(msg);
    }
}

真像大白了:

  • [1] 当Message的回调方法不为空时,则回调方法msg.callback.run(),其中callBack数据类型为Runnable,否则进入步骤2;
  • [2] 当Handler的mCallback成员变量不为空时,则回调方法mCallback.handleMessage(msg),否则进入步骤3;
  • [3] 调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。

我们一般使用的是3,如果调用的是Handler的post方法传的Runnable对象则是第一种,流程和上述差不多,只是把msg的callback对象赋为了你传入的runnable而已。

注意,从这里可以看出,Looper所在的线程,才是真正执行handleMessage或者你传入的Runnable的线程,现在基本把framework层的Handler机制理清楚了。

相关标签: Android ROM