当前位置: 首页 > news >正文

Flutter3引用原生播放器-IOS(Swift)篇

前言

由于Flutter项目中需要使用到播放器功能,因此对flutter中各种播放器解决方案进行了一番研究和比对,最后决定还是自己通过Plugin的方法去引用原生播放器符合自己的需求,本篇文章会对各种解决方案做一个简单的比较,以及讲解一下发Flutter3.0中ios引用原生view的步骤和逻辑,方便大家遇到相同问题时,可以进行一个参考。

video_player

video_player:flutter官方出品。

优点:集成速度快,易上手,使用简单,同时支持Android,ios,web;
缺点:ui丑,功能简单,可定制化差,不支持rtmp等协议直播;
适用:对播放器要求不高,不需要直播,只要视频能播放出来就可以的用户;

better_player

better_player:国外大神在video_player的基础之上二次开发而来,对video_player进行了各种优化,并添加了非常多的实用功能。

优点:同video_player,且比video_player更强大一点;
缺点:依然是可定制化差,不支持rtmp等直播格式;
适用:对视频播放器稍微有要求比如视频格式,播放速度,缓存等功能,又不想自己动手去写原生插件,对ui定制化要求也不高的用户;

fijkplayer

fijkplayer:基于ijkplayer,是对 ijkplayer 的 Flutter 封装,支持安卓和ios;

优缺点:做过安卓和ios原生的,对ijk应该都非常熟悉了,这里就不做过多说明了;
fijkplayer支持各种视频格式包括rtmp等协议直播,支持各种常用功能,支持定制化UI,具体可以去它的官网查看文档说明;
适用:对播放器要求稍高,需要简单定制化播放器ui,需要支持直播的用户;

这里说一些个人的使用体验,因为我的项目需要支持rtmp直播,并且对ui定制化要求较高,所以放弃了官方的video_player,优先选择了fijkplayer,但实际体验上不太尽如人意。虽然支持ui定制,但想要达到自己产品的ui设计,还需要费上很大一番功夫。并且该项目作者有一年多未更新维护了,后续是否会继续更新维护解决bug不得而知。另外,我在播放rtmp时播放失败,不知何故,不过我并没深究原因,因为此时,我已经动了自己动手引用双端原生播放器的念头了!

IOS制作并引用原生View步骤

1)使用Xcode运行项目:

双击flutter项目/ios目录下的Runner.xcworkspace,Xcode会自动打开项目;

2)检验项目:

直接运行,查看是否有报错信息;

如果你已经在pubspec.yaml中使用了大量的第三方插件,此时运行可能会报错:xxx Module not Found!那么你需要在打开终端并cd到ios目录,执行 pod install

在这里插入图片描述
一般情况下都可以解决问题!

3)创建VideoViewPlugin实现FlutterPlugin协议:

import Flutter
import UIKitclass VideoViewPlugin:  NSObject, FlutterPlugin {@objc static func register(with registrar: FlutterPluginRegistrar) {registrar.register(VideoViewFactory(registrar: registrar), withId: "plugins.my_video_player/view")}}

由于FlutterPlugin是OC写的,所以在Swift中实现OC协议,前面需要加上NSObject。通过FlutterPluginRegistrar的registrar注册PlatformViewFactory。

plugins.my_video_player/view即为该插件的id,在flutter中引用原生view时需要写入并且安卓,ios和flutter三方都要保持一致!

4)创建VideoViewFactory实现FlutterPlatformViewFactory协议:

import UIKit
import Flutterclass VideoViewFactory:NSObject, FlutterPlatformViewFactory {private var registrar:FlutterPluginRegistrarinit(registrar: FlutterPluginRegistrar) {self.registrar=registrarsuper.init()}//create方法是在flutter中该widget加载显示出来时才执行//所以自定flutter中使用原生View时传递的参数在create执行后,且获取到参数后,再创建channelfunc create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {let id=args as! Intreturn VideoViewPlayer(id: id, registrar: registrar)}//这个方法一定要写,否则接受不到flutter的传参func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {return FlutterStandardMessageCodec.sharedInstance()}
}

注意,createArgsCodec()一定要重写,否则无法接收到flutter在使用view时传过来的参数;create方法中的arguments即为传递的参数,create方法返回PlatformView(UIView);

5)创建VideoViewPlayer实现FlutterPlatformView协议:

import UIKit
import Flutterclass VideoViewPlayer: NSObject,FlutterPlatformView {//懒加载private var videoView:UIView={let videoView=UIView()return videoView}()//主要就是在这里,返回原生viewfunc view() -> UIView {return videoView}}

6)在AppDelegate.swift中注册插件:

import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)//插件名自定义if let register=self.registrar(forPlugin: "VideoViewPlugin"){VideoViewPlugin.register(with: register)}return super.application(application, didFinishLaunchingWithOptions: launchOptions)}
}

这里其实需要两步:

let register=self.registrar(forPlugin: "VideoViewPlugin")
VideoViewPlugin.register(with: register)

注意registrar和register是不一样的!

7)在flutter中引用VideoView:

  • 自定义Widget,MyVideoPlayer:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';class MyVideoPlayer extends StatefulWidget {final int id;const MyVideoPlayer({super.key,required this.id,});State<StatefulWidget> createState() => _VideoPlayerState();
}class _VideoPlayerState extends State<MyVideoPlayer> {void initState() {super.initState();}Widget build(BuildContext context) {if (defaultTargetPlatform == TargetPlatform.android) {return AndroidView(viewType: 'plugins.my_video_player/view',creationParams: widget.id, //传递到原生插件的参数(任意类型)creationParamsCodec: const StandardMessageCodec(),);} else {return UiKitView(viewType: 'plugins.my_video_player/view',creationParams: widget.id, //传递到原生插件的参数(任意类型)creationParamsCodec: const StandardMessageCodec(),);}}}

viewType即上面VideoViewPlugin类中注册的插件id:plugins.my_video_player/view,安卓,ios和flutter保持一致!

  • 引用MyVideoPlayer:
SizedBox(height: 200,child: MyVideoPlayer(id: 0))

这里可以看到,我给原生View传了一个id(需要唯一,我用的是时间戳)的参数,这个是为了后面在创建MethodChannel时加以区分,在同时使用了多个MyVideoPlayer时,不会相互影响!

此时我们就可以来测试一下,引用是否成功了,为了效果明显,我们可以在VideoViewPlayer.swift中为UIView添加一个背景色:

 private var videoView:UIView={let videoView=UIView()videoView.backgroundColor=UIColor.redreturn videoView}()

运行效果:

在这里插入图片描述

如上图,说明原生View已经引用成功了!不过,本篇文章的目的,不只是讲解如何引用原生View,我们的目标是,如何引用原生播放器!

如果你本身是ios开发者,或者开发过ios项目,并且在所开发的项目中使用过第三方的播放器,那么你其实可以直接将你所使用的播放器库,引入到本项目中直接使用,无非就是在VideoViewPlayer.swift中初始化并配置一些参数,最后封装进返回的videoView中!

如果你未开发过ios项目,或者没使用过第三方的播放器,那么你就可以在github上自行搜索star数量高的ios开源播放器项目了。由于我使用的是Swift语言,所以我优先查找了用Swift写的播放器,但查找后发现star量都太低了,而且还必须要支持rtmp播放,最后还是选择了OC写的star量最高的ZFPlayer!

引入开源播放器:ZFPlayer

1)安装:Podfile中添加:

  pod 'ZFPlayer', '~> 4.0'pod 'ZFPlayer/ControlView', '~> 4.0'pod 'ZFPlayer/AVPlayer', '~> 4.0'pod 'ZFPlayer/ijkplayer', '~> 4.0'

由于AVPlayer本身不支持直播,所以还需要引入ZFPlayer/ijkplayer,来支持直播功能!
执行pod intall!由于github总是间歇性无法访问,如果提示SSL超时等问题,可以多试几遍!

2)参考ZFPlayer文档,将ZFPlayer封装进UIView(viewPlayer)返回给Flutter:

VideoViewPlayer中:

    //zfplayer控制器private var player:ZFPlayerController = ZFPlayerController()//播放器进度控制条UIViewprivate var playerControlView=ZFPlayerControlView()//AVPlayer管理器private var avPlayerManager:ZFAVPlayerManager?//IjkPlayer管理器private var ijkPlayerManager:ZFIJKPlayerManager?init(id:Int,registrar:FlutterPluginRegistrar) {super.init()//为player设置进度控制条player.controlView=playerControlView//为player设置containerViewplayer.containerView=videoView}

此时其实就可以播放视频了,传入视频Url:

//可以根据视频类型,选择使用AVPlayer还是IjkPlayer播放:
//我这里点播使用AVPlayer,直播使用IjkPlayer,自己可以根据自身项目情况选择
if url.starts(with: "http"){avPlayerManager=ZFAVPlayerManager()avPlayerManager!.assetURL=URL(string: url)player.currentPlayerManager=avPlayerManager!playerControlView.portraitControlView.slider.isHidden=falseplayerControlView.portraitControlView.currentTimeLabel.isHidden=falseplayerControlView.portraitControlView.totalTimeLabel.isHidden=false}if url.starts(with: "rtmp"){ijkPlayerManager=ZFIJKPlayerManager()ijkPlayerManager!.assetURL=URL(string: url)player.currentPlayerManager=ijkPlayerManager!playerControlView.portraitControlView.slider.isHidden=trueplayerControlView.portraitControlView.currentTimeLabel.isHidden=trueplayerControlView.portraitControlView.totalTimeLabel.isHidden=true}if avPlayerManager != nil {avPlayerManager!.play()}if ijkPlayerManager != nil {ijkPlayerManager!.play()}

但这样,无法动态从flutter传入url啊?所以,我们还需要flutter和ios通信,即使用MethodChannel!

3)IOS端创建FlutterMethodChannel:

    //这里就用到了从flutter传入的idfunc initMethodChannel(id:Int,registrar:FlutterPluginRegistrar) {let channel = FlutterMethodChannel(name: "my_video_player_\(id)", binaryMessenger: registrar.messenger())channel.setMethodCallHandler(handleMethod)}//接收flutter发来的消息func handleMethod(call: FlutterMethodCall, result: FlutterResult){switch call.method {case "setUrl":let url: String=call.arguments as! StringinitPlayer(url: url)breakcase "start":play()breakcase "pause":pause()breakcase "release":stop()breakdefault:result(FlutterMethodNotImplemented)break}}

VideoViewPlayer完整代码如下:

import UIKit
import Flutter
import ZFPlayerclass VideoViewPlayer: NSObject,FlutterPlatformView {private var videoView:UIView={let videoView=UIView()return videoView}()private var player:ZFPlayerController = ZFPlayerController()private var playerControlView=ZFPlayerControlView()private var avPlayerManager:ZFAVPlayerManager?private var ijkPlayerManager:ZFIJKPlayerManager?init(id:Int,registrar:FlutterPluginRegistrar) {super.init()player.controlView=playerControlViewinitMethodChannel(id: id, registrar: registrar)}func view() -> UIView {return videoView}}// MARK:- 播放器初始化及控制
extension VideoViewPlayer{//初始化播放器func initPlayer(url:String){player.containerView=videoViewif avPlayerManager != nil {avPlayerManager!.pause()avPlayerManager=nil}if ijkPlayerManager != nil {ijkPlayerManager!.pause()ijkPlayerManager=nil}if url.starts(with: "http"){avPlayerManager=ZFAVPlayerManager()avPlayerManager!.assetURL=URL(string: url)player.currentPlayerManager=avPlayerManager!playerControlView.portraitControlView.slider.isHidden=falseplayerControlView.portraitControlView.currentTimeLabel.isHidden=falseplayerControlView.portraitControlView.totalTimeLabel.isHidden=false}if url.starts(with: "rtmp"){ijkPlayerManager=ZFIJKPlayerManager()ijkPlayerManager!.assetURL=URL(string: url)player.currentPlayerManager=ijkPlayerManager!playerControlView.portraitControlView.slider.isHidden=trueplayerControlView.portraitControlView.currentTimeLabel.isHidden=trueplayerControlView.portraitControlView.totalTimeLabel.isHidden=true}}func play(){if avPlayerManager != nil {avPlayerManager!.play()}if ijkPlayerManager != nil {ijkPlayerManager!.play()}}func pause(){if avPlayerManager != nil {avPlayerManager!.pause()}if ijkPlayerManager != nil {ijkPlayerManager!.pause()}}func stop(){pause()player.stop()}
}// MARK:- flutter消息通道处理
extension VideoViewPlayer{func initMethodChannel(id:Int,registrar:FlutterPluginRegistrar) {let channel = FlutterMethodChannel(name: "my_video_player_\(id)", binaryMessenger: registrar.messenger())channel.setMethodCallHandler(handleMethod)}//接收flutter发来的消息func handleMethod(call: FlutterMethodCall, result: FlutterResult){switch call.method {case "setUrl":let url: String=call.arguments as! StringinitPlayer(url: url)breakcase "start":play()breakcase "pause":pause()breakcase "release":stop()breakdefault:result(FlutterMethodNotImplemented)break}}
}

4)flutter端创建MethodChannel:

  init() {methodChannel = MethodChannel('my_video_player_$id');methodChannel.setMethodCallHandler((call) => flutterMethod(call));}Future<void> setUrl(String url) async {return methodChannel.invokeMethod('setUrl', url);}Future<void> start() async {return methodChannel.invokeMethod('start');}...

flutter中调用setUrl后,再调用start方法即可播放了!

我这里给flutter端的MethodChanel封装为了一个Controller:

import 'package:flutter/services.dart';
import 'package:kjjl_flutter/components/loading.dart';
import 'package:kjjl_flutter/utils/log_util.dart';class VideoViewController {late MethodChannel methodChannel;int id;VideoViewController({required this.id});init() {methodChannel = MethodChannel('my_video_player_$id');methodChannel.setMethodCallHandler((call) => flutterMethod(call));}Future<void> setUrl(String url) async {return methodChannel.invokeMethod('setUrl', url);}Future<void> start() async {return methodChannel.invokeMethod('start');}Future<void> pause() async {return methodChannel.invokeMethod('pause');}Future<void> release() async {return methodChannel.invokeMethod('release');}Future<void> stopFullScreen() async {return methodChannel.invokeMethod('stopFullScreen');}Future<dynamic> flutterMethod(MethodCall methodCall) async {switch (methodCall.method) {}}
}

在State中使用时的部分代码:

SizedBox(height: videoHeight,child: currentVideo != null && videoViewController != null? MyVideoPlayer(id: videoViewController!.id): SizedBox(),)...VideoModel? currentVideo;
VideoViewController? videoViewController;//页面销毁时释放播放器

void dispose() {if (videoViewController != null) videoViewController!.release();super.dispose();
}//点击播放时执行
play(VideoModel video) {if (videoViewController != null) {if (currentVideo != null && currentVideo!.id == video.id) return;setState(() {currentVideo = video;videoViewController!.release();startPlay(video);});} else {setState(() {currentVideo = video;videoViewController = VideoViewController(id: DateTime.now().millisecondsSinceEpoch);videoViewController!.init();//延时500ms执行,是为了防止播放器还未初始化完成,就调用了播放,导致首次播放失败;Future.delayed(const Duration(milliseconds: 500), () {startPlay(video);});});}}startPlay(VideoModel video){videoViewController!.setUrl(video.mobileUrl);videoViewController!.start();
}

效果图:

直播:

在这里插入图片描述

录播:

在这里插入图片描述

下一篇:Flutter3引用原生播放器-Android篇

相关文章:

Flutter3引用原生播放器-IOS(Swift)篇

前言由于Flutter项目中需要使用到播放器功能&#xff0c;因此对flutter中各种播放器解决方案进行了一番研究和比对&#xff0c;最后决定还是自己通过Plugin的方法去引用原生播放器符合自己的需求&#xff0c;本篇文章会对各种解决方案做一个简单的比较&#xff0c;以及讲解一下…...

【蓝桥杯每日一题】双指针算法

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; 蓝桥杯 &#x1f319;我与杀戮之中绽放&#xff0c;亦如黎明的花…...

拼数(一般贪心)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题号&#xff1a;NC16783 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld 题目描述 设有n个正整…...

LeetCode 热题 C++ 169. 多数元素 10. 正则表达式匹配 155. 最小栈

力扣169 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1&#xff1a; 输入&#xff1a;nums [3,2,3] 输出&#xff1…...

Clickhouse学习:MergeTree

MergeTree一、MergeTree逻辑存储结构二、MergeTree物理存储结构三、总结一、MergeTree逻辑存储结构 如上图所示,在排序键(CountrID、Date)上做索引,数据会按照这两个字段先后排序ClickHouse是稀疏索引,每隔8192行做一个索引,如(a,1),(a,2),比如想查a,要读取[0,3)之间的内容,稀疏…...

【java基础】包装类,自动装箱和自动拆箱

文章目录基本介绍包装类自动装箱自动拆箱包装类注意事项包装类比较包装器内容不可变基本介绍 有时&#xff0c;需要将int这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。 例如&#xff0c;Integer类对应基本类型int。通常&#xff0c;这些类称为包装器&#…...

Android笔记(二十五):两种sdk热更插件资源加载方案

背景 在研究sdk插件化热更新方式的过程中总结出了两套插件资源加载方案&#xff0c;在此记录下 资源热更方式 方式一&#xff1a;合并所有插件资源 需要解决资源id冲突问题 资源ID值一共4个字段&#xff0c;由三部分组成&#xff1a;PackageIdTypeIdEntryId PackageId&…...

spring框架--全面详解(学习笔记)

目录 1.Spring是什么 2.Spring 框架特点 3.Spring体系结构 4.Spring开发环境搭建 5.spring中IOC和DI 6.Spring中bean的生命周期 7.Spring Bean作用域 8.spring注解开发 9.Spring框架中AOP&#xff08;Aspect Oriented Programming&#xff09; 10.AOP 实现分类 11.A…...

2023年CDGA考试模拟题库(401-500)

2023年CDGA考试模拟题库(401-500) 401.数据管理战略的SMART原则指的是哪项? [1分] A.具体 、高质量、可操作 、现实、有时间限制 B.具体、可衡量、可检验、现实、有时间限制 C.具体、可衡量、可操作、现实、有时间限制 D.具体、高质量、可检验、现实12-24个月的目标 答…...

软件设计师备考文档

cpu 计算机的基本硬件系统&#xff1a;运算器、控制器、存储器、输入设备、输出设备 cpu负责获取程序指令&#xff0c;对指令进行译码并加以执行 * cpu的功能控制器&#xff08;保证程序正常执行&#xff0c;处理异常事件&#xff09; 程序控制操作控制运算器&#xff08;只能…...

Javascript的API基本内容(一)

一、获取DOM对象 querySelector 满足条件的第一个元素 querySelectorAll 满足条件的元素集合 返回伪数组 document.getElementById 专门获取元素类型节点&#xff0c;根据标签的 id 属性查找 二、操作元素内容 通过修改 DOM 的文本内容&#xff0c;动态改变网页的内容。 inn…...

10、最小公倍数

法一&#xff1a; #include <stdio.h>int main(){int a,b;scanf("%d%d",&a,&b);int m a>b?a:b;//m表示a,b之间的较大值while(1){if(m%a0&&m%b0){break;}m;}printf("%d",m);return 0; }法二&#xff1a;a*i%b0成立 #include &…...

【vue】vue2.x项目中使用md文件

一、Vue项目展示md文件的三种方式 1、将md文件 导入为 html 生成的标题标签自带具有id属性&#xff0c;值为标题内容&#xff1b; <h2 id"测试">测试</h2> # 处理md为html字符串 yarn add markdown-loader # 处理字符串&#xff0c;用于导出显示 yarn a…...

操作系统权限提升(十三)之绕过UAC提权-MSF和CS绕过UAC提权

系列文章 操作系统权限提升(十二)之绕过UAC提权-Windows UAC概述 注&#xff1a;阅读本编文章前&#xff0c;请先阅读系列文章&#xff0c;以免造成看不懂的情况&#xff01;&#xff01; MSF和CS绕过UAC提权 CS绕过UAC提权 拿到一个普通管理员的SHELL,在CS中没有*号代表有…...

快速排序+快速定位

快速排序算法采用了分治法以及递归作为解决问题的思想。在计算机科学中&#xff0c;分治法是一种很重要的算法。字面上的解释是“分而治之”&#xff0c;就是把一个复杂的问题分成两个或更多的相同或相似的子问题&#xff0c;再把子问题分成更小的子问题……直到最后子问题可以…...

nginx http rewrite module 详解

大家好&#xff0c;我是 17。 今天和大家聊聊 nginx http rewrite module 。 简单来说&#xff0c; ngx_http_rewrite_module module 用正则匹配请求&#xff0c;改写请求&#xff0c;然后做跳转。可以是内部跳转&#xff0c;也可以是外部跳转。 学习这个模块的时候&#xf…...

机器学习可解释性一(LIME)

随着深度学习的发展&#xff0c;越来越多的模型诞生&#xff0c;并且在训练集和测试集上的表现甚至于高于人类&#xff0c;但是深度学习一直被认为是一个黑盒模型&#xff0c;我们通俗的认为&#xff0c;经过训练后&#xff0c;机器学习到了数据中的特征&#xff0c;进而可以正…...

CV学习笔记-MobileNet

MobileNet 文章目录MobileNet1. MobileNet概述2. 深度可分离卷积&#xff08;depthwise separable convolution&#xff09;2.1 深度可分离卷积通俗理解2.2 深度可分离卷积对于参数的优化3. MobileNet网络结构4. 代码实现4.1 卷积块4.2 深度可分离卷积块4.3 MobileNet定义4.4 完…...

C++进阶——继承

C进阶——继承 1.继承的概念及定义 面向对象三大特性&#xff1a;封装、继承、多态。 概念&#xff1a; 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特 性的基础上进行扩展&#xff0c;增加功能&#xff0c;这…...

数据结构---单链表

专栏&#xff1a;数据结构 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;从零开始&#xff0c;数据结构&#xff01;&#xff01; 单链表前言顺序表的缺陷链表的概念以及结构链表接口实现打印链表中的元素SLTPrintphead->next!NULL和phead!NULL的区别开辟空间SLTNewNod…...

redis数据结构的底层实现

文章目录一.引言二.redis的特点三.Redis的数据结构a.字符串b.hashc.listd.sete.zset(有序集合)一.引言 redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value的NoSQL数据库。 通常使用redis作为缓存中间件来降低数据库的压力&#xff0c;除此…...

【JavaSE】复习(进阶)

文章目录1.final关键字2.常量3.抽象类3.1概括3.2 抽象方法4. 接口4.1 接口在开发中的作用4.2类型和类型之间的关系4.3抽象类和接口的区别5.包机制和import5.1 包机制5.2 import6.访问控制权限7.Object7.1 toString()7.2 equals()7.3 String类重写了toString和equals8.内部类8.1…...

Java 主流日志工具库

日志系统 java.util.logging (JUL) JDK1.4 开始&#xff0c;通过 java.util.logging 提供日志功能。虽然是官方自带的log lib&#xff0c;JUL的使用确不广泛。 JUL从JDK1.4 才开始加入(2002年)&#xff0c;当时各种第三方log lib已经被广泛使用了JUL早期存在性能问题&#x…...

产品经理有必要考个 PMP吗?(含PMP资料)

现在基本上做产品的都有一个PMP证件&#xff0c;从结果导向来说&#xff0c;不对口不会有这么大范围的人来考&#xff0c;但是需要因地制宜&#xff0c;在公司内部里&#xff0c;标准程序并不流畅&#xff0c;产品和项目并不规范&#xff0c;关系错综复杂。 而产品经理的职能又…...

什么是原型、原型链?原型和原型链的作用

1、ES6之前&#xff0c;继承都用构造函数来实现&#xff1b;对象的继承,先申明一个对象&#xff0c;里面添加实例成员<!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title></head><body><script…...

条件期望4

条件期望例题----快排算法的分析 快速排序算法的递归定义如下: 有n个数(n≥2n\geq 2n≥2), 一开始随机选取一个数xix_ixi​, 并将xix_ixi​和其他n-1个数进行比较, 记SiS_iSi​为比xix_ixi​小的元素构成的集合, Siˉ\bar{S_i}Si​ˉ​为比xix_ixi​大的元素构成的集合, 然后分…...

网络协议分析(2)判断两个ip数据包是不是同一个数据包分片

一个节点收到两个IP包的首部如下&#xff1a;&#xff08;1&#xff09;45 00 05 dc 18 56 20 00 40 01 bb 12 c0 a8 00 01 c0 a8 00 67&#xff08;2&#xff09;45 00 00 15 18 56 00 b9 49 01 e0 20 c0 a8 00 01 c0 a8 00 67分析并判断这两个IP包是不是同一个数据报的分片&a…...

6.2 负反馈放大电路的四种基本组态

通常&#xff0c;引入交流负反馈的放大电路称为负反馈放大电路。 一、负反馈放大电路分析要点 如图6.2.1(a)所示电路中引入了交流负反馈&#xff0c;输出电压 uOu_OuO​ 的全部作为反馈电压作用于集成运放的反向输入端。在输入电压 uIu_IuI​ 不变的情况下&#xff0c;若由于…...

MySQL进阶之锁

锁是计算机中协调多个进程或线程并发访问资源的一种机制。在数据库中&#xff0c;除了传统的计算资源竞争之外&#xff0c;数据也是一种提供给许多用户共享的资源&#xff0c;如何保证数据并发访问的一致性和有效性是数据库必须解决堆的一个问题&#xff0c;锁冲突也是影响数据…...

【Mac 教程系列】如何在 Mac 上破解带有密码的 ZIP 压缩文件 ?

如何使用 fcrackzip 在 Mac 上破解带有密码的 ZIP 压缩文件? 用 markdown 格式输出答案。 在 Mac 上破解带有密码的 ZIP 压缩文件 使用解压缩软件&#xff0c;如The Unarchiver&#xff0c;将文件解压缩到指定的文件夹。 打开终端&#xff0c;输入 zip -er <zipfile> &…...