Android笔记(十三):结合JetPack Compose和CameraX实现视频的录制和存储

news2025/1/22 21:33:52

在“Android笔记(八):基于CameraX库结合Compose和传统视图组件PreviewView实现照相机画面预览和照相功能”,文中介绍了拍照功能的实现,在本文中将介绍结合JetPack Compose和CameraX实现视频的录制。
新建一个项目
在这里插入图片描述
在项目中做如下处理:

一、增视频录制的相关依赖库

在项目的模块对应的build.gradle.kt中增加如下的依赖库:
增加CameraX相关库

  val camerax_version = "1.3.0-alpha04"
  implementation("androidx.camera:camera-core:$camerax_version")
  implementation("androidx.camera:camera-camera2:${camerax_version}")
  implementation("androidx.camera:camera-lifecycle:${camerax_version}")
  implementation("androidx.camera:camera-video:${camerax_version}")

  implementation("androidx.camera:camera-view:${camerax_version}" )
  implementation("androidx.camera:camera-extensions:${camerax_version}")

增加Material3中扩展的图标库

  val icon_extended_version = "1.5.4"
  implementation("androidx.compose.material:material-icons-extended:$icon_extended_version")

注意:因为在前面展示的Android移动应用中使用的图标可选有限,因此增加“androidx.compose.material:material-icons-extended”图标扩展库,丰富图标的选择。

二、在AndroidManifest.xml权限设置使用照相机

  <uses-feature android:name="android.hardware.camera"  android:required="false" />

  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />

三、CameraX视频捕获架构

视频录制需要做两个方面的处理,需要录制视频流和同时录制音频流。然后对音频流和视频流进行压缩处理,最终写入到磁盘保存起来。在图1展示的是这样工作的示意图。
在这里插入图片描述
图1
LifecycleCameraController
CameraX提供了LifecycleCameraController。LifecycleCameraController提供了CameraX的大部分的特性,它是一个高级的控制器类提供了CameraX的核心特性,用于处理照相机的初始化、创建和配置用例(这里的用例提供了将用例参数映射到相机的可用参数的功能,具体这些用例是指:拍照用例:CameraController.IMAGE_CAPTURE、 图像分析用例:CameraController.IMAGE_ANALYSIS、视频录制用例:CameraController.VIDEO_CAPTURE,…),并将它们绑定到一个生命周期拥有者对象。LifecycleCamera监听设备的Motion Sensor(运动感应器),设置目标的旋转参数。

val cameraController: LifecycleCameraController
= LifecycleCameraController(applicationContext).apply {
setEnabledUseCases(CameraController.VIDEO_CAPTURE) //设置用例
}

CameraSelector
CameraSelector用于选择摄像头,具体选择包括:

使用 CameraSelector.DEFAULT_FRONT_CAMERA 请求默认的前置摄像头。
使用 CameraSelector.DEFAULT_BACK_CAMERA 请求默认的后置摄像头。
使用CameraSelector.Builder.addCameraFilter() 按 CameraCharacteristics 过滤可用设备列表。

Recording
Recording是实际上执行录制视频的对象,在活动录制视频时,提供暂停、恢复或停止录制的控件。如果在录制的过程中发生错误,则会初始化一个VideoRecordEvent.Finalized状态,所有的控制将进入空操作。
//创建Recording对象并启动视频录制
val recording = cameraController.startRecording(…)
在这里:startRecording根据录制得到的视频存储处理方式不同有三种形式:
(1)写入到文件

public Recording startRecording(
@NonNull FileOutputOptions outputOptions, //写入到文件的输出参数
@NonNull AudioConfig audioConfig, //音频的配置
@NonNull Executor executor,//将在其上运行事件侦听器的线程池
@NonNull Consumer listener)//处理视频录制的事件监听器

(2)根据文件描述写入到文件

public Recording startRecording(
@NonNull FileDescriptorOutputOptions outputOptions,//写入到文件描述的输出参数
@NonNull AudioConfig audioConfig,//音频的配置
@NonNull Executor executor,//将在其上运行事件侦听器的线程池
@NonNull Consumer listener)//处理视频录制的事件监听器

(3)写入到媒体库

public Recording startRecording(
@NonNull MediaStoreOutputOptions outputOptions,//写入到媒体库的输出参数
@NonNull AudioConfig audioConfig,//音频的配置
@NonNull Executor executor,//将在其上运行事件侦听器的线程池
@NonNull Consumer listener)//处理视频录制的事件监听器

四、结合Compose和CameraX实现视频的录制实例

1.在主活动中设置权限处理

class MainActivity : ComponentActivity() {
    companion object{
        //定义权限数组
        val CAMERAX_PERMISSIONS = arrayOf(
            android.Manifest.permission.CAMERA,			//请求相机
            android.Manifest.permission.RECORD_AUDIO)	//请求录制音频
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        //检查权限,若无就请求权限
        handlePermissions()
        
        setContent {
            val cameraController: LifecycleCameraController = remember {
                LifecycleCameraController(applicationContext).apply {
                    setEnabledUseCases(CameraController.VIDEO_CAPTURE)
                }
            }
            VideoCaptureAppTheme {
                MainScreen(cameraController)
            }
        }
    }

    /**
     * 检查权限
     * @return Boolean
     */
    private fun hasRequiredPermissions():Boolean = CAMERAX_PERMISSIONS.all{
        ContextCompat.checkSelfPermission(
            applicationContext,
            it) == PackageManager.PERMISSION_GRANTED
    }

    /**
     * Handle permissions
     * 处理权限的操作
     */
    private fun handlePermissions(){
        if(!hasRequiredPermissions()){
            //请求权限
            ActivityCompat.requestPermissions(this, CAMERAX_PERMISSIONS,0)
        }
    }
}

运行结果如图2所示:
在这里插入图片描述
图2

2. 定义视频预览界面定义

利用view视图组件PreviewView来定义视频预览的界面,同时处理将LifecycleCameraController对象与当前的生命周期对象进行绑定。由当前生命周期拥有者LocalLifecycleOwner.current的生命周期lifecycle的状态将决定摄像机何时打开、启动、停止和关闭。

@Composable
fun VideoPreview(cameraController:LifecycleCameraController,
                 modifier: Modifier = Modifier){
    val lifecycleOwner = LocalLifecycleOwner.current
    AndroidView(
        factory={context: Context ->
             //预览视图
             PreviewView(context).apply{
                 this.controller = cameraController
                 cameraController.bindToLifecycle(lifecycleOwner)
             }
        },
        modifier = modifier
    )
}

3. 定义主界面设置摄像头前后镜头切换

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(cameraController: LifecycleCameraController){
    //设置前后摄像头的图标状态
    var cameraIcon by remember{mutableStateOf(Icons.Default.VideoCameraBack)}
    Scaffold {
        Box(modifier = Modifier.fillMaxSize().padding(it)) {
            //视频预览的界面
            VideoPreview(cameraController = cameraController, modifier = Modifier.fillMaxSize())
            //左上角的摄像头前后镜图标
            IconButton(onClick = {
                //照相机前后摄像头的切换
                    if (cameraController.cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) {
                        //设置后镜头
                        cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
                        cameraIcon = Icons.Default.VideoCameraBack
                    } else {
                        //设置前镜头
                        cameraController.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
                        cameraIcon=Icons.Default.VideoCameraFront
                    }
            }, modifier = Modifier.offset(16.dp, 16.dp)) {
                Icon(
                    imageVector = cameraIcon,
                    tint = Color.Green,
                    contentDescription = "摄像头"
                )
            }
        }
    }
}

运行结果如图3所示:
在这里插入图片描述
图3

4.在主活动中增加录制视频的处理

(1)将录制的视频保存到文件中

下列代码将录制视频采用下列录制的方式

public Recording startRecording(
@NonNull FileOutputOptions outputOptions, //写入到文件的输出参数
@NonNull AudioConfig audioConfig, //音频的配置
@NonNull Executor executor,//将在其上运行事件侦听器的线程池
@NonNull Consumer listener)//处理视频录制的事件监听器

这样的视频文件就会保存到当前应用包下的文件目录中,具体代码如下所示:

class MainActivity : ComponentActivity() {
    private var recording: Recording? = null

    companion object{
        val CAMERAX_PERMISSIONS = arrayOf(
            android.Manifest.permission.CAMERA,//请求相机
            android.Manifest.permission.RECORD_AUDIO)//请求录制音频
    }
    @SuppressLint("UnsafeOptInUsageError")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //检查权限,若无就请求权限
        handlePermissions()

        setContent {
            val cameraController: LifecycleCameraController = remember {
                LifecycleCameraController(applicationContext).apply {
                    setEnabledUseCases(CameraController.VIDEO_CAPTURE)
                }
            }
            VideoCaptureAppTheme {
                MainScreen(cameraController,this::recordVideo)
            }
        }
    }

    /**
     * 检查权限
     * @return Boolean
     */
    private fun hasRequiredPermissions():Boolean = CAMERAX_PERMISSIONS.all{
        ContextCompat.checkSelfPermission(
            applicationContext,
            it) == PackageManager.PERMISSION_GRANTED
    }

    /**
     * Request permissions
     * 处理权限的操作
     */
    private fun handlePermissions(){
        if(!hasRequiredPermissions()){
            //请求权限
            ActivityCompat.requestPermissions(this, CAMERAX_PERMISSIONS,0)
        }
    }

    /**
     * 录制视频
     * @param cmaeraController LifecycleCameraController
     */
    @SuppressLint("MissingPermission", "UnsafeOptInUsageError")
    private fun recordVideo(cameraController:LifecycleCameraController){
        if(recording!=null){
            //停止录制视频
            recording?.stop()
            recording = null
            return
        }

        if(!hasRequiredPermissions())//没有访问权限
            return

        val outputFile = File(filesDir,"video.mp4")
        //执行视频录制
        recording = cameraController.startRecording(
            //输出文件配置
            FileOutputOptions.Builder(outputFile).build(),
            //启动语音
            AudioConfig.create(true),
            //在当前应用的上下文中创建事件监听器依附运行的线程池
            ContextCompat.getMainExecutor(applicationContext)
        ){event:VideoRecordEvent->
            //监听器处理器
            when(event){
                is VideoRecordEvent.Finalize->{
                    if(event.hasError()){
                        recording?.close()
                        recording = null

                        Toast.makeText(applicationContext,
                            "录制视频失败",
                            Toast.LENGTH_LONG).show()

                    }else{
                        Toast.makeText(applicationContext,
                            "录制视频成功",
                            Toast.LENGTH_LONG).show()
                    }
                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(cameraController: LifecycleCameraController,
               onRecordAction:(cameraController:LifecycleCameraController)->Unit){
    //设置前后摄像头的图标状态
    var cameraIcon by remember{mutableStateOf(Icons.Default.VideoCameraBack)}
    var selected by remember{mutableStateOf(false)}
    Scaffold(bottomBar = {
        BottomAppBar {
            NavigationBarItem(selected = selected,
                onClick = {
                           onRecordAction(cameraController)
                          },
                icon = {
                    Icon(imageVector = Icons.Default.Videocam,contentDescription = "视频录制")
                })
        }
    }) {
        Box(modifier = Modifier
            .fillMaxSize()
            .padding(it)) {
            VideoPreview(cameraController = cameraController, modifier = Modifier.fillMaxSize())
            IconButton(onClick = {
                //照相机前后摄像头的切换
                    if (cameraController.cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) {
                        cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
                        cameraIcon = Icons.Default.VideoCameraBack
                    } else {
                        cameraController.cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
                        cameraIcon=Icons.Default.VideoCameraFront
                    }
            }, modifier = Modifier.offset(16.dp, 16.dp)) {
                Icon(
                    imageVector = cameraIcon,
                    tint = Color.Green,
                    contentDescription = "摄像头"
                )
            }
        }
    }
}

运行结果如图4所示:
在这里插入图片描述
图4
启动Device Explorer设备浏览器,在:data->data->对应应用的包名->files“目录下可以发现已经录制成功的视频文件video.mp4。
在这里插入图片描述
图5

(2)保存视频到媒体库

上述的代码需要检索并浏览录制的视频非常困难,并不是一个很好的方法。比较好的处理方式,是将录制的视频直接保存在媒体库中。通过将时间戳作为视频文件名,根据时间便可以非常方便的在移动终端的媒体库中检索和浏览录制的视频。则就需要在录制视频中采用如下的方式:

public Recording startRecording(
@NonNull MediaStoreOutputOptions outputOptions,//写入到媒体库的输出参数
@NonNull AudioConfig audioConfig,//音频的配置
@NonNull Executor executor,//将在其上运行事件侦听器的线程池
@NonNull Consumer listener)//处理视频录制的事件监听器

将上述的MainActivity中关于视频录制的私有方法recordVideo重新定义,具体代码如下:

class MainActivity : ComponentActivity() {
    private var recording: Recording? = null

    companion object{
        val CAMERAX_PERMISSIONS = arrayOf(
            android.Manifest.permission.CAMERA,//请求相机
            android.Manifest.permission.RECORD_AUDIO)//请求录制音频
    }

    @SuppressLint("UnsafeOptInUsageError")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //检查权限,若无就请求权限
        handlePermissions()

        setContent {
            val cameraController: LifecycleCameraController = remember {
                LifecycleCameraController(applicationContext).apply {
                    setEnabledUseCases(CameraController.VIDEO_CAPTURE)
                }
            }
            VideoCaptureAppTheme {
                MainScreen(cameraController,this::recordVideo)
            }
        }
    }

    /**
     * 检查权限
     * @return Boolean
     */
    private fun hasRequiredPermissions():Boolean = CAMERAX_PERMISSIONS.all{
        ContextCompat.checkSelfPermission(
            applicationContext,
            it) == PackageManager.PERMISSION_GRANTED
    }

    /**
     * Request permissions
     * 处理权限的操作
     */
    private fun handlePermissions(){
        if(!hasRequiredPermissions()){
            //请求权限
            ActivityCompat.requestPermissions(this, CAMERAX_PERMISSIONS,0)
        }
    }

    /**
     * 录制视频到文件
     * @param cmaeraController LifecycleCameraController
     */
    @SuppressLint("MissingPermission", "UnsafeOptInUsageError")
    private fun recordVideo(cameraController:LifecycleCameraController){
	    if(recording!=null){
	        //停止录制视频
	        recording?.stop()
	        recording = null
	        return
	    }
	
	    if(!hasRequiredPermissions())//没有访问权限
	        return
	
	    //定义包含时间戳的视频文件名
	    val name = SimpleDateFormat("yyyy-MM-dd hh:mm:ss",
	                        Locale.CHINA).format(System.currentTimeMillis())
	    //定义关于一条视频记录的相关配置
	    val contentValue = ContentValues().apply{
	        put(MediaStore.MediaColumns.DISPLAY_NAME,name)
	        put(MediaStore.MediaColumns.MIME_TYPE,"video/mp4")
	        if(Build.VERSION.SDK_INT>Build.VERSION_CODES.P){
	            put(MediaStore.Video.Media.RELATIVE_PATH,"Movies/CameraX-Video")
	        }
	    }
	    //配置输出到媒体库的输出参数
	    val mediaStoreOutputOptions = MediaStoreOutputOptions.Builder(
	         				contentResolver,
	        				MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
	        				.setContentValues(contentValue).build()
	
	    //执行视频录制
	    recording = cameraController.startRecording(
	        //输出到媒体库的配置
	        mediaStoreOutputOptions,
	        //启动语音
	        AudioConfig.create(true),
	        //在当前应用的上下文中创建事件监听器依附运行的线程池
	        ContextCompat.getMainExecutor(applicationContext)
	    ){event:VideoRecordEvent->
	        //监听器处理器
	        when(event){
	            is VideoRecordEvent.Finalize->{
	                if(event.hasError()){
	                    recording?.close()
	                    recording = null
	
	                    Toast.makeText(applicationContext,
	                        "录制视频失败",
	                        Toast.LENGTH_LONG).show()
	
	                }else{
	                    Toast.makeText(applicationContext,
	                        "录制视频成功",
	                        Toast.LENGTH_LONG).show()
	                     }
	                }
	             }
	        }
    }
}

运行录制视频后,在手机模拟器的图片库中可以发现刚刚录制的视频,运行结果如图6所示。
在这里插入图片描述
图6

五、设置WebCam为模拟器的摄像头

当然,模拟器的前后镜头可以自行设置webCam0,如图6所示。
在这里插入图片描述
图7
对于Camera Front和Camera Back可以任选其一设置为Webcam0,不能二者同时进行设置为WebCam0。通过这样的设置,可以在模拟器中启动WebCam,录制实时的视频。运行效果类似图8所示:
在这里插入图片描述
图8

六、参考文献

1.CameraX视频捕获架构

https://developer.android.google.cn/training/camerax/video-capture?hl=zh-cn

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

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

相关文章

nn.Embedding()的原理

nn.Embedding()的原理&#xff1a; 定义一个Embedding&#xff1a; embeddings nn.Embedding(num_embeddings10, embedding_dim3)vocab_size : 10 输出维度为&#xff1a; 3 假定输入inputs如下&#xff1a; inputs torch.tensor([[1,3,6, 8],[9,1,3,5] ],dtypetorch.lo…

达索系统SOLIDWORKS Electrical机电一体化协同设计

一秒读懂 SOLIDWORKS Electrical 问题点 电气、机械设计各自为政数据传递困难&#xff0c;存在设计错误 CHALLENGE电气设计面临挑战 问题点&#xff1a;电气、机械设计各自为政数据传递困难&#xff0c;存在设计错误 原理图绘制完毕后&#xff0c;再绘制接线图,人工统计BOM&a…

Valgrind——程序分析工具

目录 Valgrind一.摘要二.安装Valgrind三,简单上手和分析程序1(C程序):使用未初始化的内存程序2(C程序):在内存被释放后进行读/写程序3(C程序): 内存泄露程序4(C程序): 不匹配使用malloc free 和 new delete程序5(C程序): 两次释放内存 四.Qt中使用Valgrind五.内存泄露分析 Valg…

Java学习day12:static关键字,字符串声明,字符串常量池

声明&#xff1a;该专栏本人重新过一遍java知识点时候的笔记汇总&#xff0c;主要是每天的知识点题解&#xff0c;算是让自己巩固复习&#xff0c;也希望能给初学的朋友们一点帮助&#xff0c;大佬们不喜勿喷(抱拳了老铁&#xff01;) 往期回顾&#xff1a; Java学习day11&…

HBase中的数据表是如何用CHAT进行分区的?

问CHA&#xff1a;HBase中的数据表是如何进行分区的&#xff1f; CHAT回复&#xff1a; 在HBase中&#xff0c;数据表是水平分区的。每一个分区被称为一个region。当一个region达到给定的大小限制时&#xff0c;它会被分裂成两个新的region。 因此&#xff0c;随着数据量的增…

Unity | 运行时显示调试信息

「公众号&#xff1a;游戏开发手记」 1 简介 在 Unity 编辑器中&#xff0c;我们可以通过点击 Stats 按钮来查看 Statistics 面板&#xff0c;这个面板显示了许多关于游戏渲染的信息&#xff0c;如每帧的渲染时间、Tris 和 Verts 的数量、SetPass Calls 的数量等。但在其他运…

Spring6(五):Resources、i18n、Validation

文章目录 7. 资源操作&#xff1a;Resources7.1 Resource接口7.2 Resource的实现类7.2.1 UrlResource访问网络资源7.2.2 ClassPathResource 访问类路径下资源7.2.3 FileSystemResource 访问文件系统资源7.2.4 其他 7.3 Resource类图7.4 ResourceLoader 接口7.5 ResourceLoaderA…

EV代码签名证书

为了增强软件的安全性和可信度&#xff0c;EV代码签名证书&#xff08;Extended Validation Code Signing Certificate&#xff09;成为了一种具有最高级别保障的关键工具。 EV代码签名证书是一种由受信任的证书颁发机构&#xff08;CA&#xff09;或证书供应商提供的高级别代…

linux之shell

一、是什么 Shell是一个由c语言编写的应用程序&#xff0c;它是用户使用 Linux 的桥梁。Shell 既是一种命令语言&#xff0c;又是一种程序设计语言 它连接了用户和Linux内核&#xff0c;让用户能够更加高效、安全、低成本地使用 Linux 内核 其本身并不是内核的一部分&#x…

创建SpringBoot项目后无法运行Java文件的解决方法

目录 1.无法运行 &#xff08;1&#xff09;解决方法一 &#xff08;2&#xff09;解决方法二 2.包无法下载 &#xff08;1&#xff09;查看pom.xml文件中包是否存在的方法 &#xff08;2&#xff09;如果仓库存在包的处理方式 1.无法运行 当我们创建好一个SpringBoot项目…

make和makefile

一、认识make和Makefile 1、会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力 2、一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪些文件需要先编译…

借助Spire.Doc for Java控件,将 ODT 转换为 PDF。

在通过电子邮件发送或与其他人共享 ODT 文件之前&#xff0c;您可能需要将该文件转换为 PDF&#xff0c;以便任何人都可以跨多个操作系统访问该文件。在本文中&#xff0c;您将学习如何使用Spire.Doc for Java在 Java 中将 ODT 转换为 PDF。 Spire.Doc 是一款专门对 Word 文档…

中国生态功能保护区shp数据

代码 名称 类型 1 秦岭山地生态功能保护区 水涵养生态功能保护区 2 若尔盖一玛曲生态功能保护区 水涵养生态功能保护区 3 滇西北生态功能保护区 水涵养生态功能保护区 4 珠江源&#xff08;云南部分&#xff09;生态功能保护区 水涵养生态功能保护区 5 雅鲁藏布…

什么是游戏盾?怎么进行防护?

一.德迅游戏盾&#xff08;抗D盾&#xff09;概述 1.抗D盾是针对游戏行业推出的高度可定制的网络安全解决方案&#xff0c;可以针对大型DDoS攻击(T级别)进行有效防御&#xff0c;同时能彻底解决游戏行业特有的TCP协议的CC攻击问题。 2.抗D盾是新一代的智能分布式云接入系统&a…

骨传导蓝牙耳机排行榜,精选五款骨传导耳机推荐!

目前市面上的骨传导耳机大多是传统挂耳式&#xff0c;虽然佩戴更稳固&#xff0c;但是也限制住了其使用场景&#xff0c; 但近两年&#xff0c;有一款名为骨传导耳机的品类进入了大众的视野&#xff0c;它以独特的款式和超乎以往的佩戴舒适性迅速圈粉无数&#xff0c;并成为当下…

第一讲之递归与递推下篇

第一讲之递归与递推下篇 带分数费解的开关飞行员兄弟翻硬币 带分数 用暴力将所有全排列的情况都算出来 > 有三个数&#xff0c;a,b,c 每种排列情况&#xff0c;可以用两层for循环&#xff0c;暴力分为三个部分&#xff0c;每个部分一个数 当然注意这里&#xff0c;第一层fo…

【Spring进阶系列丨第二篇】Spring中的两大核心技术IoC(控制反转)与DI(依赖注入)

前言 我们都知道Spring 框架主要的优势是在 简化开发 和 框架整合 上&#xff0c;至于如何实现就是我们要学习Spring 框架的主要内容&#xff0c;今天我们就来一起学习Spring中的两大核心技术IoC&#xff08;控制反转&#xff09;与DI&#xff08;依赖注入&#xff09;。 文章目…

关于CISSP中文版计算机化自适应考试(CAT),你需要知道的!

进入ISC2宣布CISSP简体中文的线性考试(Linear Test)被取消&#xff0c;逐步实行CISSP中文版计算机化自适应考试&#xff08;CAT&#xff09;。接下来我根据ISC2官网和互联网中关于CAT相关信息&#xff0c;给大家对CISSP认证CAT进行介绍。 一、什么是CISSP认证线性考试&#xf…

实时云渲染技术在智慧园区中的广泛应用

智慧园区是指通过运用先进的信息技术&#xff0c;利用物联网、大数据、云计算等技术手段&#xff0c;来实现对园区内各类设备、设施和资源进行监测、管理、控制和优化的平台。这一概念旨在提高园区运行的效率、实现资源的可持续利用&#xff0c;并通过数字化和智能化手段来推动…

010.cat、find

1、用cat进行拼接 cat命令能够显示或拼接文件内容&#xff0c;不过它的能力远不止如此。比如说&#xff0c;cat能够将标准输入数据与文件数据组合在一起。通常的做法是将stdin重定向到一个文件&#xff0c;然后再合并两个文件。而cat命令一次就能搞定这些操作。 用cat读取文件…