【Android】实现图片和视频混合轮播(无限循环、视频自动播放)

目录

  • 前言
  • 一、实现效果
  • 二、具体实现
    • 1. 导入依赖
    • 2. 布局
    • 3. Banner基础配置
    • 4. Banner无限循环机制
    • 5. 轮播适配器
    • 6. 视频播放处理
    • 7. 完整源码
  • 总结

前言

我们日常的需求基本上都是图片的轮播,而在一些特殊需求,例如用于展览的的数据大屏,又想展示图片又想展示视频,本文将利用第三方库com.youth.play.banner轮播控件实现图片和视频混合轮播的效果,自动+手动滑动,无限循环,视频自动播放。

其中图片使用Glide图片加载库,视频使用GSYVideoPlayer播放器。

第三方库移步:【Android】常用的第三方开源库汇总


一、实现效果

在这里插入图片描述在这里插入图片描述

二、具体实现

1. 导入依赖

implementation 'com.youth.banner:banner:2.1.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'com.shuyu:GSYVideoPlayer:7.1.8'

2. 布局

主页面:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <com.youth.banner.Banner
        android:id="@+id/banner"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_margin="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
        
</androidx.constraintlayout.widget.ConstraintLayout>

这里没有使用库里面写好的指示器Indicator,需要的可以自行查看文档使用:Banner。

3. Banner基础配置


// 添加生命周期管理,确保在适当的生命周期内开始和停止轮播
banner.addBannerLifecycleObserver(this)
    .setAdapter(adapter,true) //是否开启无限循环
    .setLoopTime(loopTime) //轮播时间
    
// 设置轮播图的点击事件监听器
banner.addOnPageChangeListener(object :
    OnPageChangeListener {
    /**
     * 滑动中的监听,当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法会一直得到调用
     */
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
    }

    /**
     * 监听滑动到对应索引值的页面,第一个页面不执行
     */
    override fun onPageSelected(position: Int) {
    }
    /**
     * 滑动状态监听
     */
    override fun onPageScrollStateChanged(state: Int) {
    }
})

Banner支持图片的无限循环,可以选择性打开。我们需要在其中插入视频的播放,就需要先了解其无限循环的处理机制。

4. Banner无限循环机制

无限循环主要有两种实现,一种是设置页面总数设置得很大,一直滑不到底,这不是真正的无限循环,是一种伪循环,性能较低。另外一种就是接下来要说的,Banner的无限循环,它是通过ViewPage2去实现的,在原有页面的基础上左右各添加一个页面,滑动到最后一页跳转到第二页,滑动到第一页跳转到倒数第二页。

在这里插入图片描述

这里为什么要多两个页面呢?一个目的是为了滑动到中间时,有一半是原页面,有一半是新页面。第二个目的是为了有滑动的效果,不会出现跳动。

首先,ViewPage2有三个监听方法:

onPageScrolled :滑动中的监听,当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法会一直得到调用
onPageSelected :滑动或移动(左右滑动、点击指示器跳转)到某一页面时的监听,第一个页面不执行
onPageScrollStateChanged,滑动状态的监听

按理说,在滑动到最后一页时,我们只需要在 onPageSelected 中使用 setCurrentItem 跳转到第一页(总页数的第二页)即可,但是 onPageSelected 在滑动动画还没结束的时候就已经被调用了,此时调用 setCurrentItem 会强制取消当前正在进行的滑动动画并跳转,导致页面跳动不平滑。

所以可以这样去解决这一问题:
(1)先在 onPageSelected 中记录被选中的页面
(2)再在 onPageScrollStateChanged 判断当前动画是否结束,当动画结束时再去调用setCurrentItem方法跳转

我们查看Banner里面的源码也是这样处理无限循环的:
在这里插入图片描述

这里需要格外注意页面的下标,一个页面有两种下标,一个真实下标 realPosition,一个物理下标 position。同理,页面总数量、根据下标获取数据也是一样的逻辑。
在这里插入图片描述
以上面的图为例,下标如下

页面positionrealPosition
第一页02(最后一页)
第二页10
第三页21
第四页32
最后一页40 (第一页)
    /**
     * 获取真正的位置
     *
     * @param isIncrease 首尾是否有增加
     * @param position  当前位置
     * @param realCount 真实数量
     * @return
     */
    public static int getRealPosition(boolean isIncrease, int position, int realCount) {
        if (!isIncrease) {
            return position;
        }
        int realPosition;
        if (position == 0) {
            realPosition = realCount - 1;
        } else if (position == realCount + 1) {
            realPosition = 0;
        } else {
            realPosition = position - 1;
        }
        return realPosition;
    }

5. 轮播适配器

//轮播数据
private val resourcesList by lazy{
        arrayListOf<PlayResourcesBean>().apply {
            add(PlayResourcesBean(isImg = true, title = "图片", url = "https://upload-images.jianshu.io/upload_images/5809200-a99419bb94924e6d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://s10.mogucdn.com/mlcdn/c45406/170831_479g0ifl6f2i313feb5ech46kek21_778x440.jpg"))
            add(PlayResourcesBean(isImg = false, title = "视频",url = "https://media.w3.org/2010/05/sintel/trailer.mp4"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://s10.mogucdn.com/mlcdn/c45406/170831_7gee6d620i774ec3l5bfh55cfaeab_778x440.jpg"))
            add(PlayResourcesBean(isImg = false,title = "视频",url = "https://media.w3.org/2010/05/sintel/trailer.mp4"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://s10.mogucdn.com/mlcdn/c45406/170829_59ia6fd99ghkdkd9603kblha21h5b_778x440.jpg"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://img.zcool.cn/community/01233056fb62fe32f875a9447400e1.jpg"))
            add(PlayResourcesBean(isImg = false,title = "视频",url = "https://media.w3.org/2010/05/sintel/trailer.mp4"))
        }
    }

private val adapter by lazy{
    PlayAdapter(this,resourcesList, this)
}


@Keep
data class PlayResourcesBean(
    /**
     * 图片或者视频路径
     */
    val url: String? = "",
    val isImg:Boolean,
    val title: String?=""
)

PlayAdapter需要继承BannerAdapter,处理两种不同类型的item(图片item和视频item)

class PlayAdapter(private val mContext: Context, private val dataList: ArrayList<PlayResourcesBean>,
                  private val mActivity: MainActivity
):
    BannerAdapter<PlayResourcesBean, RecyclerView.ViewHolder>(dataList) {

    private val mVHMap = HashMap<Int,RecyclerView.ViewHolder>()

    private val options by lazy {
        RequestOptions().priority(Priority.HIGH)
            .placeholder(R.mipmap.default_ic)
            .error(R.mipmap.default_ic)
    }

    companion object{
       //视频
       const val VIDEO = 1
        //图片
        const val IMAGE = 2
    }

    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int,
        payloads: MutableList<Any>
    ) {
        super.onBindViewHolder(holder, position, payloads)

        val realPosition = getRealPosition(position)
        if (holder is VideoHolder){
            setVideo( holder, getData(realPosition),position,realPosition,realCount)
            mVHMap[position] = holder
        }else if(holder is ImageHolder){
            setImage(holder,getData(realPosition),realPosition,realCount)
            mVHMap[position] = holder
        }
    }

    override fun getItemViewType(position: Int): Int {
        //这里的position不是真实的坐标,获取数据需要转换
        return if (getData(getRealPosition(position)).isImg){
            IMAGE
        }else {
            VIDEO
        }
    }

    override fun onCreateHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
        val holder:RecyclerView.ViewHolder
        val from = LayoutInflater.from(mContext)
        if (viewType==VIDEO){
            val view=from.inflate(R.layout.play_item_video,parent,false)
            holder=VideoHolder(view)
        }else {
            val view=from.inflate(R.layout.play_item_image,parent,false)
            holder=ImageHolder(view)
        }

        return holder
    }

    private fun setImage(
        holder: ImageHolder,
        data: PlayResourcesBean,
        position: Int,
        size: Int
    ) {
        Glide.with(mContext)
            .load(data.url)
            .apply(options)
            .into(holder.image)

        holder.title.text=data.title?:""
        holder.numIndicator.text="${position+1}/$size"
    }

    private fun setVideo(
        holder: VideoHolder,
        data: PlayResourcesBean,
        position: Int,
        realPosition: Int,
        size: Int
    ) {
        holder.video.apply {
            setUp(data.url,true,mContext.externalCacheDir,null,"")
            titleTextView.visibility = View.GONE
            backButton.visibility = View.GONE
            fullscreenButton.visibility = View.GONE
            startButton.visibility = View.GONE


            //音频焦点冲突时是否释放
            isReleaseWhenLossAudio = true
            //禁止全屏
            isAutoFullWithSize = false
            //isStartAfterPrepared=true
            dismissControlTime=0
            isClickable=false
            isEnabled=false
            isLongClickable=false
            playPosition=position


            //禁止滑动
            setIsTouchWiget(false)
            setVideoAllCallBack(object :GSYSampleCallBack(){
                override fun onComplete(url: String?, vararg objects: Any?) {
                    super.onComplete(url, *objects)
                    mActivity.banner.isAutoLoop(true)
                    mActivity.banner.start()
                }

                override fun onPlayError(url: String?, vararg objects: Any?) {
                    super.onPlayError(url, *objects)
                    mActivity.banner.isAutoLoop(true)
                    mActivity.banner.start()
                }

                override fun onClickStop(url: String?, vararg objects: Any?) {
                    super.onClickStop(url, *objects)
                    mActivity.banner.isAutoLoop(true)
                    mActivity.banner.start()
                }

                override fun onPrepared(url: String?, vararg objects: Any?) {
                    super.onPrepared(url, *objects)

                }

                override fun onStartPrepared(url: String?, vararg objects: Any?) {
                    super.onStartPrepared(url, *objects)

                }
                override fun onAutoComplete(url: String?, vararg objects: Any?) {
                    super.onAutoComplete(url, *objects)
                    mActivity.banner.isAutoLoop(true)

                    //快速轮播到下一页
                    mActivity.banner.setLoopTime(100)
                    mActivity.banner.start()
                    mActivity.banner.setLoopTime(mActivity.loopTime)
                }
            })
        }

        holder.title.text=data.title?:""
        holder.numIndicator.text="${realPosition+1}/$size"

    }

    internal class VideoHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val video: MyVideoPlayer
        val title: TextView
        val numIndicator:TextView
        init {
            video = itemView.findViewById(R.id.banner_vp)
            title = itemView.findViewById(R.id.video_title)
            numIndicator = itemView.findViewById(R.id.video_numIndicator)
        }
    }

    internal class ImageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val image: ImageView
        val title: TextView
        val numIndicator:TextView
        init {
            image = itemView.findViewById(R.id.banner_image)
            title = itemView.findViewById(R.id.image_title)
            numIndicator = itemView.findViewById(R.id.image_numIndicator)
        }
    }

    fun getVHMap(): HashMap<Int,RecyclerView.ViewHolder> {
        return mVHMap
    }

    override fun onBindView(
        holder: RecyclerView.ViewHolder?,
        data: PlayResourcesBean?,
        position: Int,
        size: Int
    ) {
    }


}

这里需要用一个HashMap去存储所有ViewHolder,以便后续控制视频自动播放,这里的key是物理下标,一个key对应一个页面,如果用realPosition作为key,不是一对一,容易产生value更新不及时。

6. 视频播放处理

GSYVideoPlayer 播放器的使用可以查看文档:GSYVideoPlayer

无限循环解决之后,就需要在轮播中自动播放视频,隐藏和关闭掉各种手动按钮和事件,需要对播放器进行改造(继承StandardGSYVideoPlayer),我是在翻看了它许多源码和github上的issues才得以解决。

class MyVideoPlayer(context: Context, attrs: AttributeSet?) : StandardGSYVideoPlayer(context,attrs){


    override fun changeUiToPreparingShow() {
        super.changeUiToPreparingShow()
        hide()
    }

    override fun changeUiToPauseShow() {
        super.changeUiToPauseShow()
        hide()
    }

    override fun changeUiToError() {
        super.changeUiToError()
        hide()
    }

    override fun changeUiToCompleteShow() {
        super.changeUiToCompleteShow()
        hide()
    }

    override fun changeUiToPlayingBufferingShow() {
        super.changeUiToPlayingBufferingShow()
        hide()
    }

    override fun changeUiToPrepareingClear() {
        super.changeUiToPrepareingClear()
        hide()
    }

    override fun changeUiToPlayingClear() {
        super.changeUiToPlayingClear()
        hide()
    }

    override fun changeUiToPauseClear() {
        super.changeUiToPauseClear()
        hide()
    }

    override fun changeUiToPlayingBufferingClear() {
        super.changeUiToPlayingBufferingClear()
        hide()
    }

    override fun changeUiToClear() {
        super.changeUiToClear()
        hide()
    }

    override fun changeUiToCompleteClear() {
        super.changeUiToCompleteClear()
        hide()
    }

    override fun changeUiToPlayingShow() {
        super.changeUiToPlayingShow()
        hide()

    }

    override fun changeUiToNormal() {
        super.changeUiToNormal()
        hide()
    }
    private fun hide() {
        setViewShowState(mTopContainer, INVISIBLE)
        setViewShowState(mBottomContainer, INVISIBLE)
        setViewShowState(mStartButton, INVISIBLE)
        setViewShowState(mLoadingProgressBar, INVISIBLE)
        setViewShowState(mThumbImageViewLayout, INVISIBLE)
        setViewShowState(mBottomProgressBar, INVISIBLE)
        setViewShowState(mLockScreen, GONE)
    }


    override fun onVideoPause() {
        super.onVideoPause()
        mPauseBeforePrepared=false //是否准备完成前调用了暂停,避免再次暂停
    }
    override fun touchDoubleUp() {
        //双击会暂停,重载清除
    }
}

StandardGSYVideoPlayer是GSYVideoPlayer中的一个标准样式的播放器,在视频准备、暂停、播放完成等等状态隐藏和关闭掉各种手动按钮和事件,设置mPauseBeforePrepared避免视频准备完成后自动暂停,重载touchDoubleUp清除掉原有的双击屏幕暂停视频的效果。

视频自动播放
接下来就是主要逻辑了,这里有几个关键点:

在轮播到视频时,将自动轮播关闭,同时播放视频
在视频播放错误、非正常完成时,暂停视频,同时继续轮播
在视频播放正常完成、手动滑动到下一页时,立即轮播到下一页

首先,就需要在MyVideoPlayer适配器中处理视频控件的各种监听事件(查看源码视频有各种状态):

setVideoAllCallBack(object :GSYSampleCallBack(){
    override fun onComplete(url: String?, vararg objects: Any?) {
        super.onComplete(url, *objects)
        mActivity.banner.isAutoLoop(true)
        mActivity.banner.start()
    }

    override fun onPlayError(url: String?, vararg objects: Any?) {
        super.onPlayError(url, *objects)
        mActivity.banner.isAutoLoop(true)
        mActivity.banner.start()
    }

    override fun onClickStop(url: String?, vararg objects: Any?) {
        super.onClickStop(url, *objects)
        mActivity.banner.isAutoLoop(true)
        mActivity.banner.start()
    }

    override fun onPrepared(url: String?, vararg objects: Any?) {
        super.onPrepared(url, *objects)

    }

    override fun onStartPrepared(url: String?, vararg objects: Any?) {
        super.onStartPrepared(url, *objects)

    }
    override fun onAutoComplete(url: String?, vararg objects: Any?) {
        super.onAutoComplete(url, *objects)
        mActivity.banner.isAutoLoop(true)

        //设置轮播时间,立即轮播到下一页
        mActivity.banner.setLoopTime(100)
        mActivity.banner.start()
        mActivity.banner.setLoopTime(mActivity.loopTime)
    }
})

其次,在Activity中设置任务,在页面切换时通过之前保存的ViewHolder去执行视频播放或者暂停的任务:

private val taskHandler by lazy{
	Handler(Looper.getMainLooper())
}
val task = Runnable { 
    //可能是首尾切换页,两个页面循环跳转
    if(adapter.getVHMap().containsKey(currentPos)){
        if(adapter.getVHMap()[currentPos] is PlayAdapter.VideoHolder){
            val holder = (adapter.getVHMap()[currentPos] as PlayAdapter.VideoHolder)
            GSYVideoManager.onPause()
            holder.video.startPlayLogic()
            banner.isAutoLoop(true)
            banner.stop()
            banner.isAutoLoop(false)
        }else{
            GSYVideoManager.onPause()

            banner.isAutoLoop(true)
            banner.start()
        }
    }else{
        GSYVideoManager.onPause()
        banner.isAutoLoop(true)
        banner.start()
    }
}

// 设置轮播图的点击事件监听器
banner.addOnPageChangeListener(object :
    OnPageChangeListener {

    /**
     * 滑动中的监听,当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法会一直得到调用
     */
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {

    }

    /**
     * 监听滑动到对应索引值的页面,第一个页面不执行
     */
    override fun onPageSelected(position: Int) {
        if (position == banner.realCount - 1) {
            currentPos=banner.realCount
        } else if (position == 0) {
            currentPos = 1
        } else {
            currentPos = position + 1
        }
    }
    /**
     * 滑动状态监听
     */
    override fun onPageScrollStateChanged(state: Int) {
        // Banner跳转之后再去控制视频播放
        if (state == ViewPager2.SCROLL_STATE_IDLE) {
            if (banner.isInfiniteLoop) {
                taskHandler.post(task)
            }
        }
    }
})

任务一定要在onPageScrollStateChanged中去执行,这时候特殊的两个页面已经跳转完成了,只会执行一次任务,否则执行两个异步任务会导致一个视频播放一个视频暂停,两个异步没有固定的执行顺序,出现视频没有画面但是有声音的问题。

完整的Activity代码:

class MainActivity:AppCompatActivity() {
    val loopTime=6000L

    //列表+前后两页过渡页的坐标
    private var currentPos= 1

    private val taskHandler by lazy{
        Handler(Looper.getMainLooper())
    }

    private val adapter by lazy{
        PlayAdapter(this,resourcesList, this)
    }

    private val resourcesList by lazy{
        arrayListOf<PlayResourcesBean>().apply {
            add(PlayResourcesBean(isImg = true, title = "图片", url = "https://upload-images.jianshu.io/upload_images/5809200-a99419bb94924e6d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://s10.mogucdn.com/mlcdn/c45406/170831_479g0ifl6f2i313feb5ech46kek21_778x440.jpg"))
            add(PlayResourcesBean(isImg = false, title = "视频",url = "https://media.w3.org/2010/05/sintel/trailer.mp4"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://s10.mogucdn.com/mlcdn/c45406/170831_7gee6d620i774ec3l5bfh55cfaeab_778x440.jpg"))
            add(PlayResourcesBean(isImg = false,title = "视频",url = "https://media.w3.org/2010/05/sintel/trailer.mp4"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://s10.mogucdn.com/mlcdn/c45406/170829_59ia6fd99ghkdkd9603kblha21h5b_778x440.jpg"))
            add(PlayResourcesBean(isImg = true,title = "图片",url = "https://img.zcool.cn/community/01233056fb62fe32f875a9447400e1.jpg"))
            add(PlayResourcesBean(isImg = false,title = "视频",url = "https://media.w3.org/2010/05/sintel/trailer.mp4"))
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initBanner()
    }

    private fun initBanner() {

        // 添加生命周期管理,确保在适当的生命周期内开始和停止轮播
        banner.addBannerLifecycleObserver(this)
            .setAdapter(adapter,true) //是否开启无限循环
            .setLoopTime(loopTime) //轮播时间


        // 设置轮播图的点击事件监听器
        banner.addOnPageChangeListener(object :
            OnPageChangeListener {

            /**
             * 滑动中的监听,当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法会一直得到调用
             */
            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {

            }

            /**
             * 监听滑动到对应索引值的页面,第一个页面不执行
             */
            override fun onPageSelected(position: Int) {
                if (position == banner.realCount - 1) {
                    currentPos=banner.realCount
                } else if (position == 0) {
                    currentPos = 1
                } else {
                    currentPos = position + 1
                }
            }
            /**
             * 滑动状态监听
             */
            override fun onPageScrollStateChanged(state: Int) {
                // Banner跳转之后再去控制视频播放
                if (state == ViewPager2.SCROLL_STATE_IDLE) {
                    if (banner.isInfiniteLoop) {
                        taskHandler.post(task)
                    }
                }
            }
        })

        //重新设置banner数据
        //banner.setDatas(resourcesList)

    }


    val task = Runnable {
        //可能是首尾切换页,两个页面循环跳转
        if(adapter.getVHMap().containsKey(currentPos)){
            if(adapter.getVHMap()[currentPos] is PlayAdapter.VideoHolder){
                val holder = (adapter.getVHMap()[currentPos] as PlayAdapter.VideoHolder)
                GSYVideoManager.onPause()
                holder.video.startPlayLogic()
                banner.isAutoLoop(true)
                banner.stop()
                banner.isAutoLoop(false)
            }else{
                GSYVideoManager.onPause()

                banner.isAutoLoop(true)
                banner.start()
            }
        }else{
            GSYVideoManager.onPause()
            banner.isAutoLoop(true)
            banner.start()
        }
    }

    override fun onResume() {
        super.onResume()
        taskHandler.post(task)
    }


    override fun onPause() {
        super.onPause()
        GSYVideoManager.onPause()
    }
    override fun onDestroy() {
        super.onDestroy()
        GSYVideoManager.releaseAllVideos()
        //移除数据绑定,否则第二次设置适配器出错
        banner.destroy()
    }

}

7. 完整源码

到这就完结撒花了🎉,完整代码我已上传github,需要的可以自行pull:https://github.com/FullCourage/BannerPlayer


总结

这个项目让我深刻体会到源码的重要性,解决jar包的一些底层问题,不仅要知道原理,还要学会从源码中了解它的执行逻辑。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

三维点云目标识别对抗攻击研究综述

源自&#xff1a;电子与信息学报 作者&#xff1a;刘伟权 郑世均 郭宇 王程 注&#xff1a;若出现无法显示完全的情况&#xff0c;可 V 搜索“人工智能技术与咨询”查看完整文章 摘 要 当前&#xff0c;人工智能系统在诸多领域都取得了巨大的成功&#xff0c;其中深度学…

云原生架构:未来应用程序设计和部署的革新

目录 前言1. 云原生架构的概述1.1 什么是云原生架构1.2 云原生架构的核心理念 2. 云原生架构的核心特征2.1 容器化应用2.2 微服务架构2.3 自动化管理 3. 云原生架构的优势3.1 弹性和可伸缩性3.2 高可用性和容错性3.3 快速交付和持续部署 4. 实施云原生架构的关键技术4.1 容器编…

对比A100和4090:两者的区别以及适用点

自2022年年末英伟达发布4090芯片以来&#xff0c;这款产品凭借着其优异的性能迅速在科技界占据了一席之地。现如今&#xff0c;不论是在游戏体验、内容创作能力方面还是模型精度提升方面&#xff0c;4090都是一个绕不过去的名字。而A100作为早些发布的产品&#xff0c;其优异的…

【高性能计算笔记】

第1章 - 高性能计算介绍 1. 概念&#xff1a; 高性能计算(High performance computing&#xff0c;缩写HPC)&#xff1a; 指通常使用很多处理器&#xff08;作为单个机器的一部分&#xff09;或者某一集群中组织的几台计算机&#xff08;作为单个计算资源操作&#xff09;的…

宝宝早教电子图书 酷得电子方案

宝宝早教发声书是一种专为婴幼儿设计的图书&#xff0c;旨在通过有趣的图画和声音来吸引宝宝的注意力&#xff0c;帮助他们学习语言、认知和发展各种技能。这类书籍通常包括以下特点&#xff1a; 鲜艳的图画&#xff1a;发声书通常配有色彩鲜艳、形象生动的图画&#xff0c;以…

Linux安装minio及mc客户端(包含ARM处理器架构)

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

【44 Pandas+Pyecharts | 全国海底捞门店数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 查看描述信息 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 各省海底捞门店数量分布柱状图3.2 各省海底捞门店数量分布地…

西南地区某大型钢厂蓝鹏测控又一组测径仪设备投入交付使用

近日&#xff0c;蓝鹏测控为西南地区某大型钢铁厂定制生产的又一台测径仪完成交付安装, 并通过了现场调试验收。这些智能测径仪被广泛应用于各种轧钢生产线&#xff0c;用于检测不同规格的圆棒圆管钢材等。这些设备能够精确测量棒材管材的外径、椭圆度、可以实时显示最大直径, …

os7安装gitlab

gitlab安装要求&#xff1a;os7以上版本&#xff0c;4G内存&#xff0c;磁盘50GB 1.克隆 由于我这里不想影响原来的&#xff0c;所以这里克隆一个os系统。如果其他是第一次安装则不用。 2.修改ip地址 cd /etc/sysconfig/network-scriptsvi ifcfg-ens33 按&#xff1a;insert…

跟《经济学人》学英文:2024年6月22日这期 Think Nvidia looks dear?

Think Nvidia looks dear? American shares could get pricier still Investors are willing to follow whichever narrative paints the rosiest picture 觉得Nvidia看起来很贵&#xff1f;美国股票可能会变得更贵 投资者愿意追随任何一个描绘出最乐观的故事 dear&#x…

一键登录功能实现(采用极光SDK)

前端流程 1. 引入极光认证 SDK&#xff1a; 通过 <script> 标签引入&#xff0c;在 public/index.html 中确认 SDK 脚本已正确加载&#xff1a;参考官网Web SDK 概述 - 极光文档 <!-- 引入极光认证 SDK --> <script type"text/javascript" src&quo…

【权威发布】2024年文化、设计与社会科学国际会议(ICCDSS 2024)

2024年文化、设计与社会科学国际会议 2024 International Conference on Culture, Design, and Social Sciences 会议简介 2024年文化、设计与社会科学国际会议旨在为全球范围内的专家学者提供一个交流文化、设计与社会科学研究成果的平台。会议将围绕文化、设计与社会科学的前…

尴尬时刻:如何在忘记名字时巧妙应对

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

你还在手动操作仓库?这款 CLI 工具让你效率飙升300%!

前言 作为一名开发者&#xff0c;我经常会在 GitHub 和 Gitee 上 fork 各种项目。时间一长&#xff0c;这些仓库就会堆积如山&#xff0c;变成了“垃圾仓库”。每次打开代码托管平台&#xff0c;看到那些不再需要的仓库&#xff0c;我的强迫症就会发作。手动一个一个删除这些仓…

【软件下载】Camtasia Studio 2024详细安装教程视频

习惯上来说Camtasia Studio是一款简单易用的高清录屏和视频编辑软件&#xff0c;拥有录制屏幕和配音、视频的剪辑和过场动画片、添加说明字幕和水印、制作视频封面和菜单、视频压缩和播放。不得不说Camtasia是一款屏幕录制和视频剪辑软件&#xff0c;教授课程&#xff0c;培训他…

Twinkle Tray:屏幕亮度控制更智能

名人说&#xff1a;一点浩然气&#xff0c;千里快哉风。 ——苏轼 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍1、Twinkle Tray2、核心特点 二、下载安装1、下载2、安装 三、使用方法 很高兴你打开…

实用软件下载:CrossOver 2024最新安装包及详细安装教程

​根据软件大数据显示上传或者手动输入软件都非常简单&#xff0c;一般来说CrossOver会自动连接到一个Win文件共享服务器&#xff08;Samba或CIFS&#xff09;上&#xff0c;使用者能够直接在这个服务器中选择并上传软件执行文件。实际上我们可以这样讲调整CrossOver设置&#…

PingCAP 再度入选“中国独角兽企业”,数据库领域的先锋力量

6月16日&#xff0c;2024中国&#xff08;重庆&#xff09;独角兽企业大会上&#xff0c;长城战略咨询发布了《中国独角兽企业研究报告2024》。 2023年&#xff0c;中国独角兽企业共375家&#xff0c;大数据赛道共5家。 估值排序企业名称2023年估值&#xff08;亿美元&#xff…

dp经典问题:LCS问题

dp&#xff1a;LCS问题 最长公共子序列&#xff08;Longest Common Subsequence, LCS&#xff09;问题 是寻找两个字符串中最长的子序列&#xff0c;使得这个子序列在两个字符串中出现的相对顺序保持一致&#xff0c;但不要求连续。 力扣原题链接 1.定义 给定两个字符串 S1…

猫狗识别—视频识别

猫狗识别—视频识别 1. 导入所需的库&#xff1a;2. 创建Tkinter主窗口并设置标题&#xff1a;3. 设置窗口的宽度和高度&#xff1a;4. 创建一个Canvas&#xff0c;它将用于显示视频帧&#xff1a;5. 初始化一个视频流变量cap&#xff0c;用于存储OpenCV的视频捕获对象&#xf…