Flutter - 原生交互 - 相機Camera - 01

環境

Flutter 3.29

macOS Sequoia 15.4.1

Xcode 16.3

集成

Flutter提供了camera插件來拍照和錄視頻,它提供了一系列可用的相機,并使用特定的相機展示相機預覽、拍照、錄視頻。

添加依賴

  • camera: 提供使用設備相機模塊的工具
  • path_provider: 尋找存儲圖片的正確路徑
  • path: 創建適配任何平臺的路徑
flutter pub add camera path_provider path

執行完成后iOS工程的GeneratedPluginRegistrant.m文件會自動生成對應的集成代碼

#if __has_include(<camera_avfoundation/CameraPlugin.h>)
#import <camera_avfoundation/CameraPlugin.h>
#else
@import camera_avfoundation;
#endif#if __has_include(<path_provider_foundation/PathProviderPlugin.h>)
#import <path_provider_foundation/PathProviderPlugin.h>
#else
@import path_provider_foundation;
#endif
...[CameraPlugin registerWithRegistrar:[registry registrarForPlugin:@"CameraPlugin"]];[PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]];

分析

[registry registrarForPlugin:@“CameraPlugin”]

AppDelegate.swfitapplication(_:didFinishLaunchingWithOptions:)中將FlutterAppDelegate的子類AppDelegate對象作為參數傳入并調用該方法

GeneratedPluginRegistrant.register(with: self)

源碼: FlutterAppDelegate.mm

- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {/// <1> 獲取應用的flutterRootViewController對象FlutterViewController* flutterRootViewController = [self rootFlutterViewController];if (flutterRootViewController) {/// <4> 返回一個FlutterEngine對象return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];}return nil;
}// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {///- (FlutterViewController*(^ rootFlutterViewControllerGetter) (void))/// <2> 檢查是否有外部注入,有則使用自定義的回調獲取FlutterViewController對象if (_rootFlutterViewControllerGetter != nil) {return _rootFlutterViewControllerGetter();}/// <3> 沒有則檢查window的rootViewController屬性,如果是FlutterViewController則返回,否則返回nilUIViewController* rootViewController = _window.rootViewController;if ([rootViewController isKindOfClass:[FlutterViewController class]]) {return (FlutterViewController*)rootViewController;}return nil;
}
[flutterRootViewController pluginRegistry]

第<4>步中的方法調用在 FlutterViewController.mm

///  pluginRegistry方法獲得一個遵守FlutterPluginRegistry協議的對象
- (id<FlutterPluginRegistry>)pluginRegistry {return self.engine;
}
.../// engine是FlutterEngine對象
- (void)sharedSetupWithProject:(nullable FlutterDartProject*)projectinitialRoute:(nullable NSString*)initialRoute {
...
engine = [[FlutterEngine alloc] initWithName:@"io.flutter"project:projectallowHeadlessExecution:self.engineAllowHeadlessExecutionrestorationEnabled:self.restorationIdentifier != nil];
...
_engine = engine;
...              - (instancetype)initWithEngine:(FlutterEngine*)enginenibName:(nullable NSString*)nibNamebundle:(nullable NSBundle*)nibBundle {
...
_engine = engine;          

FlutterPluginRegistry協議的繼承結構

17485097331657.png

FlutterEngine對象調用registrarForPlugin:方法

源碼: FlutterEngine.mm

/// 文件: FlutterEngine.mm
...
/*** All registrars returned from registrarForPlugin:*/
@property(nonatomic, readonly)NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* pluginRegistrars;
...- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {/// <5> 檢查可變字典中是否已存在插件名的keyid<FlutterPluginRegistrar> registrar = self.pluginRegistrars[pluginName];if (!registrar) {/// <6> 首次注冊,生成FlutterEngineRegistrar對象并持有pluginName和弱引用FlutterEngine 對象/// 為什么是弱引用?/// 文件:FlutterViewController.m 強引入了FlutterEngine對象/// @property(nonatomic, readonly) FlutterEngine* engine;FlutterEngineRegistrar* registrarImpl =[[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self];/// 接收傳入的pluginName  + self即FlutterEngine對象/// @interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>/// @property(nonatomic, weak) FlutterEngine* flutterEngine;/// @implementation FlutterEngineRegistrar {///   NSString* _pluginKey;/// }/// 因為FlutterViewController.m 已經強引入了FlutterEngine對象,所以這里的flutterEngine弱引用即可/// @property(nonatomic, readonly) FlutterEngine* engine;/// /// <7> 添加到FlutterEngine對象的可變字典中self.pluginRegistrars[pluginName] = registrarImpl;registrar = registrarImpl;}/// <8> 返回FlutterEngineRegistrar注冊對象,其中保存FlutterEngine相關的信息,負責與Flutter的iOS插件交互return registrar;
}
[CameraPlugin registerWithRegistrar:registrar]
/// camera插件的類定義
public final class CameraPlugin: NSObject, FlutterPlugin {
@protocol FlutterPlugin <NSObject, FlutterApplicationLifeCycleDelegate>
@required
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar;

Flutter中的iOS插件遵守FlutterPlugin協議且必須實現+registerWithRegistrar:方法

以camera插件為例,CameraPlugin.swift是對外的Swift的接口包裝

public static func register(with registrar: FlutterPluginRegistrar) {let instance = CameraPlugin(/// 文件: FlutterEngine.mm/// <9> 從FlutterEngineRegistrar對象中獲取紋理的對象/// - (NSObject<FlutterTextureRegistry>*)textures {///   return _flutterEngine.textureRegistry;/// }registry: registrar.textures(),/// Returns a `FlutterBinaryMessenger` for creating Dart/iOS communication/// channels to be used by the plugin./// <10> 從FlutterEngineRegistrar返回Dart與iOS原生消息的對象messenger: registrar.messenger(),globalAPI: FCPCameraGlobalEventApi(binaryMessenger: registrar.messenger()),deviceDiscoverer: FLTDefaultCameraDeviceDiscoverer(),permissionManager: FLTCameraPermissionManager(permissionService: FLTDefaultPermissionService()),deviceFactory: { name in// TODO(RobertOdrowaz) Implement better error handling and remove non-null assertionFLTDefaultCaptureDevice(device: AVCaptureDevice(uniqueID: name)!)},captureSessionFactory: { FLTDefaultCaptureSession(captureSession: AVCaptureSession()) },captureDeviceInputFactory: FLTDefaultCaptureDeviceInputFactory(),captureSessionQueue: DispatchQueue(label: "io.flutter.camera.captureSessionQueue"))/// <11>設置Dart相機API的消息通道SetUpFCPCameraApi(registrar.messenger(), instance)}
registrar.messenger()

從前面可知registrar是一個FlutterEngineRegistrar

/// 文件: FlutterEngine.mm
@implementation FlutterEngineRegistrar {
...
- (NSObject<FlutterBinaryMessenger>*)messenger {/// 返回的是FlutterEngineRegistrar對象綁定的FlutterEngine中的binaryMessenger屬性return _flutterEngine.binaryMessenger;
}
...@implementation FlutterEngine {
...
FlutterBinaryMessengerRelay* _binaryMessenger;
.../// FlutterEngine對象中的binaryMessenger屬性是FlutterBinaryMessengerRelay對象
/// 且parent屬性關聯的是FlutterEngine對象
_binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
SetUpFCPCameraApi(registrar.messenger(), instance)

代碼執行進入文件 message.g.m

void SetUpFCPCameraApiWithSuffix(id<FlutterBinaryMessenger> binaryMessenger,NSObject<FCPCameraApi> *api, NSString *messageChannelSuffix) {messageChannelSuffix = messageChannelSuffix.length > 0? [NSString stringWithFormat:@".%@", messageChannelSuffix]: @"";/// Returns the list of available cameras./// 建立設備的相機可用列表API{FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]initWithName:[NSString stringWithFormat:@"%@%@",@"dev.flutter.pigeon.camera_avfoundation."@"CameraApi.getAvailableCameras",messageChannelSuffix]binaryMessenger:binaryMessengercodec:FCPGetMessagesCodec()];if (api) {NSCAssert([api respondsToSelector:@selector(availableCamerasWithCompletion:)],@"FCPCameraApi api (%@) doesn't respond to @selector(availableCamerasWithCompletion:)",api);[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {[api availableCamerasWithCompletion:^(NSArray<FCPPlatformCameraDescription *> *_Nullable output,FlutterError *_Nullable error) {callback(wrapResult(output, error));}];}];} else {[channel setMessageHandler:nil];}}.../// 綁定一系列相機操作的API

FlutterBasicMessageChannel類在FlutterChannel.m中,先初始化一個FlutterBasicMessageChannel對象,實現上只是接收外界參數

- (instancetype)initWithName:(NSString*)namebinaryMessenger:(NSObject<FlutterBinaryMessenger>*)messengercodec:(NSObject<FlutterMessageCodec>*)codectaskQueue:(NSObject<FlutterTaskQueue>*)taskQueue {self = [super init];NSAssert(self, @"Super init cannot be nil");_name = [name copy];_messenger = messenger;_codec = codec;_taskQueue = taskQueue;return self;
}
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) ...];

接著判斷入參的api是否不為空,api是生成的CameraPlugin對象,所以不為空,然后消息的回調

- (void)setMessageHandler:(FlutterMessageHandler)handler {/// 未自定義回調時,這里應該是多次調用則清空上一次的,然后再重新創建if (!handler) {if (_connection > 0) {[_messenger cleanUpConnection:_connection];_connection = 0;} else {[_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil];}return;}// Grab reference to avoid retain on self.// `self` might be released before the block, so the block needs to retain the codec to// make sure it is not released with `self`/// 從前面的代碼可以知道這個self即channel對象只有一個局部對象在持有,所以超過作用域會被回收,所以這里接收到codecNSObject<FlutterMessageCodec>* codec = _codec;FlutterBinaryMessageHandler messageHandler = ^(NSData* message, FlutterBinaryReply callback) {handler([codec decode:message], ^(id reply) {callback([codec encode:reply]);});};_connection = SetMessageHandler(_messenger, _name, messageHandler, _taskQueue);
}
static FlutterBinaryMessengerConnection SetMessageHandler(NSObject<FlutterBinaryMessenger>* messenger,NSString* name,FlutterBinaryMessageHandler handler,NSObject<FlutterTaskQueue>* taskQueue) {/// 是否要在指定的任務隊列上執行/// name在這里是 dev.flutter.pigeon.camera_avfoundation.CameraApi.getAvailableCameras.../// handler是設置的回調/// 發送給FlutterBinaryMessengerRelay對象if (taskQueue) {NSCAssert([messenger respondsToSelector:@selector(setMessageHandlerOnChannel:binaryMessageHandler:taskQueue:)],@"");return [messenger setMessageHandlerOnChannel:namebinaryMessageHandler:handlertaskQueue:taskQueue];} else {return [messenger setMessageHandlerOnChannel:name binaryMessageHandler:handler];}
}

進入到FlutterBinaryMessengerRelay對象的setMessageHandlerOnChannel:binaryMessageHandler:taskQueue:

文件: FlutterBinaryMessengerRelay.mm

- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channelbinaryMessageHandler:(FlutterBinaryMessageHandler)handlertaskQueue:(NSObject<FlutterTaskQueue>*)taskQueue {/// parent就是engine對象,因此又回到engine上setMessageHandlerOnChannel:binaryMessageHandler:                                         taskQueue:if (self.parent) {return [self.parent setMessageHandlerOnChannel:channelbinaryMessageHandler:handlertaskQueue:taskQueue];} else {FML_LOG(WARNING) << "Communicating on a dead channel.";return -1;}
}

文件: FlutterEngine.mm

- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channelbinaryMessageHandler:(FlutterBinaryMessageHandler)handlertaskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {NSParameterAssert(channel);if (_shell && _shell->IsSetup()) {/// 獲取原生平臺的線程,并傳入channel名,回調,任務隊列self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String,handler, taskQueue);/// std::unique_ptr<flutter::ConnectionCollection> _connections;/// ///  文件:connection_collection.mm///  ConnectionCollection::Connection   ConnectionCollection::AquireConnection(const std::string& name) {///   Connection nextConnection = ++counter_;///   connections_[name] = nextConnection;///   return nextConnection;/// }/// FlutterEngine對象中的連接集合屬性,AcquireConnection方法讓connections字典中key為channel的計數加1return _connections->AquireConnection(channel.UTF8String);} else {NSAssert(!handler, @"Setting a message handler before the FlutterEngine has been run.");// Setting a handler to nil for a channel that has not yet been set up is a no-op.return flutter::ConnectionCollection::MakeErrorConnection(-1);}
}

文件: platform_view_ios.h

/// 調用GetPlatformMessageHandlerIos即返回platform_message_handler_屬性
class PlatformViewIOS final : public PlatformView {
...
std::shared_ptr<PlatformMessageHandlerIos> GetPlatformMessageHandlerIos() const {return platform_message_handler_;}
...

文件: platform_view_ios.mm


PlatformViewIOS::PlatformViewIOS(PlatformView::Delegate& delegate,const std::shared_ptr<IOSContext>& context,__weak FlutterPlatformViewsController* platform_views_controller,const flutter::TaskRunners& task_runners): PlatformView(delegate, task_runners),ios_context_(context),platform_views_controller_(platform_views_controller),accessibility_bridge_([this](bool enabled) { PlatformView::SetSemanticsEnabled(enabled); }),/// 從初始化列表中可以看出platform_message_handler_的值通過GetPlatformTaskRunner獲取UI的主線程platform_message_handler_(new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {}

到這里Flutter的插件的原生代碼部分已經將channel,回調,執行隊列(可選)給原生平臺的主線程。

獲取可用相機列表

/// Dart端調用獲取可用攝像頭列表
final cameras = await availableCameras();

文件: camera_controller.dart

/// Completes with a list of available cameras.
///
/// May throw a [CameraException].
Future<List<CameraDescription>> availableCameras() async {return CameraPlatform.instance.availableCameras();
}

調用CameraPlatform對象的availableCameras方法

文件: camera_platform.dart

abstract class CameraPlatform extends PlatformInterface {/// Constructs a CameraPlatform.CameraPlatform() : super(token: _token);.../// Completes with a list of available cameras.////// This method returns an empty list when no cameras are available.Future<List<CameraDescription>> availableCameras() {throw UnimplementedError('availableCameras() is not implemented.');}

CameraPlatform是個抽象類,要找具體的實現。找到camera插件的pubspec.yaml

flutter:plugin:implements: cameraplatforms:ios:pluginClass: CameraPlugindartPluginClass: AVFoundationCamera

dartPluginClass: Optional. The Dart class that serves as the entry point for a Flutter plugin. This can be used with the Android, iOS, Linux macOS, and Windows platforms.

因此camera插件dart的入口應該是AVFoundationCamera這個類,它繼承了上面的CameraPlatform抽象類

文件: avfoundation_camera.dart

class AVFoundationCamera extends CameraPlatform {/// Creates a new AVFoundation-based [CameraPlatform] implementation instance.AVFoundationCamera({ CameraApi? api}): _hostApi = api ?? CameraApi();...Future<List<CameraDescription>> availableCameras() async {try {return (await _hostApi.getAvailableCameras()).map(cameraDescriptionFromPlatform).toList();} on PlatformException catch (e) {throw CameraException(e.code, e.message);}}

從上面代碼可知實際調用的是_hostApigetAvailableCameras方法

Future<List<PlatformCameraDescription>> getAvailableCameras() async {/// 前面建立channel時已經傳入了,這樣原生執行完相關方法后能通過channel調用對應的回調final String pigeonVar_channelName ='dev.flutter.pigeon.camera_avfoundation.CameraApi.getAvailableCameras$pigeonVar_messageChannelSuffix';/// A named channel for communicating with platform plugins using asynchronous message passing./// 創建異步消息final BasicMessageChannel<Object?> pigeonVar_channel =BasicMessageChannel<Object?>(pigeonVar_channelName,pigeonChannelCodec,binaryMessenger: pigeonVar_binaryMessenger,);/// 阻塞**發送消息**final List<Object?>? pigeonVar_replyList =await pigeonVar_channel.send(null) as List<Object?>?;/// 消息為空拋異常if (pigeonVar_replyList == null) {throw _createConnectionError(pigeonVar_channelName);} else if (pigeonVar_replyList.length > 1) {throw PlatformException(code: pigeonVar_replyList[0]! as String,message: pigeonVar_replyList[1] as String?,details: pigeonVar_replyList[2],);} else if (pigeonVar_replyList[0] == null) {throw PlatformException(code: 'null-error',message: 'Host platform returned null value for non-null return value.',);} else {/// 得到可用列表轉換為Flutter上的攝像頭描述的對象return (pigeonVar_replyList[0] as List<Object?>?)!.cast<PlatformCameraDescription>();}}
await pigeonVar_channel.send(null) as List<Object?>?;
/// Sends the specified [message] to the platform plugins on this channel.////// Returns a [Future] which completes to the received response, which may/// be null.Future<T?> send(T message) async {/// 調用binaryMessenger對象編碼發送消息 && 得到返回后再解碼return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message)));}

Flutter Dart端發送指定channel消息 && 調用原來添加的回調走到Flutter原生插件部分這里,比如當前走到獲取可用攝像頭的回調

...
[api availableCamerasWithCompletion:^(NSArray<FCPPlatformCameraDescription *> *_Nullable output,FlutterError *_Nullable error) {callback(wrapResult(output, error));}];

即調用api(CameraPlugin)的availableCamerasWithCompletion:方法

extension CameraPlugin: FCPCameraApi {public func availableCameras(completion: @escaping ([FCPPlatformCameraDescription]?, FlutterError?) -> Void) {captureSessionQueue.async { [weak self] inguard let strongSelf = self else { return }var discoveryDevices: [AVCaptureDevice.DeviceType] = [.builtInWideAngleCamera,.builtInTelephotoCamera,].../// 前置,后置等攝像頭,然后統一添加到reply這個數組對象for device in devices {var lensFacing: FCPPlatformCameraLensDirectionswitch device.position {case .back:lensFacing = .backcase .front:lensFacing = .frontcase .unspecified:...}reply.append(cameraDescription)}/// 最后執行callback(wrapResult(output, error));/// 獲取成功后,Flutter的Dart部分可以獲取到可用的攝像頭列表completion(reply, nil)
}

總結

17486664768352.jpg

  1. 原生插件將方法的channel(約定的字符串),回調設置給Flutter原生平臺代碼(C++ && OC)
  2. Flutter的Dart層發送消息并傳遞channel,Flutter根據channel找到設置的回調(Dart -> C++ && OC),然后執行原生插件提供的方法(C++ && OC)
  3. 獲取執行結果并返回給Flutter的原生平臺代碼(C++ && OC),再發送給Dart代碼(C++ && OC -> Dart)

參考

  1. camera
  2. 使用 Camera 插件實現拍照功能
  3. do-not-use-buildcontexts-across-async-gaps
  4. Flutter pubspec options
  5. 一張圖理解Flutter中Dart與原生環境通信

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/83562.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/83562.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/83562.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于 Amazon Q Developer CLI 和 Amazon Bedrock Knowledge Bases 實現智能問答系統

1. 引言 傳統企業通常將常見問題&#xff08;FAQ&#xff09;發布在網站上&#xff0c;方便客戶自助查找信息。然而&#xff0c;隨著生成式 AI 技術的迅速發展與商業滲透&#xff0c;這些企業正積極探索構建智能問答系統的新途徑。這類系統不僅能顯著提升客戶體驗&#xff0c;…

Go 為何天生適合云原生?

當前我們正處在 AI 時代&#xff0c;但是在基礎架構領域&#xff0c;仍然處在云原生時代。云原生仍然是當前時代的風口之一。作為一個 Go 開發者&#xff0c;職業進階的下一站就是學習云原生技術。作為 Go 開發者學習云原生技術有得天獨厚的優勢&#xff0c;這是因為 Go 天生適…

Mac查看MySQL版本的命令

通過 Homebrew 查看&#xff08;如果是用 Homebrew 安裝的&#xff09; brew info mysql 會顯示你安裝的版本、路徑等信息。 你的終端輸出顯示&#xff1a;你并沒有安裝 MySQL&#xff0c;只是查詢了 brew 中的 MySQL 安裝信息。我們一起來看下重點&#xff1a; &#x1f9fe…

Kafka ACK機制詳解:數據可靠性與性能的權衡之道

在分布式消息系統中&#xff0c;消息確認機制是保障數據可靠性的關鍵。Apache Kafka 通過 ACK&#xff08;Acknowledgment&#xff09;機制 實現了靈活的數據確認策略&#xff0c;允許用戶在 數據可靠性 和 系統性能 之間進行權衡。本文將深入解析 Kafka ACK 機制的工作原理、配…

FastMCP:構建 MCP 服務器和客戶端的高效 Python 框架

在人工智能領域&#xff0c;模型上下文協議&#xff08;Model Context Protocol&#xff0c;簡稱 MCP&#xff09;作為一種標準化的協議&#xff0c;為大型語言模型&#xff08;LLM&#xff09;提供了豐富的上下文和工具支持。而 FastMCP 作為構建 MCP 服務器和客戶端的 Python…

動態庫導出符號與extern “C“

1. windows下動態庫導出符號 根據C/C語法規則&#xff0c;函數聲明中的修飾符&#xff08;如__declspec(dllexport)&#xff09;可以放在返回類型之前或返回類型之后、函數名之前。這兩種方式在功能上是等價的&#xff0c;編譯器會以相同的方式處理。 __declspec(dllexport) …

Linux(9)——進程(控制篇——下)

目錄 三、進程等待 1&#xff09;進程等待的必要性 2&#xff09;獲取子進程的status 3&#xff09;進程的等待方法 wait方法 waitpid方法 多進程創建以及等待的代碼模型 非阻塞的輪訓檢測 四、進程程序替換 1&#xff09;替換原理 2&#xff09;替換函數 3&…

Datatable和實體集合互轉

1.使用已廢棄的 JavaScriptSerializer&#xff0c;且反序列化為弱類型 ArrayList。可用但不推薦。 using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Web; using Sy…

阿里云服務器ECS詳解:云服務器是什么,云服務器優勢和應用場景及參考

云服務器ECS是阿里云眾多云產品中&#xff0c;最受用戶關注的產品&#xff0c;阿里云服務器提供多樣化的計算能力&#xff0c;支持x86、Arm架構&#xff0c;涵蓋CPU、GPU等多種服務器類型&#xff0c;滿足各種用戶需求。其便捷易用特性包括分鐘級交付、通用API和性能監控框架&a…

【Oracle】游標

個人主頁&#xff1a;Guiat 歸屬專欄&#xff1a;Oracle 文章目錄 1. 游標基礎概述1.1 游標的概念與作用1.2 游標的生命周期1.3 游標的分類 2. 顯式游標2.1 顯式游標的基本語法2.1.1 聲明游標2.1.2 帶參數的游標 2.2 游標的基本操作2.2.1 完整的游標操作示例 2.3 游標屬性2.3.1…

pikachu靶場通關筆記11 XSS關卡07-XSS之關鍵字過濾繞過(三種方法滲透)

目錄 一、源碼分析 1、進入靶場 2、代碼審計 3、攻擊思路 二、滲透實戰 1、探測過濾信息 2、注入Payload1 3、注入Payload2 4、注入Payload3 本系列為通過《pikachu靶場通關筆記》的XSS關卡(共10關&#xff09;滲透集合&#xff0c;通過對XSS關卡源碼的代碼審計找到安…

XML 元素:基礎、應用與優化

XML 元素:基礎、應用與優化 引言 XML(可擴展標記語言)作為一種數據交換的標準格式,廣泛應用于互聯網數據交換、數據存儲等領域。XML 元素是 XML 文檔的核心組成部分,本文將深入探討 XML 元素的概念、特性、應用以及優化方法。 一、XML 元素概述 1.1 XML 元素的定義 X…

【Axure高保真原型】交通事故大屏可視化分析案例

今天和大家分享交通事故大屏可視化分析案例的原型模板&#xff0c;包括餅圖分類分析、動態顯示發生數、柱狀圖趨勢分析、中部地圖展示最新事故發現地點和其他信息、右側列表記錄發生事故的信息…… 通過多種可視化圖表展示分析結果&#xff0c;具體效果可以點擊下方視頻觀看或…

HCIP(BGP基礎)

一、BGP 基礎概念 1. 網絡分類與協議定位 IGP&#xff08;內部網關協議&#xff09;&#xff1a;用于自治系統&#xff08;AS&#xff09;內部路由&#xff0c;如 RIP、OSPF、EIGRP&#xff0c;關注選路效率、收斂速度和資源占用。EGP&#xff08;外部網關協議&#xff09;&a…

【HarmonyOS 5】 ArkUI-X開發中的常見問題及解決方案

一、跨平臺編譯與適配問題 1. 平臺特定API不兼容 ?問題現象?&#xff1a;使用Router模塊的replaceUrl或startAbility等鴻蒙專屬API時&#xff0c;編譯跨平臺工程報錯cant support crossplatform application。 ?解決方案?&#xff1a; 改用ohos.router的跨平臺封裝API&a…

Matlab2018a---安裝教程

目錄 壹 | 引 言 貳 | 安裝環境 叁 | 安 裝 肆 | 結 語 壹 | 引 言 大家好&#xff0c;我是子正。 最近想學習一下DSP數字信號處理有關的知識&#xff0c;要用到Matlab進行數據處理&#xff0c;于是又重新把Matlab撿了回來; 記得上學那會兒用的還是Matlab2012a&#xff…

分布式流處理與消息傳遞——Kafka ISR(In-Sync Replicas)算法深度解析

Java Kafka ISR&#xff08;In-Sync Replicas&#xff09;算法深度解析 一、ISR核心原理 #mermaid-svg-OQtnaUGNQ9PMgbW0 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-OQtnaUGNQ9PMgbW0 .error-icon{fill:#55222…

ARM GIC V3概述

中斷類型 locality- specific peripheral interrupt&#xff08;LPI&#xff09;&#xff1a;LPI是一個有針對性的外設中斷&#xff0c;通過affinity路由到特定的PE。 為非安全group1中斷邊沿觸發可以通過its進行路由沒有active狀態&#xff0c;所以不需要明確的停用操作LPI總…

藍橋杯國賽訓練 day1

目錄 k倍區間 舞獅 交換瓶子 k倍區間 取模后算組合數就行 import java.util.HashMap; import java.util.Map; import java.util.Scanner;public class Main {static Scanner sc new Scanner(System.in);public static void main(String[] args) {solve();}public static vo…

安裝和配置 Nginx 和 Mysql —— 一步一步配置 Ubuntu Server 的 NodeJS 服務器詳細實錄6

前言 昨天更新了四篇博客&#xff0c;我們順利的 安裝了 ubuntu server 服務器&#xff0c;并且配置好了 ssh 免密登錄服務器&#xff0c;安裝好了 服務器常用軟件安裝, 配置好了 zsh 和 vim 以及 通過 NVM 安裝好Nodejs&#xff0c;還有PNPM包管理工具 。 作為服務器的運行…