Android Handler机制原理及源码解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 今天打算写一下Handler机制的原理及源码解析,Handler在我们的开发中用到的频率还是非常高的,同时这也是一个非常基础的知识点,但是即使是基础知识,有很多工作两三年的安卓开发依然是一知半解,搞不清楚原理,包括View、ViewGroup的事件分发及绘制流程。

今天打算写一下Handler机制的原理及源码解析,Handler在我们的开发中用到的频率还是非常高的,同时这也是一个非常基础的知识点,但是即使是基础知识,有很多工作两三年的安卓开发依然是一知半解,搞不清楚原理,包括View、ViewGroup的事件分发及绘制流程。
在深入学习一下知识点之前,希望能够带着疑问去思考:
1.为什么在子线程实例化Handler会报错闪退,而主线程不会
2.为什么每个线程只能存在一个Looper和MessageQueue
3.多个Handler发送消息是怎么保证Looper轮询消息队列发送最新消息不错乱发给其他Handler的
4.子线程真的不能更新UI吗?
5.ThreadLocal的作用
......

 Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            Log.e("接收消息", (String) msg.obj);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Message msg = handler.obtainMessage();
        msg.obj= "hello world";
        handler.sendMessage(msg);
    }

我们在实际的开发中基本上都是这样类似的写法,但是为什么当我们在子线程实例化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(
            // 在实例化Handler时, mLooper 为空的时候就会抛出这个异常
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

那么问题找到,Looper为空,接下来我们再看Looper里的代码

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

好的,问题找到,也回到了我们第4个问题ThreadLocal是什么

public class ThreadLocal<T> {
/**
     * 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();
    }
/**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

}

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
//  Thread 源码里的私有变量,为 Map 键值对类型
    ThreadLocal.ThreadLocalMap threadLocals = null;

Java基础不算太差的朋友相信已经懂了,sThreadLocal.get()获取的Looper其实就保存在当前线程的私有变量threadLocals,那我们怎样给子线程加一个Looper呢,为什么在主线程可以直接使用呢?

/** 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()}.
      */
    public static void prepare() {
        prepare(true);
    }

    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() 就可以给当前线程添加一个轮询器,主线程之所以不需要开发者添加,是因为在程序启动的时候,famework层就已经给我们实例化了一个,在ActivityThread的main()方法中

  public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");
        // 实例化轮询器
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // 开启消息轮询
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

当你在子线程加上轮询器时,却发现并不行,因为轮询器还没有开启轮询消息

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        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死循环
        for (;;) {
            Message msg = queue.next(); // might block
            // 如果消息为空就return,进入下一次轮询
            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 slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

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

那么接下来我们从头到尾过一遍Handler机制,首先Looper在程序启动的时候系统就已经帮我们创建好了,那我们去看一下方法里面的实现。

 // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 /**
     * 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()}
     */
//   系统实例化Handler
    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");
        }
       // 往当前线程的私有变量里添加 Looper
        sThreadLocal.set(new Looper(quitAllowed));
    }
// Looper 在实例化的时候也实例化了一个消息队列同时还持有了当前线程的引用
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

然后我们从发送消息查看源码

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
------经过几个方法的调用进入下面的方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
       // mQueue 在Handler 实例化的时候就从当前线程中取出消息队列并赋值了
        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) {
       // 重点在这里,把当前Handler的引用赋值给 msg 的 target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

进入消息队列的源码

boolean enqueueMessage(Message msg, long when) {
        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) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
        // p == null 代表前面没有消息, when 是延迟消息的时间值
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
              //  当前消息的 next  是 p 引用,形成一个单链表结构,如果是第一个消息的话,p为空
                msg.next = p;
              // 赋值消息到轮询器
                mMessages = msg;
                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;
          // 发送了第一个消息后   mMessages 就不为空了
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

Looper.loop()会进入轮询,然后从Looper获取MessageQueue ,再调用MessageQueue .next(),这个方法不再解析,简单的说就是把当前的消息返回,注意理解Message的单链表结构,next是null,当msg获取的是null,进入下一次轮询,消息不为空调用msg.target.dispatchMessage(msg),target就是Handler,所以消息不会发送错乱。
最后再啰嗦一句,子线程可以更新UI,View不是只能在主线程更新UI,而是只能在创建它的线程更新UI,详情见源码的ViewRootImpl。

相关文章
|
6天前
|
前端开发 Java 应用服务中间件
21张图解析Tomcat运行原理与架构全貌
【10月更文挑战第2天】本文通过21张图详细解析了Tomcat的运行原理与架构。Tomcat作为Java Web开发中最流行的Web服务器之一,其架构设计精妙。文章首先介绍了Tomcat的基本组件:Connector(连接器)负责网络通信,Container(容器)处理业务逻辑。连接器内部包括EndPoint、Processor和Adapter等组件,分别处理通信、协议解析和请求封装。容器采用多级结构(Engine、Host、Context、Wrapper),并通过Mapper组件进行请求路由。文章还探讨了Tomcat的生命周期管理、启动与停止机制,并通过源码分析展示了请求处理流程。
|
2天前
|
开发框架 缓存 前端开发
electron-builder 解析:你了解其背后的构建原理吗?
本文首发于微信公众号“前端徐徐”,详细解析了 electron-builder 的工作原理。electron-builder 是一个专为整合前端项目与 Electron 应用的打包工具,负责管理依赖、生成配置文件及多平台构建。文章介绍了前端项目的构建流程、配置信息收集、依赖处理、asar 打包、附加资源准备、Electron 打包、代码签名、资源压缩、卸载程序生成、安装程序生成及最终安装包输出等环节。通过剖析 electron-builder 的原理,帮助开发者更好地理解和掌握跨端桌面应用的构建流程。
16 2
|
4天前
|
开发工具 Android开发 iOS开发
深入解析安卓与iOS开发环境的优劣
【10月更文挑战第4天】 本文将深入探讨安卓和iOS两大主流移动操作系统的开发环境,从技术架构、开发工具、用户体验等方面进行详细比较。通过分析各自的优势和不足,帮助开发者更好地理解这两个平台的异同,从而为项目选择最合适的开发平台提供参考。
12 3
|
3天前
|
SQL 分布式计算 大数据
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
14 0
|
3天前
|
SQL 分布式计算 算法
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
28 0
|
5天前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
49 0
|
10天前
|
XML 存储 Java
探索安卓开发之旅:从基础到进阶
【9月更文挑战第37天】安卓开发,一个充满无限可能的领域。它不仅关乎技术的深度与广度,更关乎开发者的成长与突破。本文将带你走进安卓开发的世界,从基础知识的学习到进阶技巧的掌握,一起感受编程的魅力与乐趣。
|
Android开发 安全
Android应用内广播LocalBroadcastManager机制详解
终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 1. 简介 通常我们在使用Android广播的时候都会直接将广播注册到系统的AMS当中,由于AMS任务繁忙,一般可能不会立即能处理到我们发出的广播,如果我们使用广播是在应用内的单个进程中使用,则完全可以采用LocalBroadcastManager来处理。
1295 0
|
4天前
|
缓存 搜索推荐 Android开发
安卓开发中的自定义控件实践
【10月更文挑战第4天】在安卓开发的海洋中,自定义控件是那片璀璨的星辰。它不仅让应用界面设计变得丰富多彩,还提升了用户体验。本文将带你探索自定义控件的核心概念、实现过程以及优化技巧,让你的应用在众多竞争者中脱颖而出。
|
4天前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
19 1

推荐镜像

更多