Android Handler,Message,MessageQueue,Loper源码解析详解

本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文《 Android中Handler的使用》,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解。

概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制。我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能。但作为程序员,我不能只知道怎么用Handler,还要知道其内部如何实现的。Handler的内部实现主要涉及到如下几个类: Thread、MessageQueue和Looper。这几类之间的关系可以用如下的图来简单说明:

Thread是最基础的,Looper和MessageQueue都构建在Thread之上,Handler又构建在Looper和MessageQueue之上,我们通过Handler间接地与下面这几个相对底层一点的类打交道。

MessageQueue

MessageQueue源码链接

最基础最底层的是Thread,每个线程内部都维护了一个消息队列——MessageQueue。消息队列MessageQueue,顾名思义,就是存放消息的队列(好像是废话…)。那队列中存储的消息是什么呢?假设我们在UI界面上单击了某个按钮,而此时程序又恰好收到了某个广播事件,那我们如何处理这两件事呢? 因为一个线程在某一时刻只能处理一件事情,不能同时处理多件事情,所以我们不能同时处理按钮的单击事件和广播事件,我们只能挨个对其进行处理,只要挨个处理就要有处理的先后顺序。 为此Android把UI界面上单击按钮的事件封装成了一个Message,将其放入到MessageQueue里面去,即将单击按钮事件的Message入栈到消息队列中,然后再将广播事件的封装成以Message,也将其入栈到消息队列中。也就是说一个Message对象表示的是线程需要处理的一件事情,消息队列就是一堆需要处理的Message的池。线程Thread会依次取出消息队列中的消息,依次对其进行处理。MessageQueue中有两个比较重要的方法,一个是enqueueMessage方法,一个是next方法。enqueueMessage方法用于将一个Message放入到消息队列MessageQueue中,next方法是从消息队列MessageQueue中阻塞式地取出一个Message。在Android中,消息队列负责管理着顶级程序对象(Activity、BroadcastReceiver等)以及由其创建的所有窗口。需要注意的是,消息队列不是Android平台特有的,其他的平台框架也会用到消息队列,比如微软的MFC框架等。

Looper

Looper源码链接

消息队列MessageQueue只是存储Message的地方,真正让消息队列循环起来的是Looper,这就好比消息队列MessageQueue是个水车,那么Looper就是让水车转动起来的河水,如果没有河水,那么水车就是个静止的摆设,没有任何用处,Looper让MessageQueue动了起来,有了活力。

Looper是用来使线程中的消息循环起来的。默认情况下当我们创建一个新的线程的时候,这个线程里面是没有消息队列MessageQueue的。为了能够让线程能够绑定一个消息队列,我们需要借助于Looper:首先我们要调用Looper的prepare方法,然后调用Looper的Loop方法。典型的代码如下所示:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

需要注意的是Looper.prepare()和Looper.loop()都是在新线程的run方法内调用的,这两个方法都是静态方法。我们通过查看Looper的源码可以发现,Looper的构造函数是private的,也就是在该类的外部不能用new Looper()的形式得到一个Looper对象。根据我们上面的描述,我们知道线程Thread和Looper是一对一绑定的,也就是一个线程中最多只有一个Looper对象,这也就能解释Looper的构造函数为什么是private的了,我们只能通过工厂方法Looper.myLooper()这个静态方法获取当前线程所绑定的Looper。

Looper通过如下代码保存了对当前线程的引用:

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

所以在Looper对象中通过sThreadLocal就可以找到其绑定的线程。ThreadLocal中有个set方法和get方法,可以通过set方法向ThreadLocal中存入一个对象,然后可以通过get方法取出存入的对象。ThreadLocal在new的时候使用了泛型,从上面的代码中我们可以看到此处的泛型类型是Looper,也就是我们通过ThreadLocal的set和get方法只能写入和读取Looper对象类型,如果我们调用其ThreadLocal的set方法传入一个Looper,将该Looper绑定给了该线程,相应的get就能获得该线程所绑定的Looper对象。

我们再来看一下Looper.prepare(),该方法是让Looper做好准备,只有Looper准备好了之后才能调用Looper.loop()方法,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));
}

上面的代码首先通过sThreadLocal.get()拿到线程sThreadLocal所绑定的Looper对象,由于初始情况下sThreadLocal并没有绑定Looper,所以第一次调用prepare方法时,sThreadLocal.get()返回null,不会抛出异常。重点是下面的代码sThreadLocal.set(new Looper(quitAllowed)),首先通过私有的构造函数创建了一个Looper对象的实例,然后通过sThreadLocal的set方法将该Looper绑定到sThreadLocal中。
这样就完成了线程sThreadLocal与Looper的双向绑定:
a. 在Looper内通过sThreadLocal可以获取Looper所绑定的线程;
b.线程sThreadLocal通过sThreadLocal.get()方法可以获取该线程所绑定的Looper对象。

上面的代码执行了Looper的构造函数,我们看一下其代码:

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

我们可以看到在其构造函数中实例化一个消息队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。

在执行完了Looper.prepare()之后,我们就可以在外部通过调用Looper.myLooper()获取当前线程绑定的Looper对象。
myLooper的代码如下所示:

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

需要注意的是,在一个线程中,只能调用一次Looper.prepare(),因为在第一次调用了Looper.prepare()之后,当前线程就已经绑定了Looper,在该线程内第二次调用Looper.prepare()方法的时候,sThreadLocal.get()会返回第一次调用prepare的时候绑定的Looper,不是null,这样就会走的下面的代码throw new RuntimeException(“Only one Looper may be created per thread”),从而抛出异常,告诉开发者一个线程只能绑定一个Looper对象。

在调用了Looper.prepare()方法之后,当前线程和Looper就进行了双向的绑定,这时候我们就可以调用Looper.loop()方法让消息队列循环起来了。
需要注意的是Looper.loop()应该在该Looper所绑定的线程中执行。

Looper.loop()的代码如下:

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 (;;) {
            //注意下面这行
            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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            //注意下面这行
            msg.target.dispatchMessage(msg);

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

上面有几行代码是关键代码:
1. final MessageQueue queue = me.mQueue;
变量me是通过静态方法myLooper()获得的当前线程所绑定的Looper,me.mQueue是当前线程所关联的消息队列。
2. for (;;)
我们发现for循环没有设置循环终止的条件,所以这个for循环是个死循环。
3. Message msg = queue.next(); // might block
我们通过消息队列MessageQueue的next方法从消息队列中取出一条消息,如果此时消息队列中有Message,那么next方法会立即返回该Message,如果此时消息队列中没有Message,那么next方法就会阻塞式地等待获取Message。
4. msg.target.dispatchMessage(msg);
msg的target属性是Handler,该代码的意思是让Message所关联的Handler通过dispatchMessage方法让Handler处理该Message,关于Handler的dispatchMessage方法将会在下面详细介绍。

Handler

Handler源码链接

Handler是暴露给开发者最顶层的一个类,其构建在Thread、Looper与MessageQueue之上。
Handler具有多个构造函数,签名分别如下所示:
1. publicHandler()
2. publicHandler(Callbackcallback)
3. publicHandler(Looperlooper)
4. publicHandler(Looperlooper, Callbackcallback)
第1个和第2个构造函数都没有传递Looper,这两个构造函数都将通过调用Looper.myLooper()获取当前线程绑定的Looper对象,然后将该Looper对象保存到名为mLooper的成员字段中。
第3个和第4个构造函数传递了Looper对象,这两个构造函数会将该Looper保存到名为mLooper的成员字段中。
第2个和第4个构造函数还传递了Callback对象,Callback是Handler中的内部接口,需要实现其内部的handleMessage方法,Callback代码如下:

public interface Callback {
        public boolean handleMessage(Message msg);
}

Handler.Callback是用来处理Message的一种手段,如果没有传递该参数,那么就应该重写Handler的handleMessage方法,也就是说为了使得Handler能够处理Message,我们有两种办法:
1. 向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法
2. 无需向Hanlder的构造函数传入Handler.Callback对象,但是需要重写Handler本身的handleMessage方法
也就是说无论哪种方式,我们都得通过某种方式实现handleMessage方法,这点与Java中对Thread的设计有异曲同工之处。
在Java中,如果我们想使用多线程,有两种办法:
1. 向Thread的构造函数传入一个Runnable对象,并实现Runnable的run方法
2. 无需向Thread的构造函数传入Runnable对象,但是要重写Thread本身的run方法
所以只要用过多线程Thread,应该就对Hanlder这种需要实现handleMessage的两种方式了然于心了。

我们知道通过sendMessageXXX系列方法可以向消息队列中添加消息,我们通过源码可以看出这些方法的调用顺序,
sendMessage调用了sendMessageDelayed,sendMessageDelayed又调用了sendMessageAtTime。
Handler中还有一系列的sendEmptyMessageXXX方法,而这些sendEmptyMessageXXX方法在其内部又分别调用了其对应的sendMessageXXX方法。

通过以下调用关系图我们可以看的更清楚些:

由此可见所有的sendMessageXXX方法和sendEmptyMessageXXX最终都调用了sendMessageAtTime方法。

我们再来看看postXXX方法,会发现postXXX方法在其内部又调用了对应的sendMessageXXX方法,我们可以查看下sendMessage的源码:

public final boolean post(Runnable r)
{
       return  sendMessageDelayed(getPostMessage(r), 0);
}

可以看到内部调用了getPostMessage方法,该方法传入一个Runnable对象,得到一个Message对象,getPostMessage的源码如下:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
 }

通过上面的代码我们可以看到在getPostMessage方法中,我们创建了一个Message对象,并将传入的Runnable对象赋值给Message的callback成员字段,然后返回该Message,然后在post方法中该携带有Runnable信息的Message传入到sendMessageDelayed方法中。由此我们可以看到所有的postXXX方法内部都需要借助sendMessageXXX方法来实现,所以postXXX与sendMessageXXX并不是对立关系,而是postXXX依赖sendMessageXXX,所以postXXX方法可以通过sendMessageXXX方法向消息队列中传入消息,只不过通过postXXX方法向消息队列中传入的消息都携带有Runnable对象(Message.callback)。

我们可以通过如下关系图看清楚postXXX系列方法与sendMessageXXX方法之间的调用关系:

通过分别分析sendEmptyMessageXXX、postXXX方法与sendMessageXXX方法之间的关系,我们可以看到在Handler中所有可以直接或间接向消息队列发送Message的方法最终都调用了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);
}

该方法内部调用了enqueueMessage方法,该方法的源码如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //注意下面这行代码
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //注意下面这行代码
        return queue.enqueueMessage(msg, uptimeMillis);
}

在该方法中有两件事需要注意:
1. msg.target = this
该代码将Message的target绑定为当前的Handler
2. queue.enqueueMessage
变量queue表示的是Handler所绑定的消息队列MessageQueue,通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。

所以我们通过下图可以看到完整的方法调用顺序:

我们在分析Looper.loop()的源码时发现,Looper一直在不断的从消息队列中通过MessageQueue的next方法获取Message,然后通过代码msg.target.dispatchMessage(msg)让该msg所绑定的Handler(Message.target)执行dispatchMessage方法以实现对Message的处理。
Handler的dispatchMessage的源码如下:

public void dispatchMessage(Message msg) {
        //注意下面这行代码
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
             //注意下面这行代码
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
             //注意下面这行代码
            handleMessage(msg);
        }
}

我们来分析下这段代码:

1.首先会判断msg.callback存不存在,msg.callback是Runnable类型,如果msg.callback存在,那么说明该Message是通过执行Handler的postXXX系列方法将Message放入到消息队列中的,这种情况下会执行handleCallback(msg), handleCallback源码如下:

private static void handleCallback(Message message) {
        message.callback.run();
}

这样我们我们就清楚地看到我们执行了msg.callback的run方法,也就是执行了postXXX所传递的Runnable对象的run方法。

2.如果我们不是通过postXXX系列方法将Message放入到消息队列中的,那么msg.callback就是null,代码继续往下执行,接着我们会判断Handler的成员字段mCallback存不存在。mCallback是Hanlder.Callback类型的,我们在上面提到过,在Handler的构造函数中我们可以传递Hanlder.Callback类型的对象,该对象需要实现handleMessage方法,如果我们在构造函数中传递了该Callback对象,那么我们就会让Callback的handleMessage方法来处理Message。

3.如果我们在构造函数中没有传入Callback类型的对象,那么mCallback就为null,那么我们会调用Handler自身的hanldeMessage方法,该方法默认是个空方法,我们需要自己是重写实现该方法。

综上,我们可以看到Handler提供了三种途径处理Message,而且处理有前后优先级之分:首先尝试让postXXX中传递的Runnable执行,其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理,最后才是让Handler自身的handleMessage方法处理Message。

一图胜千言

我们在本文讨论了Thread、MessageQueue、Looper以及Hanlder的之间的关系,我们可以通过如下一张传送带的图来更形象的理解他们之间的关系。

在现实生活的生产生活中,存在着各种各样的传送带,传送带上面洒满了各种货物,传送带在发动机滚轮的带动下一直在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到另一端进行收集处理。

我们可以把传送带上的货物看做是一个个的Message,而承载这些货物的传送带就是装载Message的消息队列MessageQueue。传送带是靠发送机滚轮带动起来转动的,我们可以把发送机滚轮看做是Looper,而发动机的转动是需要电源的,我们可以把电源看做是线程Thread,所有的消息循环的一切操作都是基于某个线程的。一切准备就绪,我们只需要按下电源开关发动机就会转动起来,这个开关就是Looper的loop方法,当我们按下开关的时候,我们就相当于执行了Looper的loop方法,此时Looper就会驱动着消息队列循环起来。

那Hanlder在传送带模型中相当于什么呢?我们可以将Handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。我们在传送带的一端放入货物的操作就相当于我们调用了Handler的sendMessageXXX、sendEmptyMessageXXX或postXXX方法,这就把Message对象放入到了消息队列MessageQueue中了。当货物从传送带的另一端顺着管道划出时,我们就相当于调用了Hanlder的dispatchMessage方法,在该方法中我们完成对Message的处理。

啰啰嗦嗦说了很多,希望本文对于大家理解Android中的Handler和消息循环机制有所帮助。

到此这篇关于Android Handler,Message,MessageQueue,Loper源码解析详解的文章就介绍到这了,更多相关Android Handler,Message,MessageQueue,Loper内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入Android Handler,MessageQueue与Looper关系

    关联篇:HandlerThread 使用及其源码完全解析 关联篇:Handler内存泄漏详解及其解决方案 一说到Android的消息机制,自然就会联想到Handler,我们知道Handler是Android消息机制的上层接口,因此我们在开发过程中也只需要和Handler交互即可,很多人认为Handler的作用就是更新UI,这也确实没错,但除了更新UI,Handler其实还有很多其他用途,比如我们需要在子线程进行耗时的I/O操作,可能是读取某些文件或者去访问网络等,当耗时操作完成后我们可能需要在U

  • android利用handler实现打地鼠游戏

    本文实例为大家分享了android利用handler实现打地鼠游戏的具体代码,供大家参考,具体内容如下 xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"

  • Android Handler机制的工作原理详析

    写在前面 上一次写完Binder学习笔记之后,再去看一遍Activity的启动流程,因为了解了Binder的基本原理,这次看印象会更深一点,学习效果也比以前好很多.本来打算直接来写Activity的启动流程的,但总觉得Handler也需要写一下,知道Handler和Binder的原理后,再去看Activity的启动流程,应该也没什么问题了.虽然网上已经有很多Handler相关的文章了,而且Handler机制的上层原理也并不难,还是决定写一下,因为我想构建自己的知识体系.也希望给看我博客的朋友们一

  • Android Handler实现闪屏页倒计时代码

    我就废话不多说了,大家还是直接看代码吧~ package com.zjx.todayinfomation; import android.os.Handler; public class CustomCountDownTimer implements Runnable{ // 1.实时去回调 这个时候是什么时间 倒计时到几点 观察者设计模式 // 2.支持传入总时间 动态传入 // 3.每过一秒 总秒数 -1 // 4.总时间倒计时为0时候 回调完成状态 private int time; //

  • 使用C++11实现Android系统的Handler机制

    封面出自:板栗懒得很背景 线程作为系统的基础资源,相信大多数读者都有使用到.一般情况下我们会直接开一个线程做一些耗时操作,处理完之后让线程自动结束,资源被系统回收.这种简单粗暴的方法不少读者.甚至一些大厂的APP都在用.以Java语言为例,我们可以直接new一个Thread对象,然后覆盖run方法,最后调一下start方法便可以成功运行一个线程.如果我们每次异步做一些耗时处理都单独开启一个线程,比如异步加载网络图片这种高并发操作,每张图片都开一个线程的话,必然会造成线程资源的浪费,而且也没有很好

  • Android Handler,Message,MessageQueue,Loper源码解析详解

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文< Android中Handler的使用>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能.但

  • Golang HTTP编程的源码解析详解

    目录 1.网络基础 2.Golang HTTP编程 2.1 代码示例 2.2 源码分析 3. 总结 1.网络基础 基本TCP客户-服务器程序Socket编程流程如如下图所示. TCP服务器绑定到特定端口并阻塞监听客户端端连接, TCP客户端则通过IP+端口向服务器发起请求,客户-服务器建立连接之后就能开始进行数据传输. Golang的TCP编程也是基于上述流程的. 2.Golang HTTP编程 2.1 代码示例 func timeHandler(w http.ResponseWriter, r

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • SpringCloud微服务续约实现源码分析详解

    目录 一.前言 二.客户端续约 1.入口 构造初始化 initScheduledTasks()调度执行心跳任务 2.TimedSupervisorTask组件 构造初始化 TimedSupervisorTask#run()任务逻辑 3.心跳任务 HeartbeatThread私有内部类 发送心跳 4.发送心跳到注册中心 构建请求数据发送心跳 三.服务端处理客户端续约 1.InstanceRegistry#renew()逻辑 2.PeerAwareInstanceRegistryImpl#rene

  • Node.js高级编程cluster环境及源码调试详解

    目录 前言 准备调试环境 编译 Node.js 准备 IDE 环境 Cluster 源码调试 SharedHandle RoundRobinHandle 为什么端口不冲突 SO_REUSEADDR 补充 SharedHandle 和 RoundRobinHandle 两种模式的对比 前言 日常工作中,对 Node.js 的使用都比较粗浅,趁未羊之际,来学点稍微高级的,那就先从 cluster 开始吧. 尼古拉斯张三说过,“带着问题去学习是一个比较好的方法”,所以我们也来试一试. 当初使用 clu

  • IOS身份证识别(OCR源码)详解及实例代码

    IOS身份证识别(OCR源码)详解 最近项目用到身份证识别,在github上搜了一堆demo,在Google上找了一堆代码,有能识别出证件照的,但是都是打包成.a的静态库,没有源码,我努力吃了几天书,有了一点研究成果,现在贴出来与大家分享,要是有更好的方法,希望大神指正,共同探讨解决方案.(以下代码本人亲测可用,正在进一步探索智能识别,如有兴趣,请加入) 这里用到了两个开源库:OpenCV.TesseractOCRiOS,两个语言包chi_sim.eng.身份证识别的流程主要有:灰度化,阀值二值

  • Python日志打印里logging.getLogger源码分析详解

    实践环境 WIN 10 Python 3.6.5 函数说明 logging.getLogger(name=None) getLogger函数位于logging/__init__.py脚本 源码分析 _loggerClass = Logger # ...略 root = RootLogger(WARNING) Logger.root = root Logger.manager = Manager(Logger.root) # ...略 def getLogger(name=None): "&quo

  • Vue之vue.$set()方法源码案例详解

    在使用vue开发项目的过程中,经常会遇到这样的问题:当vue的data里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新视图的. 这是因为新加入的属性不是响应式的,因此不会触发视图的更新,通常使用静态方法Vue.set()或者实例方法this.$set()解决 ,使用方式: 对象:this.$set(target,key,  value) 数组:this.$set(target,index,  value) 但不管是静态方法Vue.

  • Java Spring @Lazy延迟注入源码案例详解

    前言 有时候我们会在属性注入的时候添加@Lazy注解实现延迟注入,今天咱们通过阅读源码来分析下原因 一.一个简单的小例子 代码如下: @Service public class NormalService1 { @Autowired @Lazy private MyService myService; public void doSomething() { myService.getName(); } } 作用是为了进行延迟加载,在NormalService1进行属性注入的时候,如果MyServ

  • Spring源码BeanFactoryPostProcessor详解

    Spring源码分析-BeanFactoryPostProcessor BeanFactoryPostProcessor接口是Spring提供的对Bean的扩展点,它的子接口是BeanDefinitionRegistryPostProcessor @FunctionalInterface public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory b

随机推荐