• 源码分析之Handler


    Handler是Android中的消息机制实现,可以实现UI线程和子线程的消息传递,这里就来深入了解Android的消息机制,来分析Handler的源代码

    入手实例

    在Android开发中,子线程与主线程通信是再寻常不过的事情了,那么为何需要子线程和主线程通信呢,相信只要是做过Android开发都知道这是为啥,为了博客的尽可能详尽,这里还是说说原因

    举个简单例子,以前刚做Android开发的时候,啥也不懂,记得有一次,在主线程中做了耗时操作,具体记不得啥操作了,然后一运行,报了个无响应,也就是常说的ANR,然后就知道了主线程阻塞,耗时的操作放在子线程,主线程一旦阻塞超过5s就会报ANR,既然主线程不做耗时操作,那么子线程里面执行怎么和主线程互动呢,由此便引生出了Handler,当然AsyncTask也是基于同样的道理,AsyncTask的源代码在之前已经分析过了,这里就不再赘述

    那么在开发中Handler是怎么使用的呢,这里结合一个例子作为讲解

    public class MainActivity extends Activity {
    
        private static final String TAG = "cj5785";
        private TextView textView;
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Log.d(TAG, "handleMessage: " + Thread.currentThread().getName());
                String str = (String) msg.obj;
                textView.setText(str);
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            textView = (TextView) findViewById(R.id.text_view);
        }
    
        public void buttonClick(View view) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "run: " + Thread.currentThread().getName());
                    Message message = Message.obtain();
                    message.obj = "更新UI";
                    handler.sendMessage(message);
                }
            }).start();
        }
    }
    

    这是一个简单的更新UI的例子,通过log可以清楚的看到,两个打印语句是处在不同的线程中的,然而却实现了主线程和子线程的消息传递

    D/cj5785: run: Thread-13741
    D/cj5785: handleMessage: main
    

    如果直接在子线程中更新UI则会报出以下错误

    Only the original thread that created a view hierarchy can touch its views.
    

    那么这样的线程通信是怎么实现的呢,下面就来分析一下源代码

    源代码分析

    分析源代码,尤其是涉及到几个类的这种代码,找准切入点很关键,那么这个分析的切入点在哪呢,子线程发出消息,主线程接收,那么很明显是子线程的发出点,那么就应该先看子线程里面代码的处理

    Message message = Message.obtain();
    message.obj = "更新UI";
    handler.sendMessage(message);
    

    Message部分

    首先是生成了一个Message对象,这里有两种方式,一种是直接new一个出来,一种是使用obtain(),那就去看看这个对象是怎么生成的

    public static Message obtain() {
        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();
    }
    

    这里主要关注的是next参数,那么next是啥呢,有C语言基础的一看就知道是啥,没有也没关系,我们继续往下看

    // sometimes we store linked lists of these things
    /*package*/ Message next;
    
    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    

    相信这句注释已经很明显了,这不就是链表么,Java中的链表使用对象引用实现,那么也就是说这里判断有没有Message有的话就实现复用,没有的话就new一个,而sPoolSync就是一个同步锁而已,到这里,并没有给出主线程和子线程消息传递的理由,那么继续往下看,message.obj = "更新UI";这里只是设置了变量,也没有参考价值,那么只剩handler.sendMessage(message);了,到此貌似Message的任务就完成了,那么来看看Message里面的一些值得注意的地方吧
    照例先从类开始

    public final class Message implements Parcelable {
    	···
    }
    

    Message实现了Parcelable接口,Parcelable和Serializable都是实现序列化,用于消息的传输,Message是一个final类,简而言之无法继承
    再看看Message的成员变量,常用那几个就不说了,这里谈一谈重要的一些

    /*package*/ Bundle data;
    /*package*/ Handler target;
    /*package*/ Runnable callback;
    
    private static final int MAX_POOL_SIZE = 50;
    private static boolean gCheckRecycle = true;
    

    首先是Bundle,这也就是Message为何可以设置Bundle的原因所在了,这里出现了Handler,也就是说在这里涉及到了Handler,这里简单来说就是Message会持有Handler的引用,这里注意到似乎只用带参的obtain()以及setTarget()才会初始化Handler,MAX_POOL_SIZE说明了Message的大小,gCheckRecycle则是清除信息的标志位

    Handler部分

    那么到这里已经没啥线索了,可以猜测在Handler中对target进行了初始化,那看看sendMessage()做了啥吧

    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);
    }
    

    所以从这就可以看出,当延迟小于零时会将其设为0,这里注意一下,SystemClock.uptimeMillis()是一个native方法,返回的是boot时间,再继续追寻

    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);
    }
    

    到这里,出现了一个新东西,这便是Handler组成的重要部分,那么MessageQueue是啥呢,熟悉数据结构的一眼就看出来了,这其实是个队列,稍后再看这个队列做了啥,队列不为空就要调enqueueMessage()了,那么继续

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    这里,对target赋值了,然后设置了异步状态标识,然后回到了MessageQueueenqueueMessage()方法,至此,有必要暂停Handler的分析,去看一看MessageQueue

    MessageQueue部分

    MessageQueue中找到handler调用的方法,由于方法比较长,不便于描述,将其描述用注释形式写在代码中

    boolean enqueueMessage(Message msg, long when) {
    	//target不能为空
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
    	//msg不能正在被使用
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
    
    	//同步代码块,保证线程安全
        synchronized (this) {
    		//未退出
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle(); //清除Message
                return false;
            }
    		
            msg.markInUse(); //标记正在使用
            msg.when = when; //得到延迟
            Message p = mMessages; //赋值,在第一次调用此方法前,mMessages为null
            boolean needWake;
            if (p == null || when == 0 || when < p.when) { //由于是短路或,不会执行到第三个条件
                // New head, wake up the event queue if blocked.
                msg.next = p; //将msg的next指向p
                mMessages = msg; //将msg赋值给mMessages
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev; //定义前节点
                for (;;) { //条件循环
                    prev = p; //设置前节点为p
                    p = p.next; //p的后节点赋值给p
                    if (p == null || when < p.when) { //循环终止条件(到达尾节点或者when值小于后面的节点)
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; //msg的下一节点指向p
                prev.next = msg; //prev的下一节点指向msg
            }
    
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    

    如果只是一个msg,那么就只形成一个节点,如果多个msg,则会按时间顺序形成一条链表,虽然形成了链表,那么我们最关心的主线程与子线程的通信去哪了,或者说我们的分析过程问题出在哪里,注意到前面的mQueue,这个东西是哪里来的

    final MessageQueue mQueue;
    

    那么是在那里初始化的,注意到在MainActivity中,调用前生成了Handler对象,使用的是无参的构造方法,那么来看看这个方法

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

    额,好像没啥用,继续

    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 that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    

    关于第一个if判断,这里说明一下:FIND_POTENTIAL_LEAKS标志设置为true以检测扩展的Handler类,如果不是静态的匿名,本地或成员类, 这类可能会产生泄漏。也就是我们常见构造时的警告说明!至于消除警告方法一般是设置成静态或弱引用
    接下来发现有个mLooper,这是一个循环器,MessageQueue和Handler之间桥梁,那么这里就跳转到Looper吧

    Looper

    首先看看Looper.myLooper()干了啥

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    结合注释知道了,这里返回带有当前线程的Looper,下面是get啥呢,继续来看get()

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    

    结合注释可知,这里返回的是线程值,注意sThreadLocal的生成

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    嗯,也就是说在get前要调用prepare(),从而可以推导出在ActivityThread里面调用了这个方法,查看一下

    public static void main(String[] args) {
        ···
        Looper.prepareMainLooper();
    	···
        Looper.loop();
    	···
    }
    

    看来没错,那么再看看Looper.prepareMainLooper()

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    

    重点关注第一个方法,那么这个方法一定就是关键了

    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));
    }
    

    这里还需要注意一点,这里的set,使用的是当前线程,也就保证了一个Thread只对应一个Looper
    好像还差一点,再来,感觉到曙光了

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

    到这里,终于找到了关键点,也就是说主线程和子线程通过MeaasgeQueue建立起了连接,这个Queue是同一个对象,那么具体是怎么执行的,前面执行了Looper.prepareMainLooper();,后面还有Looper.loop();,那么就来看看这里面做了什么吧

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
    	得到当前线程Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
    	//得到当前线程Queue
        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 (;;) { //条件循环
    		//执行queue的next()方法,不断从queue中取出Message
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
    
            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
    
            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
    			//之前设置了target,这里调用Handler的dispatchMessage()方法
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
    
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
    
            // 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();
        }
    }
    

    这里就是不断循环得到Message,然后分发消息,这里的重点在于dispatchMessage()做了啥

    /**
     * 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);
        }
    }
    

    可以看到,这里由于前面没有设置callback,所以执行的是handleMessage(),那么再继续看看

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
    

    这里并没有实现这个方法,注明了要实现这个方法才能收到消息,到此,在子线程发出的消息在主线程就能够得到了
    那么到这里,消息机制的源代码分析也就完成了

    思路整理

    这里画个图就简单明了
    源码分析之Handler-时序图
    其工作原理就是通过Handler将消息发送给MessageQueue,而MessageQueue又是由Looper创建的,这里Handler和Looper使用的是同一个Queue,此时在Looper中通过loop()会不断从MessageQueue中读取消息,当msg全部都发送以后,回收消息。
    Message: 消息对象
    MessageQueen: 存储消息对象的队列
    Looper: 读取MessageQueue里面的消息,交由Handler处理
    Handler:发送消息和处理消息
    handler一定要记得在页面退出时remove

    至此,Handler消息机制就分析完毕了

  • 相关阅读:
    原型和原型链
    面向对象
    git在idea中的使用,如何构远程git方仓库
    java maven 安装
    Kafka消费者APi
    Kafka生产者APi
    kafka基本概念
    redis常用命令
    redis.conf配置文件参数说明
    Redis.RedisNativeClient的方法get_Db 没有实现
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664642.html
Copyright © 2020-2023  润新知