zoukankan      html  css  js  c++  java
  • Handler系列之原理分析

    本节让我们来学习一下Handler的工作原理吧!!!

      我们知道Android中我们只能在ui线程(主线程)更新ui信息,那么你们知道为什么只能通过Handler机制更新ui吗?其实最根本的目的就是解决多线程并发的问题。

      假设在一个Activity中有多个线程去更新ui,并且都没有加锁,那么会是什么样子?

        导致的结果就是更新界面错乱。

      如果对更新ui的操作都进行加锁处理的话又产生什么问题哪?

        性能下降。

      处于对以上问题的考虑,Android给我们提供了一套更新ui的机制,我们只需要遵守这样的机制就可以了。根本不用去关心多线程问题,因为所有更新ui的操作,都是在主线程的消息队列当中通过轮训处理的。

    <一>Handler机制的角色和职责

        1  MessageQueue  消息队列

            存储消息的容器,可以向其中添加、取出消息。遵循先进先出的原则。

        2  Handler 

            负责将消息发向消息容器即MessageQueue中。

        3  Looper 轮训器

            通过调用自身的loop方法,不断的从消息队列当中取出消息并发送给target(即handler)处理消息。当消息队列当中没有轮训消息时,它就处于堵塞状态。

      来个实际图来看一下Handler的工作原理:

        

    <二>Handler机制工作原理分析

      Handler机制要想起作用有三个步骤:

        1  创建Looper

        2  创建Handler

        3  调用Looper的loop方法,循环消息

        下面让我们来看看android中,如何去遵循这三点的,在那之前,先普及一下一个知识:

          默认整个应用程序,都是通过ActivityThread类启动的,在ActivityThread类当中,负责创建我们所有的Activity,并回调每个Activity中的生命周期方法。ActivityThread类中,默认会去创建一个线程,这个线程叫做main线程(主线程)。所有的应用程序,更新ui的操作,都是在这个main线程中进行的。

      创建Looper和调用loop方法的工作,Android SDK 已经为我们做好了,所以我们在平时使用的时候,只需要创建Handler并发送消息即可。下面我们跟随Android源码看看它是怎么做的。入口是ActivityThread的main方法。

    跟进Looper的prepareMainLooper方法

    跟进prepare方法

      这里我们需要对ThreadLocal类进行一下解释,ThreadLocal在我们的线程当中用于去保存一些变量信息,默认情况下,创建一个与线程相关的一个对象,是通过threadLocal存储的,threadLocal有set和get方法,set是把变量设置到threadLocal当中 ,get方法是获取出来。因为当前线程是ui线程,默认情况下threadLocal是没有存储的,所以为null,所以不走if而是new Looper对象之后在存储,下面我们在看看初始化Looper的时候做了哪些事情!

    我们看到,在创建Looper轮训器的时候,自动的创建了消息队列MessageQuene。也就是说默认的情况下,android为我们自动创建了主线程的Looper和MessageQuene。

    那么Handler怎么和我们的MessageQuene消息队列联系在一起的那?因为之前不是说handler发出的消息是发送到消息队列中了吗?

    原因还要看我们在创建Handler的时候做了那些事情,跟进Handler初始化源码发现最终调用的是下面这个构造器创建实例的。

    复制代码
        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;
        }
    复制代码

    跟进Looper的myLooper方法

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

    看到了什么?sThreadLocal是不是很熟悉,没错它就是ThreadLocal对象。默认情况下android为我们创建了主线程Looper对象并存储在sThreadLocal中,所以此处返回的就是主线程的Looper对象,也就是说我们在创建Handler的时候,它就和消息队列关联起来了。

    那么当我们使用handler发送消息的时候,不管使用哪一种方法,一步一步跟进源码发现最终调用的都是Handler的sendMessageAtTime方法

    复制代码
        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);
        }
    复制代码

    看代码可知,在发送消息的时候消息队列不能为null,继续跟进enqueueMessage方法

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

    可以看到,消息最终发送到了消息队列当中。那么消息是怎么轮训的那?前面已经提过,是通过Looper的loop方法,那么来看看吧!!!

    可以看到loop方法里面的机制就是一个死循环,不断的从消息队列中取出消息,然后发送给target(handler对象)的dispatchMessage方法,跟进去!!!

    一般情况下我们发送消息的时候没有给Message的callback赋值,所以第一个if条件不满足。下面的mCallback是在我们初始化Handler的时候才被初始化,Handler初始化有一种方法Handler(Callback callback),此处的参数就是给mCallback赋值的。我们一般初始化Handler的时候使用的是空参数的构造器,所以导致mCallback未被初始化,所以会直接走handleMessage(msg)方法,也就是我们初始化Handler时重写的handleMessage方法。至此,Handler工作的机制就开始工作了,你、了解了吗?

    下面让我们看看如果我们选择的是带Callback参数的初始化方式逻辑又会是什么样那,请看初始化代码:

    复制代码
        Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.d(TAG,"callback参数-------handleMessage");
                return true;//此处的返回值会影响下面的handleMessage方法是否调用
                //false     调用
                //true      不调用
            } }){
            @Override
            public void handleMessage(Message msg) {
                Log.d(TAG,"重写handler的-------handleMessage方法");
                super.handleMessage(msg);
    
            }
        };
    复制代码

    根据上面的源码分析我们知道此处Callback参数中的handleMessage方法的返回值会影响到下面第二个handleMessage方法是否调用。经过验证,return true  则不调用 ,return false则调用。

      好了,Android中的Handler机制工作原理我已经介绍完毕!!!参考了幕课网中《Android面试常客Handler详解》,大家如果没有明白可以去该网站自行学习。下篇我将介绍如何在子线程创建Handler。

  • 相关阅读:
    最新以及历史各版本 .NET Framework 的下载
    Nuget 配置文件的位置
    记录一些树莓派的问题和解决
    前端自动化测试工具doh学习总结(一)
    浏览器兼容性小记-DOM篇(一)
    Selenium自动化测试问题
    DOM何时Ready
    dijit样式定制(三)Button、RadioButton、CheckBox
    dijit样式定制(二)dijit.form.Select与dijit.form.NumberSpinner
    Portal for ArcGIS安装指南
  • 原文地址:https://www.cnblogs.com/hehheai/p/6509769.html
Copyright © 2011-2022 走看看