Android Handler消息机制完全解析-同步屏障(三)

news2024/10/9 18:25:06

Android 消息机制Handler完全解析(一)

Android 消息机制Handler完全解析(二)

前面两篇我们主要讲了Handler消息机制的一些基础,今天来看下消息屏障,通过本篇文章你将学到如下知识点

(1)什么是同步屏障

(2)为什么要有同步屏障

(3)同步屏障的原理

(4)Android FrameWork中哪里用到了同步屏障

(5)使用反射机制调用FrameWork层的发送屏障消息的方法手写一个demo

一、什么是同步屏障,为什么会有同步屏障?

想搞明白这个问题,我们需要对Android的UI刷新有点了解,以电影为例,动画至少要达到24FPS,才能保证动画的流畅性,低于这个值肉眼会感觉到卡顿,在手机上这个值被调整到60FPS,60FPS意思就是每秒播放60帧画面,那么每帧画面的时间间隔为1000 /60=16ms,这也是为什么有个16ms的指标,也就是说一般android中UI刷新的间隔是16ms。UI的刷新也是通过Handler发送消息进行的,并且UI刷新的消息是优先级最高的,否则当消息比较多时手机上的画面就会卡顿,这一点大家应该很容易想明白。通过前面两篇文章我们知道Handler的消息会被发送到MessageQueue中并且会按照时间进行排序,那么问题来了,当有比较紧急的消息到来时怎么能让它优先执行呢?比如MessageQueue中已经有100个Message排好了队,这时UI刷新消息到来怎么能让这个UI刷新的消息优先执行呢?这里就用到了我们的消息屏障。

对比现实生活中的例子就是120可以闯红灯,这是一个紧急信号,当救护车的声音响起时它就有特权插队,它的优先级是最高的因为生命是最重要的。

所以同步屏障是一种同步机制,用于确保某些操作按照预定的顺序执行,在Handler中是一种优先级策略它会让异步消息的优先级高于同步消息。为什么会有同步屏障呢?是因为在Android中有些的消息(UI刷新的消息)的优先级是最高的,当此类消息到来时我们要确保此类消息能够立即执行

二、三种消息类型是如何产生的

刚才我们提到同步屏障会让异步消息的优先级高于同步消息,我们首先来看下在Handler消息机制中有几种消息,共有三种消息分别为:

  • 同步消息
  • 异步消息
  • 屏障消息

在MessageQueue这种数据结构中存储方式如下图

在这里插入图片描述

这三种消息都是怎么来的呢?接下来我们就通过源码来具体分析下,首先同步消息在前面两篇文章中我们讲的都是同步消息,平时我们开发中使用Handler发送的消息大部分也都是同步消息。因此我们主要看下屏障消息和异步消息。

屏障消息

屏障消息怎么来的呢?在MessageQueue中提供了此方法,通过调用系统的postSyncBarrier方法会发送一条屏障消息。

这里有一点需要注意:系统的postSyncBarrier方法是添加了@hide的public方法,谷歌不建议开发者使用此方法,因为此方法使用不当很容易出问题,在后面我们会看在FrameWork中哪里用到了此方法。然后通过一个demo反射调用此方法大家就会对此有比较深入的理解。

public final class MessageQueue {
    /**
      * @hide
      */
    @UnsupportedAppUsage
    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            // 从消息池中取消息
            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) {
                    prev = p;
                    p = p.next;
                }
            }
            // 将message插入到MessgeQueue中
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }
}

这里的关键就是在初始化Message对象的时候没有给Message的target赋值,即target=null,这一点非常重要,即屏障消息的target为null

同时重置msg的when和arg1对象,将msg.arg1的值设置为token。

还记得我们自己发送的Handler消息的target是什么吗?它的源码如下

public class Handler {
    ...
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();

            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    ...
}

在这里msg.target=this就是将handler对象赋值给msg.target,这种就是同步消息,到这里大家应该能区分屏障消息和非屏障消息了,主要就是看msg.target是否为null。

异步消息

什么样的消息是异步消息呢?异步消息可以通过Message的setAsynchronous(true)进行设置,也可以通过Handler的构造方法进行设置

public class Handler {
    
    /**
     * @hide
     */
    public Handler(@Nullable Callback callback, boolean async) {
        ...
        mAsynchronous = async;
        ...
    }
    ...
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    ...
}

从代码中可以看出,当Handler的构造方法中传递async为true时,在调用Handler的enqueueMessage时会调用msg.setAsynchronous(true)将此消息设置为异步消息,本质还是调用Message的setAsynchronous方法。

到这里同步消息、异步消息、屏障消息这三种消息的区别大家应该都清楚了吧

同步消息: Message的asynchronous为false且msg.target != null

异步消息: Message的asynchronous为true且msg.target != null

屏障消息: msg.target == null

三、屏障消息是如何实现插队的

在了解了三种消息的区别之后,我们来看下同步屏障是怎么实现优先级高的消息先执行的,这里要从取消息的方法看起,我们知道MessageQueue有一个enqueueMessage用来将消息按照时间顺序插入到MessageQueue中,还有一个next()方法用来取消息,next方法的源码如下(为了阅读方便省略了部分代码)

@UnsupportedAppUsage
Message next() {
    ...
    for (;;) {
    Message prevMsg = null;
    Message msg = mMessages;
    if (msg != null && msg.target == null) { // 注释1
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        // 先执行do,再执行while,遇到同步消息会跳过,遇到异步消息退出循环
        // 即取出的msg为该屏障消息后的第一条异步消息,屏障消息不会被取出
        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }
    if (msg != null) {
        if (now < msg.when) {
            // Next message is not ready.  Set a timeout to wake up when it is ready.
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
            // Got a message.
            mBlocked = false;
            if (prevMsg != null) { // 注释2
                // 移出异步消息
                prevMsg.next = msg.next;
            } else {
                // 移出同步消息
                mMessages = msg.next;
            }
            msg.next = null;
            msg.markInUse();
            return msg;
        }
    }
    ...
}

这里重点来了,可以看到第7行如果msg != null && msg.target == null则会进入到一个do…while循环,这个while循环结束的条件就是msg==null 或者msg.isAsynchronous()为true即异步消息,如果是同步消息的话就会继续往后找,因此这里就会跳过同步消息直到找到链表中的第一个异步消息然后跳出while循环进入到下面的if条件中,对于prevMsg需要特别注意它是指向异步消息的前一个节点的Message对象,最开始的时候prevMsg的值为null,只有进入到“注释1”即第7行的if语句中时才会被赋值并且不为null,所以当找到异步消息退出while循环时会进入到"注释2"即第23行的if语句中将此异步消息移除然后将其返回。如果你不是特别清楚,我画个图你可能会理解的更加深入

假如当前的MessageQueue是如下情形

在这里插入图片描述

即链表的表头是mMessages指向同步消息msg1,因为msg1不是屏障消息所以不会进入到第7行的if语句中而是直接进入到第16行的if语句中,假如到了此消息的执行时间则会进入到第26行的分支将msg1移除,此时MessageQueue的情形如下

在这里插入图片描述

可以看到执行了mMessages=msg.next之后将同步消息msg1移除了,接着继续取消息,由于msg2是屏障消息因此会进入到第7行的if语句并进入到do…while循环首先会把链表的第一个Message赋值给prevMsg,然后msg=msg.next遍历链表直到找到异步消息退出while循环,这里也就是找到msg5。msg3和msg4这两条同步消息会被直接跳过。此时的情形如下

在这里插入图片描述

然后执行第25行移除异步消息的语句这也是单链表移除某个节点的常规操作即prevMsg.next = msg.next执行之后的情形如下

在这里插入图片描述

这样就把异步消息移除并且返回了,到这里不知道大家有没有发现一个问题,就是上述这种情形,每次进入到第4行的for(;;)循环之后都会进入到第7行的if语句中,因为链表的第一个元素始终都是屏障消息,这样是不是会导致同步消息永远执行不到?是的会有这样的问题,因此当我们插入一条消息屏障之后一定要记得将其移除,也就是说我们的屏障消息让优先级高的消息执行完之后要将其移除掉,然后去执行优先级低的消息。就好比120救护车来了时其它车辆收到一条消息(当前有紧急任务要执行)要给其让道,当120救护车行驶过之后(当前有紧急任务要执行)这条消息被移除,其它车辆就可以正常行驶了。问题来了消息屏障是怎么移除的呢?插入消息屏障我们会调用postSyncBarrier同理移除消息屏障系统也为我们提供了方法removeSyncBarrier源码如下

    /**
     * @hide
     */
    @UnsupportedAppUsage
    @TestApi
    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            // 循环遍历,直到遇到屏障消息时退出循环
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                // 删除屏障消息p
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

删除屏障消息的方法很简单,就是不断遍历消息链表,直到找到屏障消息,退出第13行while循环的条件有两个p.target == null(说明是屏障消息)和p.arg1 == token(说明p是屏障消息,在屏障消息入队的时候,设置过msg.arg1 = token)。找到屏障消息后,把它从消息队列中删除并回收。举个实例看下你就会清楚整个过程,假设当前的是如下情形

在这里插入图片描述

此时调用了removeSyncBarrier方法首先会循环遍历找到屏障消息,由于msg2满足p.target==null且p.arg1=token所以会退出第13行while循环,此时情形如下

在这里插入图片描述

然后进入到第22行的if语句执行

prev.next = p.next;

这种典型的单链表删除某个节点的代码我们是不是见到很多次了,执行之后的

在这里插入图片描述

此时就完成屏障消息的删除操作,如果链表的第一个节点就是屏障消息则更简单,因为第13行的while循环不会进入,会直接进入第26行的分支执行mMessages = p.next;从而将屏障消息删除。到这里相信大家对同步屏障有了了解,接下来通过查看FrameWork使用同步屏障的源码以及自己写的一个demo来让大家对同步屏障的了解更近一步。

四、FrameWork层哪里用到了同步屏障

接下来看下在Android中哪里用到了同步屏障,我们开头就提到了UI刷新的时候是通过Handler发送的消息且它的优先级是最高的,关于UI刷新也包含很多内容后续也会写几篇文章,在这里大家只需要知道UI更新都会调用ViewRootImpl.scheduleTraversals()方法,它的源码如下

public final class ViewRootImpl {
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 获取到handler的MessgeQueue对象并调用postSyncBarrier方法往队列中插入一个屏障消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 往队列中插入一个异步消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
}

因为UI刷新的消息优先级最高所以在这里插入了一个同步屏障,第7行的postCallback最终会调用Choreographer的postCallbackDelayedInternal这个方法

public final class Choreographer {
    private void postCallbackDelayedInternal(int callbackType,
                Object action, Object token, long 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);
                }
            }
        }
}

这就往MessageQueue中插入了一条异步消息,因为在前面插入了一个屏障消息因此此异步消息的会优先执行。前面我们说过屏障消息不会自己移除,需要调用相关的方法才能移除,从而让同步消息能够正常执行。Android源码中在执行UI绘制流程之前执行了移除同步屏障的代码

 public final class ViewRootImpl {   
	void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // 移除屏障消息
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
			...
            // 这个方法很重要,后面讲UI刷新机制的时候会再次提到,这里执行了UI绘制的三大流程
            performTraversals();
            ...
        }
    }
 }

所以在FrameWork UI刷新时首先会发送一个屏障消息,确保后续的异步消息要优先执行,然后再发送一个刷新UI的异步消息,此消息会优先执行,在UI绘制之前将屏障消息移除,从而确保同步消息的正常运行。

五、使用反射调用FrameWork层的发送屏障消息的方法

接下来我们自己手写一个实例,相信通过这个实例你会对同步屏障有更进一步的认识,前面我们提到屏障消息的发送和移除方法都是@hide的因此要通过反射才能调用,首先我们先看下如何反射调用

   private Deque<Integer> mBarrierTokens = new LinkedList<>();   
   private void addBarrier() {
        try {
            // 获得发送同步屏障的方法
            Method method = MessageQueue.class.getMethod("postSyncBarrier");
            method.setAccessible(true);
            // postSyncBarrier返回的token
            int barrierToken = (int) method.invoke(mHandler.getLooper().getQueue());
            mBarrierTokens.add(barrierToken);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private void removeBarrier() {
        if (mBarrierTokens.isEmpty()) {
            return;
        }
        try {
            // 获取移除同步屏障的方法
            Method method = MessageQueue.class.getMethod("removeSyncBarrier", int.class);
            method.setAccessible(true);
            int barrierToken = mBarrierTokens.pollLast();
            // 取出最新的token做为参数调用removeSyncBarrier方法
            method.invoke(mHandler.getLooper().getQueue(), barrierToken);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

我们先在Activity中定义几个Message,然后点击按钮发送消息,代码如下

    Message msg1 = Message.obtain(mHandler, new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "1000");
            // 添加屏障
            addBarrier();
        }
    });

    Message msg2 = Message.obtain(mHandler, new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "2000");
        }
    });

    Message msg3 = Message.obtain(mHandler, new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "3000");
        }
    });

    Message msg4 = Message.obtain(mHandler, new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "4000");
        }
    });

    Message msg5 = Message.obtain(mHandler, new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "5000");
            // 解除屏障
            removeBarrier();
        }
    });
	// 将msg3设置为异步消息
    msg3.setAsynchronous(true);
	// 将msg5设置为异步消息
    msg5.setAsynchronous(true);

    btnSend.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 发送延时消息
            mHandler.sendMessageDelayed(msg1, 1000);
            mHandler.sendMessageDelayed(msg2, 2000);
            mHandler.sendMessageDelayed(msg3, 3000);
            mHandler.sendMessageDelayed(msg4, 4000);
            mHandler.sendMessageDelayed(msg5, 5000);
        }
    });

大家先想想会如何打印

2024-10-07 22:45:43.013   MainActivity  D  1000
2024-10-07 22:45:45.014   MainActivity  D  3000
2024-10-07 22:45:47.013   MainActivity  D  5000
2024-10-07 22:45:47.018   MainActivity  D  2000
2024-10-07 22:45:47.018   MainActivity  D  4000

我来解释下为什么会这样打印,点击按钮发送了5条消息分别延时1s到5s,首先会执行msg1打印"1000",在msg1里我们添加了一个屏障根据前面所讲此时异步消息会优先执行,在上述五个消息中msg3和msg5是异步消息因此会先执行打印"3000"和"5000",在msg5执行之后会解除屏障此时同步消息正常执行,此时会打印"2000"和"4000"

总结

到这里关于消息屏障就讲的差不多了,做个总结吧

(1)Handler消息机制中的消息分为三类分别为:屏障消息、同步消息、异步消息。

(2)屏障消息的发送和移除必须成对出现,否则会一直循环查找并执行异步消息

(3)屏障消息发送后异步消息会优先执行

大家有任何疑问或者发现文章中的错误欢迎批评指正,如果觉得不错的欢迎点赞评论666

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

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

相关文章

获取时隔半个钟的三天

摘要&#xff1a; 今天遇到需求是配送时间&#xff0c;时隔半个钟的排线&#xff01;所以需要拼接时间&#xff01;例如2024-10-08 14&#xff1a;30&#xff0c;2024-10-08 15&#xff1a;00&#xff0c;2024-10-08 15&#xff1a;30 <el-form-item label"配送时间&a…

24下软考中级系统集成项目管理工程师怎么备考?

备考资料&#xff1a; 1.教材 教材可以准备由清华大学出版社出版的系统集成项目管理工程师教材&#xff0c;这也是官方所推荐的教材&#xff0c;准备这本书是绝对没错的。 2.真题 真题也是在备考过程中少不了的资料之一&#xff0c;而且系统集成项目管理工程师考试就是需要多…

初始项目托管到gitee教程,开箱即用

0.本地仓库与远程仓库关联&#xff08;需先在gitee创建仓库&#xff09; ①打开powershell生成ssh key ssh-keygen -t ed25519 -C "Gitee SSH Key"-t key 类型-C 注释 生成成功如下&#xff0c;并按下三次回车 ②查看公私钥文件 ls ~/.ssh/输出&#xff1a; id_…

华为---Super VLAN简介及示例配置

目录 1. Super VLAN技术产生背景 2. Super VLAN概念 3. Super VLAN应用场景 4. Super VLAN工作原理 5. Super-VLAN主要配置命令 6. Super-VLAN主要配置步骤 7. 示例配置 7.1 示例场景 7.2 网络拓扑 7.3 配置代码 7.4 代码解析 7.5 测试验证 1. Super VLAN技术产生背…

虹软人脸 报错 Can‘t find dependent libraries

系列文章目录 文章目录 系列文章目录一、虹软人脸 报错 Can‘t find dependent libraries 一、虹软人脸 报错 Can‘t find dependent libraries 在项目中使用了 虹软 人脸识别SDK&#xff0c;环境一直出错。 错误&#xff1a; Can’t find dependent libraries 从错误信息来…

项目启动 | 盘古信息赋能奥尼视讯数字化转型升级,实现全面数智化发展

随着信息技术的飞速发展与全球市场竞争的日益激烈&#xff0c;传统制造业正面临生存和发展的危机&#xff0c;制造企业为谋求发展&#xff0c;纷纷开启数字化转型之路&#xff0c;深度融入数字技术&#xff0c;实现生产流程的智能化、管理模式的精细化以及产品服务的个性化&…

[面试] java开发面经-1

前言 目录 1.看到你的简历里说使用Redis缓存高频数据&#xff0c;说一下Redis的操作 2.说一下Redis的缓存击穿、缓存穿透、缓存雪崩 3.你的项目中使用了ThreadLocal&#xff0c;那么当有两个请求同时发出时&#xff0c;会怎么处理&#xff0c;可以同时处理两个请求吗 4.使用…

CUDA、Pytorch、Pycharm的安装与配置

文章目录 一、CUDA安装1.检查英伟达驱动支持的最高CUDA版本 二、Pytorch的安装与环境配置1.选择是下载CPU版本还是GPU版本2.上Pytorch官网找到安装命令3.运行指令(1)CPU版本(2)GPU版本 4.验证5.安装其他所需模块(0)安装torch(1)安装Matplotlib(2)安装 pillow&#xff08;可能an…

高效稳压,YB2411 DCDC降压芯片助力高电压功率转换系统

在现代的科技发展中&#xff0c;高电压功率转换系统的需求越来越多。为满足市场需求&#xff0c;我们推出了一款高输入电压DCDC降压芯片——YB2411。 YB2411系列 1>昱灿 YB2411R SOT23-6 DC-DC高压降压 36V 0.6A 2>昱灿 YB2411SR SOT23-6 DC-DC高压降压 60V 0.8A YB241…

【simulink仿真模型】Buck变换器闭环控制,电力电子仿真模型

摘要 本文介绍了基于Simulink的Buck变换器闭环控制系统的设计与仿真。通过对Buck变换器的数学模型进行建模&#xff0c;并引入PI控制器对输出电压进行实时调节&#xff0c;实现了系统的稳态控制。仿真结果显示&#xff0c;该闭环控制系统能够快速响应负载变化&#xff0c;保持…

头戴式耳机性价比推荐有哪些?头戴式耳机性价比之王推荐

这不是马上就要双十一了&#xff1f;对于环境比较吵的人来说&#xff0c;趁着最近双11开始&#xff0c;是购买耳机的好时机。即将入冬&#xff0c;佩戴头戴式耳机频率越来越多&#xff0c;包裹着耳朵很舒适保暖。有的人入耳式已经非常多了&#xff0c;而且同样的价格&#xff0…

地图箭头方向检测系统源码分享

地图箭头方向检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

哈佛大学俩学生开发出一种 AI 眼镜,看你一眼就能扒光所有个人信息

最近&#xff0c;科幻场景中的一个设想变成了现实&#xff1a;一副眼镜能够一瞥便获取人的全部信息&#xff01; 两位来自哈佛大学的学生开发了一个名为I-XRAY的项目&#xff0c;该项目利用智能眼镜结合面部识别技术&#xff0c;能够实时分析并获取被捕捉者的个人信息。这些信…

ChatTTS使用demo示例(包含长文本生成语音、固定音色pt文件)

ChatTTS使用demo示例&#xff08;包含长文本生成语音、固定音色pt文件&#xff09; 一、配置开发环境 安装anaconda&#xff0c;安装参考文章&#xff1a;https://blog.csdn.net/Q_fairy/article/details/129158178 建议anaconda最新版&#xff1a;https://mirrors.tuna.tsi…

windows11下面使用Pyinstaller打包python程序

文章目录 一、安装Python二、安装pip三、通过pip安装pyinstaller四、使用pyinstaller打包python为二进制程序参考 一、安装Python 我这里直接下载的是Python的可执行程序包&#xff0c;打开即用的版本&#xff0c; 也可以按照以下的教程安装python工具到windows上面 &#…

海洋鱼类图像分类分割系统源码&数据集分享

海洋鱼类图像分类分割系统源码&#xff06;数据集分享 [yolov8-seg-slimneck&#xff06;yolov8-seg-attention等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Globa…

MySQL联合索引、索引下推Demo

1.联合索引 测试SQL语句如下&#xff1a;表test中共有4个字段(id, a, b, c)&#xff0c;id为主键 drop table test;#建表 create table test(id bigint primary key auto_increment,a int,b int,c int )#表中插入数据 insert into test(a, b, c) values(1,2,3),(2,3,4),(4,5,…

数据分析Power BI设置万为单位的数据

玩过Power BI的同学都知道&#xff0c;power BI在度量值设置单位里&#xff0c;唯独没有万这个单位&#xff0c;但是我们可以自定义&#xff0c;操作过程如下&#xff1a; 1.用DAX新建单位表 单位 SELECTCOLUMNS( { ( "元", 1), ("万",10000), ("千…

清华大学经管学院朱武祥教授:五步构建高效的数据飞轮,提升企业核心竞争力

面对AI时代的到来&#xff0c;企业应积极拥抱这一变革&#xff0c;构建和优化自身的数据飞轮&#xff0c;让飞轮高速转动起来&#xff0c;为企业的创新发展持续赋能。 近期&#xff0c;清华管理评论发表了一篇名为《AI时代如何构建数据飞轮》的文章&#xff0c;引起了我们的重点…

Java 根据字符生成背景透明的图片

上代码 package com.example.demotest.controller;/*** Author shaolin* Date 2024-10-08 10:11**/import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.WritableRaster; impor…