Android FrameWork 层 Handler源码解析

news2024/10/7 20:32:31

Handler生产者-消费者模型

在android开发中,经常会在子线程中进行一些耗时操作,当操作完毕后会通过handler发送一些数据给主线程,通知主线程做相应的操作。 其中:子线程、handler、主线程,其实构成了线程模型中经典的生产者-消费者模型。

生产者-消费者模型:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加数据,消费者从存储空间中取走数据。 

这么设计有什么好处呢?

保证数据生产消费的顺序(通过MessageQueue,先进先出) - 不管是生产者(子线程)还是消费者(主线程)都只依赖缓冲区(handler),生产者消费者之间不会相互持有,使他们之间没有任何耦合 。

Handler是Android消息管理机制,屏幕触摸事件、生命周期事件等都是封装成message,发送到handler进行处理。通过源码,我们进一步分析Handler的机制。

Handler机制

Hanlder:发送和接收消息;

Looper:用于轮询消息队列,一个线程只能有一个Looper;

Message: 消息实体;

MessageQueue: 消息队列用于存储消息和管理消息 。

创建Looper

创建 Looper 的方法是调用 Looper.prepare() 方法,在 ActivityThread 中的 main 方法中为我们创建了 prepare。
public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    //其他代码省略...
    Looper.prepareMainLooper(); //初始化Looper以及MessageQueue
    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");
}

Looper.prepareMainLooper(); 

public static void prepareMainLooper() {
    prepare(false);//消息队列不可以quit
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been
                    prepared.");
        }
        sMainLooper = myLooper();
    }
}

prepare有两个重载的方法,主要看 prepare(boolean quitAllowed) quitAllowed的作用是在创建MessageQueue时标识消息队列是否可以销毁, 主线程不可被销毁 下面有介绍。

public static void prepare() {
    prepare(true);//消息队列可以quit
}
//quitAllowed 主要
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {//不为空表示当前线程已经创建了Looper
        throw new RuntimeException("Only one Looper may be created per thread");
        //每个线程只能创建一个Looper
    }
    sThreadLocal.set(new Looper(quitAllowed));//创建Looper并设置给sThreadLocal,这样get的时候就不会为null了
}

创建MessageQueue以及Looper与当前线程的绑定

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);//创建了MessageQueue
    mThread = Thread.currentThread(); //当前线程的绑定
}

MessageQueue的构造方法

MessageQueue(boolean quitAllowed) {
    //mQuitAllowed决定队列是否可以销毁 主线程的队列不可以被销毁需要传入false, 在MessageQueue的quit()方法就不贴源码了
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

Looper.loop()

同时是在main方法中 Looper.prepareMainLooper() 后Looper.loop(); 开始轮询

public static void loop() {
    final Looper me = myLooper();//里面调用了sThreadLocal.get()获得刚才创建的Looper对象
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this
                thread.");
    }//如果Looper为空则会抛出异常
    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) {//由于刚创建MessageQueue就开始轮询,队列里是没有消息的,等到Handler sendMessage
            enqueueMessage后
            //队列里才有消息
            // 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);//msg.target就是绑定的Handler,详见后面Message的部分,Handler开始
        //后面代码省略.....
        msg.recycleUnchecked();
    }
}

Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

public Handler(Callback callback, boolean async) {
    //前面省略
    mLooper = Looper.myLooper();//获取Looper,**注意不是创建Looper**!
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//创建消息队列MessageQueue
    mCallback = callback; //初始化了回调接口
    mAsynchronous = async;
}

创建Handler

Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};
在内部调用 this(null, false);
public Handler(Callback callback, boolean async) {
    //前面省略
    mLooper = Looper.myLooper();//获取Looper,**注意不是创建Looper**!
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//创建消息队列MessageQueue
    mCallback = callback; //初始化了回调接口
    mAsynchronous = async;
}
Looper.myLooper();
//这是Handler中定义的ThreadLocal ThreadLocal主要解多线程并发的问题
// 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();
}
sThreadLocal.get() will return null unless you’ve called prepare(). 这句话告诉我们get可能返回null 除非先调用 prepare()方法创建Looper。

创建Message

可以直接new Message 但是有更好的方式 Message.obtain。因为可以检查是否有可以复用的Message,用过复用避免过多的创建、销毁Message对象达到优化内存和性能的目地。
public static Message obtain(Handler h) {
    Message m = obtain();//调用重载的obtain方法
    m.target = h;//并绑定的创建Message对象的handler
    return m;
}

public static Message obtain() {
    synchronized (sPoolSync) {//sPoolSync是一个Object对象,用来同步保证线程安全
        if (sPool != null) {//sPool是就是handler dispatchMessage 后 通过recycleUnchecked
            回收用以复用的Message
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

Message和Handler的绑定

创建Message的时候可以通过 Message.obtain(Handler h) 这个构造方法绑定。当然可以在 在Handler 中的enqueueMessage()也绑定了,所有发送Message的方法都会调用此方法入队,所以在创建Message的时候是可以不绑定的。

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

Handler发送消息

Handler发送消息的重载方法很多,但是主要只有2种。 sendMessage(Message) sendMessage方法通过一系列重载方法的调用,sendMessage调用sendMessageDelayed,继续调用sendMessageAtTime,继续调用enqueueMessage,继续调用messageQueue的enqueueMessage方法,将消息保存在了消息队列中,而最终由Looper取出,交给Handler的dispatchMessage进行处理;

我们可以看到在dispatchMessage方法中,message中callback是一个Runnable对象,如果callback不为空,则直接调用callback的run方法,否则判断mCallback是否为空,mCallback在Handler构造方法中初始化,在主线程通直接通过无参的构造方法new出来的为null,所以会直接执行后面的handleMessage()方法。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//callback在message的构造方法中初始化或者使用
        handler.post(Runnable)时候才不为空
        handleCallback(msg);
    } else {
        if (mCallback != null) {//mCallback是一个Callback对象,通过无参的构造方法创建出来的handler,
            该属性为null,此段不执行
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//最终执行handleMessage方法
    }
}

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

Handler处理消息

在handleMessage(Message)方法中,我们可以拿到message对象,根据不同的需求进行处理,整个Handler机制的流程就结束了。 

总结:

handler.sendMessage 发送消息到消息队列MessageQueue,然后looper调用自己的loop()函数带动MessageQueue从而轮询messageQueue里面的每个Message,当Message达到了可以执行的时间的时候开始执行,执行后就会调用message绑定的Handler来处理消息。

大致的过程如下图所示:

handler机制就是一个传送带的运转机制。

1)MessageQueue就像履带。

2)Thread就像背后的动力,就是我们通信都是基于线程而来的。

3)传送带的滚动需要一个开关给电机通电,那么就相当于我们的loop函数,而这个loop里面的for循环就会带着不断的滚动,去轮询messageQueue。

4)Message就是我们的货物了。 

Handler线程同步问题

Handler是用于线程间通信的,但是它产生的根本并不只是用于UI处理,而更多的是handler是整个app通信的框架,大家可以在ActivityThread里面感受到,整个App都是用它来进行线程间的协调。Handler既然这么重要,那么它的线程安全就至关重要了,那么它是如何保证自己的线程安全呢?

Handler机制里面最主要的类MessageQueue,这个类就是所有消息的存储仓库,在这个仓库中,我们如何的管理好消息,这个就是一个关键点了。消息管理就2点:1)消息入库(enqueueMessage),2)消息出库(next),所以这两个接口是确保线程安全的主要档口。

enqueueMessage 源码:

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;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            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;
            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);
        }
    }
//锁结束的地方

synchronized锁是一个内置锁,也就是由系统控制锁的lock unlock时机的。这个锁,说明的是对所有调用同一个MessageQueue对象的线程来说,他们都是互斥的,然而,在我们的Handler里面,一个线程是对应着一个唯一的Looper对象,而Looper中又只有一个唯一的MessageQueue(这个在上文中也有介绍)。所以,我们主线程就只有一个MessageQueue对象,也就是说,所有的子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其他的都需要等待,那么这个时候消息队列就不会出现混乱。

next函数 源码:

Message next() {
    ....
    for (;;) {
        ....
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            // Try to retrieve the next message. Return if found.
                    ...
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            ...
        }//synchronized 结束之处
        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;
        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

next函数很多同学会有疑问:我从线程里面取消息,而且每次都是队列的头部取,那么它加锁是不是没有意义呢?

答案是否定的,我们必须要在next里面加锁,因为,这样由于synchronized(this)作用范围是所有 this正在访问的代码块都会有保护作用,也就是它可以保证 next函数和 enqueueMessage函数能够实现互斥。这样才能真正的保证多线程访问的时候messagequeue的有序进行。

Handler消息机制--同步屏障

同步屏障,view绘制中使用,同步屏障的概念,在Android开发中非常容易被人忽略,因为这个概念在我们普通的开发中太少见了,很容易被忽略。

大家经过上面的学习应该知道,线程的消息都是放到同一个MessageQueue里面,取消息的时候是互斥取消息,而且只能从头部取消息,而添加消息是按照消息的执行的先后顺序进行的排序,那么问题来了,同一个时间范围内的消息,如果它是需要立刻执行的,那我们怎么办,按照常规的办法,我们需要等到队列轮询到我自己的时候才能执行哦,那岂不是黄花菜都凉了。所以,我们需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。

同步屏障是什么?

屏障的意思即为阻碍,顾名思义,同步屏障就是阻碍同步消息,只让异步消息通过。如何开启同步屏障呢?使用:MessageQueue#postSyncBarrier()

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token
    synchronized (this) {
        final int token = mNextBarrierToken++;
        //从消息池中获取Message
        final Message msg = Message.obtain();
        msg.markInUse();
        //就是这里!!!初始化Message对象的时候,并没有给target赋值,因此 target==null
        msg.when = when;
        msg.arg1 = token;
        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                //如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
                prev = p;
                p = p.next;
            }
        }
        //根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

可以看到,Message 对象初始化的时候并没有给 target 赋值,因此, target == null 的 来源就找到了。上面消息的插入也做了相应的注释。这样,一条 target == null 的消息就进入了消息队列。

同步屏障总结:

1)messageQueue.postSyncBarrier(),发送一个message.target = null消息,开启同步屏障;
2)随后发送一个异步消息(massage.setAsynchronous(true))到messageQueue;
3)messageQueue会优先处理异步消息;
4)异步消息处理完,调用MessageQueue.removeSyncBarrier移除屏障消息。

那么,开启同步屏障后,所谓的异步消息又是如何被处理的呢?

如果对消息机制有所了解的话,应该知道消息的最终处理是在消息轮询器 Looper#loop() 中,而 loop() 循环中会调用 MessageQueue#next() 从消息队列中进行取消息。

    .....//省略一些代码
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
    // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
    // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
    // 如果期间有程序唤醒会立即返回。
    int nextPollTimeoutMillis = 0;
    //next()也是一个无限循环
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            //获取系统开机到现在的时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages; //当前链表的头结点
            //关键!!!
            //如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
            if (msg != null && msg.target == null) {
                // Stalled by a barrier. Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                //如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间,
                //场景如常用的postDelay
                if (now < msg.when) {
                    //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                    //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now,
                    Integer.MAX_VALUE);
                } else {
                    // 获取到消息
                    mBlocked = false;
                    //链表操作,获取msg并且删除该节点
                    if (prevMsg != null)
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    //返回拿到的消息
                    return msg;
                }
            } else {
                //没有消息,nextPollTimeoutMillis复位
                nextPollTimeoutMillis = -1;
            }
        .....//省略
}

从上面可以看出,当消息队列开启同步屏障的时候(即标识为 msg.target == null ),消息机制在处理消息的时候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

下面用示意图简单说明:

如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙----同步屏障(红色部分)。有了同步屏障的存在,msg_2 和 msg_M 这两个异步消息可以被优先处理,而后面的 msg_3 等同步消息则不会被处理。那么这些同步消息什么时候可以被处理呢?那就需要先移除这个同步屏障,即调用 removeSyncBarrier() 。

同步消息的应用场景

似乎在日常的应用开发中,很少会用到同步屏障。那么,同步屏障在系统源码中有哪些使用场景呢?Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。

比如,在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了ViewRootImpl#scheduleTraversals() ,如下:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //开启同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //发送异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

postCallback() 最终走到了 ChoreographerpostCallbackDelayedInternal() :

private void postCallbackDelayedInternal(int callbackType,
                                         Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType- ", action=" + action + ",
                token=" + token =" + delayMillis);
    }
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true); //异步消息
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

这里就开启了同步屏障,并发送异步消息,由于 UI 更新相关的消息是优先级最高的,这样系统就会优先处理这些异步消息。最后,当要移除同步屏障的时候需要调用 ViewRootImpl#unscheduleTraversals() 。

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        //移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

小结:

同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用
Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的 setAsynchronous(true) 时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/870592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker搭建LNMP

docker安装 略 下载镜像 nginx:最新版php-fpm:根据自己需求而定mysql:根据自己需求定 以下是我搭建LNMP使用的镜像版本 rootVM-12-16-ubuntu:/docker/lnmp/php/etc# docker images REPOSITORY TAG IMAGE ID CREATED SIZE mysql 8.0…

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验六 静态数码管显示

目录 前言 一、原理图及知识点介绍 1.1、数码管原理图&#xff1a; 二、代码分析 前言 第一个实验&#xff1a; 51单片机&#xff08;普中HC6800-EM3 V3.0&#xff09;实验例程软件分析 实验一 点亮第一个LED_ManGo CHEN的博客-CSDN博客 第二个实验&#xff1a;51单片机&am…

【踩坑】最新亲测能用!修复MacOS安装软件时提示“应该移到废纸篓”并且无法打开软件

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 目录 网上方法的尝试 方法一&#xff1a;xattr 方法二&#xff1a;UPX 真的能用的方法 GateKeeper_Helper.command的内容 网上方法的尝试 方法一&#xff1a;xattr 以前的Mac版本可以通过以下方式来解开限…

从ChatGLM2-6B来看大模型扩展上下文和加速推理相关技术

ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本&#xff0c;在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上&#xff0c;ChatGLM2-6B 引入了如下新特性&#xff1a; 更强大的性能&#xff1a;基于 ChatGLM 初代模型的开发经验&#xff0c;全面…

【深入探索Docker】:开启容器化时代的技术奇迹

深入探索Docker 深入探索Docker&#xff1a;开启容器化时代的技术奇迹前言1. 容器化&#xff1a;实现快速部署和可移植性2. 虚拟化&#xff1a;提高安全性和可靠性3. 映像&#xff1a;打包应用及依赖项的模板4. 网络管理&#xff1a;连接容器和主机5. 持久化数据&#xff1a;保…

Linux 终端命令之文件浏览(3) less

Linux 文件浏览命令 cat, more, less, head, tail&#xff0c;此五个文件浏览类的命令皆为外部命令。 hannHannYang:~$ which cat /usr/bin/cat hannHannYang:~$ which more /usr/bin/more hannHannYang:~$ which less /usr/bin/less hannHannYang:~$ which head /usr/bin/he…

多个 el-select 下拉框调用同一个数组时,下拉框的值不能重复

实现的效果 多个下拉框&#xff0c;每个下拉框选中的值不能重复&#xff0c;选过的下拉值会被过滤 共用一个接口 realUserList , 里面是部门的人 四个下拉框必选&#xff0c;数据结构如下 我使用的方法是计算属性完成, 我是从vuex 里面的获取的数据( alreaySelectUser ), …

Linux MQTT智能家居(温度,湿度,环境监测,摄像头等界面布局设置)

文章目录 前言一、温度湿度曲线布局二、环境监测界面布局三、摄像头界面布局总结 前言 本篇文章来完成另外三个界面的布局设置。 这里会使用到 feiyangqingyun的一些控件库。 一、温度湿度曲线布局 TempHumtiy.h: #ifndef TEMPHUMTIY_H #define TEMPHUMTIY_H#include <…

Maven 基础学习及使用

Maven1 Maven简介1.1 Maven模型1.2 仓库 2 Maven安装配置3 Maven基本使用3.1 Maven 常用命令3.2 Maven 生命周期 4 IDEA使用Maven4.1 IDEA配置Maven环境4.2 Maven 坐标详解4.3 IDEA 创建 Maven项目4.4 IDEA 导入 Maven项目 5 依赖管理5.1 使用坐标引入jar包5.2 依赖范围 Maven …

UVA-1601 万圣节后的早晨 题解答案代码 算法竞赛入门经典第二版

GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 以三个点的当前位置作为状态&#xff0c;广度优先遍历&#xff0c;找到终点即为最短次数。 注意&#xff1a; 一次可以移动多个点&#xff0c;但是每个点只能移动一步。在同一次中&#xf…

人工智能监听——研究人员发现新的安全威胁

大学的一项新研究表明&#xff0c;人工智能 (AI) 可以通过按键声音识别密码&#xff0c;准确率超过 90%。 在通过 Zoom 等视频通话聊天时输入计算机密码可能会打开网络攻击的大门&#xff0c;一项新的研究表明&#xff0c;人工智能可以通过窃听打字声音来找出正在按下的按键。…

索引堆及其优化(Java 实例代码)

目录 索引堆及其优化 一、概念及其介绍 二、适用说明 三、结构图示 四、Java 实例代码 src/runoob/heap/IndexMaxHeap.java 文件代码&#xff1a; 索引堆及其优化 一、概念及其介绍 索引堆是对堆这个数据结构的优化。 索引堆使用了一个新的 int 类型的数组&#xff0c;…

JavaScript【BOM】

【BOM】 原创内容&#xff0c;转载请注明出处&#xff01; 一、BOM是什么 BOM&#xff08;Browser Object Model&#xff0c;浏览器对象模型&#xff09;是 JS 与 浏览器窗口交互的接口。 一些与浏览器改变尺寸、滚动条滚动相关的特效&#xff0c;都要借助 BOM 技术。 二、w…

Arduino+esp32学习笔记

学习目标&#xff1a; 使用Arduino配置好蓝牙或者wifi模块 学习使用python配置好蓝牙或者wifi模块 学习内容&#xff08;笔记&#xff09;&#xff1a; 一、 Arduino语法基础 Arduino语法是基于C的语法,C又是c基础上增加了面向对象思想等进阶语言。那就只记录没见过的。 单多…

【分布式】Viewstamped Replication Revisited

篇前感悟&#xff1a; 阅读分布式系统文章的意义其实并不在于你个人真正地去开发这样一个基于这种协议的系统&#xff0c;因为真正去开发一个高可用的分布式系统实在是太难了&#xff08;对我来说…&#xff09;更多的还是汲取其中的思想&#xff0c;包括设计思路&#xff0c;优…

高性能跨平台网络通信框架 HP-Socket v5.9.3

项目主页 : http://www.oschina.net/p/hp-socket开发文档 : https://www.docin.com/p-4478351216.html下载地址 : https://github.com/ldcsaa/HP-SocketQQ Group: 44636872, 663903943 v5.9.3 更新 一、主要更新 问题修复&#xff1a;通过 POST/PUT 等带有请求内容的 HTTP 方…

Vue主面板组件模板(简洁版)

文章目录 &#x1f412;个人主页&#x1f3c5;JavaEE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;源码如下&#xff1a; &#x1f412;个人主页 &#x1f3c5;JavaEE系列专栏 &#x1f4d6;前言&#xff1a; 本篇博客主要以介绍【&#x1f380;主面板组件模板&#xf…

怎么才能突破认知?别灌鸡汤行吗

怎么才能突破认知&#xff1f;别灌鸡汤行吗&#xff1f; 趣讲大白话&#xff1a;大部分人得一步一步来 【趣讲信息科技254期】 **************************** 孔子说有三种情况&#xff1a; 1生而知之&#xff1a;就是天生就会 2学而知之&#xff1a;需要后天学习 3困而学之&am…

【计算机网络】——数据链路层

二、组帧 1、字符计数法 帧头部使用一个字符来表示帧的大小(包括第一个计数字符) &#xff08;此处一字符一个字节&#xff09; 2、字符填充收尾定界法 特定字符来定界帧的首和尾。若帧中数据段出现等同于特定字符的字符内容&#xff0c;前置一个转义字符。(类似于正则表达…

读发布!设计与部署稳定的分布式系统(第2版)笔记30_为部署而设计

1. 部署行为是系统生命的重要组成部分 1.1. 只编写代码是不够的&#xff0c;只要没有在生产环境中运行&#xff0c;一切都不算完成 1.2. 要想取得成功&#xff0c;需要早早地频繁部署软件 1.3. 设计易于部署的软件非常有必要 1.4. 零停机部署就是目标 2. 机器与服务 2.1.…