0. 环境:

电脑:Windows10

Android Studio: 2024.3.2

编程语言: Java

Gradle version:8.11.1

Compile Sdk Version:35

Java 版本:Java11

1. 本篇文章涉及到的内容

lifecycle

livedata

databinding

viewModel

2. lifecycle简单使用

2.1注解的方法已过时

以MVP架构为例:

我们在basePresenter中实现lifecycleObserver的监听即可:

package com.liosen.androidnote.presenter;import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;public class BasePresenter implements LifecycleObserver {@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)void onCreateX(LifecycleOwner owner) {}@OnLifecycleEvent(Lifecycle.Event.ON_START)void onStartX(LifecycleOwner owner) {}@OnLifecycleEvent(Lifecycle.Event.ON_STOP)void onStopX(LifecycleOwner owner) {}@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)void onResumeX(LifecycleOwner owner) {}@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)void onPauseX(LifecycleOwner owner) {}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)void onDestroyX(LifecycleOwner owner) {}@OnLifecycleEvent(Lifecycle.Event.ON_ANY)void onAny(LifecycleOwner owner) {}
}

此时,我们要在实际presenter中监听生命周期,就可以继承基类来实现

package com.liosen.androidnote.presenter;import androidx.lifecycle.LifecycleOwner;public class FruitPresent extends BasePresenter{//省略业务代码···// ------------------------------- 此时,可以监听activity的生命周期 -------------------------------@Overridevoid onCreateX(LifecycleOwner owner) {super.onCreateX(owner);}@Overridevoid onStartX(LifecycleOwner owner) {super.onStartX(owner);}@Overridevoid onStopX(LifecycleOwner owner) {super.onStopX(owner);}@Overridevoid onResumeX(LifecycleOwner owner) {super.onResumeX(owner);}@Overridevoid onPauseX(LifecycleOwner owner) {super.onPauseX(owner);}@Overridevoid onDestroyX(LifecycleOwner owner) {super.onDestroyX(owner);}@Overridevoid onAny(LifecycleOwner owner) {super.onAny(owner);}
}

示例,mainActivity:

public class MainActivity extends BaseActivity<FruitPresent> {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);}@Overrideprotected FruitPresent createPresenter() {return new FruitPresent();}}

baseActivity的代码如下:

public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity {protected T presenter;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);presenter = createPresenter();init();}protected abstract T createPresenter();protected void init() {getLifecycle().addObserver(presenter);}}

细心的网友已经看出来了,目前版本@OnLifecycleEvent的注解已经过时了

我们通过源码可以看到最新的方法:

/*** Annotation that can be used to mark methods on {@link LifecycleObserver} implementations that* should be invoked to handle lifecycle events.** @deprecated This annotation required the usage of code generation or reflection, which should* be avoided. Use {@link DefaultLifecycleObserver} or* {@link LifecycleEventObserver} instead.*/
@SuppressWarnings("unused")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Deprecated
public @interface OnLifecycleEvent {Lifecycle.Event value();
}

2.2 使用DefaultLifecycleObserver

直接实现DefaultLifecycleObserver,就可以重写这些方法了

3. livedata的使用

3.1 livedata的简单使用

先写一个最简单的viewModel:

public class TextViewModel extends ViewModel {private MutableLiveData<String> text;   // 用于记录文字public int count;   // 用于记录次数public MutableLiveData<String> getText() {if (text == null) {text = new MutableLiveData<>();}return text;}
}

 接着看示例代码:

public class MainActivity extends AppCompatActivity implements DefaultLifecycleObserver {private TextView textView;private Button button;private TextViewModel model;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);textView = findViewById(R.id.textView);button = findViewById(R.id.button);model = new ViewModelProvider(this).get(TextViewModel.class);observerChange();// 按钮点击一次,计数增加一次button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {test();}});}/*** 订阅 viewModel中,text的变化*/private void observerChange() {Observer<String> observer = new Observer<String>() {@Overridepublic void onChanged(String string) {// 收到数据变化时,通过setText 更新UItextView.setText(string);}};model.getText().observe(this, observer);}/*** 测试文字变化的代码*/private void test() {String text = "liosen" + model.count++;model.getText().setValue(text);}}

此时,我们每次点击按钮,count都会增加1次。然后通过viewModel储存数据,触发了数据变化。监听者监听到数据变化后,调用了onChanged,实现了UI更新。

好处是,即使发生了重新绘制(例如屏幕旋转)也可以保证数据不会被回收。重新加载页面时,也能显示livedata中缓存的数据。

3.2 livedata的消息通知

3.1中,我们学会了一个对象的数据存储。但是一个项目中,需要存储的对象肯定不止一个。如果每个对象都建一个viewModel,肯定会让项目非常不优雅。所以需要批量管理。于是,我们可以创建一个LiveDataBus

public class LiveDataBus {private Map<String, MutableLiveData<Object>> bus;   // hashMap用于存放订阅者private static LiveDataBus liveDataBus = new LiveDataBus();public LiveDataBus() {bus = new HashMap<>();}public static LiveDataBus getInstance() {return liveDataBus;}/*** 注册订阅者*/public synchronized <T> MutableLiveData<T> with(String key, Class<T> type) {if (!bus.containsKey(key)) {bus.put(key, new MutableLiveData<Object>());}return (MutableLiveData<T>) bus.get(key);}
}

此时,activity修改为:

public class MainActivity extends AppCompatActivity implements DefaultLifecycleObserver {private TextView textView;private Button button;private TextViewModel model;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);textView = findViewById(R.id.textView);button = findViewById(R.id.button);model = new ViewModelProvider(this).get(TextViewModel.class);observerChange();// 按钮点击一次,计数增加一次button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {test();}});}private void observerChange() {LiveDataBus.getInstance().with("text", String.class).observe(this, new Observer<String>() {@Overridepublic void onChanged(String string) {// 收到数据变化时,通过setText 更新UItextView.setText(string);}});}/*** 测试文字变化的代码*/private void test() {new Thread() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {// livedata 发消息通知观察者LiveDataBus.getInstance().with("text", // 这个key,为自定义。最终会储存在LiveDataBus中hashMap中String.class).postValue("livedata: liosen" + count++    // 这个是具体变化的内容);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}.start();}}

这样,我们就把每个viewModel都抽离出来,通过LiveDataBus中的map给保存好。

只需要每次通过

LiveDataBus.getInstance().with(key, String.class)

既可以postValue(),也可以observe();

这样就解决了项目中viewModel过多的问题

4. databinding和viewModel的使用

4.1 databinding的简单使用

可以参考我这篇文章:【安卓笔记】用MVC、MVP、MVVM来实现井字棋案例-CSDN博客。

我贴出文章中的部分内容:

······

4. MVVM实现井字棋功能
文件结构如下:(忽略mvc文件夹和mvp文件夹)

4.1 第一步增加dataBinding
在app级别(如果有使用其他module,那该module也需要增加)的build.gradle中,android下增加,如下所示:

android {···dataBinding {enable true}
}


 这里我插一嘴:dataBinding和viewBinding的区别:

viewBinding:省略findViewById 的功能dataBinding:除了viewBinding的功能,还能绑定data部分

4.2 修改activity.xml
 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><data><import type="android.view.View" /><variablename="viewModel"type="com.liosen.androidnote.mvvm.viewmodel.TicTacToeViewModel" /></data><LinearLayoutandroid:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="60dp"android:fitsSystemWindows="true"android:gravity="center_horizontal"android:orientation="vertical"tools:context=".TicTacToeActivity"><GridLayoutandroid:id="@+id/gl_chessboard"android:layout_width="wrap_content"android:layout_height="wrap_content"android:columnCount="3"android:rowCount="3"><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(0,0)}"android:text='@{viewModel.board["00"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(0,1)}"android:text='@{viewModel.board["01"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(0,2)}"android:text='@{viewModel.board["02"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(1,0)}"android:text='@{viewModel.board["10"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(1,1)}"android:text='@{viewModel.board["11"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(1,2)}"android:text='@{viewModel.board["12"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(2,0)}"android:text='@{viewModel.board["20"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(2,1)}"android:text='@{viewModel.board["21"]}' /><Buttonstyle="@style/chessboard_btn"android:onClick="@{()->viewModel.onClickedChessboard(2,2)}"android:text='@{viewModel.board["22"]}' /></GridLayout><LinearLayoutandroid:id="@+id/ll_winner"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"android:visibility="@{viewModel.winner == null ? View.GONE : View.VISIBLE}"><TextViewandroid:id="@+id/tv_winner"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="20dp"android:textSize="40sp"tools:text="X"android:text="@{viewModel.winner}"/><TextViewandroid:id="@+id/tv_tips"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{viewModel.result}"android:textSize="30sp" /></LinearLayout></LinearLayout></layout>

可以看到有一些新内容:

首先,必须要用<layout></layout>包裹原有的xml

然后,<variable>标签需要至少引入viewModel,其中name为自定义的名称,type为绑定的ViewModel。此处,我需要将该xml和TicTacToeViewModel.java 进行绑定。

最后,看到<Button>标签中onClick方法变成了

android:onClick="@{()->viewModel.onClickedChessboard(0,0)}"

格式为 "@{}" ,此处中间为lamda,viewModel中的onClickedChessboard方法,在后面的viewModel文件中会有。

text文字变成了

android:text='@{viewModel.board["00"]}'

数据来源为viewModel中的board,同样的 在viewModel文件中会有。

4.3 增加viewModel文件 
 

package com.liosen.androidnote.mvvm.viewmodel;import androidx.databinding.ObservableArrayMap;
import androidx.databinding.ObservableField;import com.liosen.androidnote.mvvm.model.GameState;
import com.liosen.androidnote.mvvm.model.Board;
import com.liosen.androidnote.mvvm.model.Player;public class TicTacToeViewModel {public Board model;public final ObservableArrayMap<String, String> board = new ObservableArrayMap<>(); // 此处为被观察者,被观察的数据为map,用于存 <棋盘格子, 棋手>public final ObservableField<String> winner = new ObservableField<>();  // 此处也为被观察者,被观察的数据为String类型的对象public final ObservableField<String> result = new ObservableField<>();public TicTacToeViewModel() {model = new Board();}public void reset() {model.restartGame();winner.set(null);board.clear();}public void onClickedChessboard(int row, int col) {Player player = model.mark(row, col);if (player != null) {// 棋盘格子 显示玩家X或者Oboard.put("" + row + col, player == null ? null : player.toString());if (model.getWinner() != null) {   // 如果胜利的棋手 不为空,则显示胜利的信息winner.set(model.getWinner() == null ? null : model.getWinner().toString());result.set("你赢了");} else if (model.getState() == GameState.FINISHED) {winner.set("");result.set("本局平局");}}}
}

4.4 最后activity文件
 

package com.liosen.androidnote.mvvm.view;import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.databinding.DataBindingUtil;import com.liosen.androidnote.R;
import com.liosen.androidnote.databinding.ActivityMainMvvmBinding;
import com.liosen.androidnote.mvvm.viewmodel.TicTacToeViewModel;public class TicTacToeMVVMActivity extends AppCompatActivity {// --------------------- View ---------------------TicTacToeViewModel viewModel = new TicTacToeViewModel();    //@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);
//        setContentView(R.layout.activity_main);   // 此时已经不需要该行代码了,通过下面两行实现xml和activity的绑定ActivityMainMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main_mvvm); // binding.setViewModel(viewModel);    //ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});// 重置游戏viewModel.reset();}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.menu_tictactoe, menu);return super.onCreateOptionsMenu(menu);}@Overridepublic boolean onOptionsItemSelected(@NonNull MenuItem item) {if (item.getItemId() == R.id.reset) {viewModel.reset();return true;} else {return super.onOptionsItemSelected(item);}}
}

activity中绑定viewModel即可。

甚至连findViewById也省了。如果需要在activity中操作UI,可以直接通过binding获取,例如:

binding.tvWinner

至于tvWinner是哪里来的:是由于在xml文件中,设置的id。这样生成binding文件(编译时自动生成)时,就会自动生成该field,可以通过binding获取到。 

······

 其中4. MVVM实现井字棋功能,就可以学会简单的使用。

4.2 结合lifecycle的使用

layout文件:

<?xml version="1.0" encoding="utf-8"?>
<layout><data><import type="android.view.View" /><variablename="viewModel"type="com.liosen.androidnote.viewmodel.TextViewModel" /></data><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{viewModel.text}"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="@{viewModel::test}"android:text="count++"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/textView" /></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

修改TextViewModel文件:

public class TextViewModel extends ViewModel {private MutableLiveData<String> text;   // 用于记录文字public int count;   // 用于记录次数public MutableLiveData<String> getText() {if (text == null) {text = new MutableLiveData<>();text.setValue("Hello World!");  // 初始化文字}return text;}/*** 测试文字变化的代码*/public void test(View view) {String text = "binding: liosen" + count++;getText().setValue(text);}
}

activity文件:

public class MainActivity extends AppCompatActivity {ActivityMainBinding binding;private TextViewModel model;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);model = new ViewModelProvider(this).get(TextViewModel.class);binding = DataBindingUtil.setContentView(this, R.layout.activity_main);binding.setViewModel(model);observerChange();}/*** 订阅 viewModel中,text的变化*/private void observerChange() {Observer<String> observer = new Observer<String>() {@Overridepublic void onChanged(String string) {// 收到数据变化时,通过setText 更新UIbinding.textView.setText(string);}};model.getText().observe(this, observer);}
}

可以看到,每次按钮的点击事件,实际上更新的是livedata中的text。由于我们在mainActivity中观察者观察了text的值,每当text的值发生变化时,就可以更新到UI当中。

4.3 viewModel

实际上,我们在3.1中已经实现了viewModel。3.2中也实现了viewModel。

3.1中的监听者在于自身viewModel

3.2中的监听者在activity中实现绑定了

两种都可以。

5. 写在最后

至此,我们就学会最基本的lifecycle、livedata、viewModel的基本使用了

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

84、逆向工程开发方法

逆向工程开发方法是一种通过分析现有产品、系统或代码来理解其设计原理、功能实现及潜在缺陷&#xff0c;并在此基础上进行改进、复制或创新的技术过程。它广泛应用于软件、硬件、机械、电子等多个领域&#xff0c;尤其在缺乏原始设计文档或需要快速掌握复杂系统时具有显著优势…

ospf单区域实验

拓扑图&#xff1a;AR1&#xff1a;[Huawei]ospf 1 router-id 1.1.1.1 [Huawei-ospf-1]area 0[Huawei-ospf-1-area-0.0.0.0]network 192.168.1.0 0.0.0.255&#xff08;1.当前网段会被ospf的进程1学习到然后通告出去&#xff1b;2.如果接口的IP地址处于这个网段中&#xff0c…

Linux命令基础完结篇

用户权限修改 chmod修改文件权限 文字设定法 u&#xff1a;所有者g&#xff1a;所属组o&#xff1a;其他人a&#xff1a;所有&#xff1a;添加权限-&#xff1a;删除权限&#xff1a;赋予权限数字设定法 r&#xff1a;4w&#xff1a;2x&#xff1a;1每一组权限&#xff1a;0~7举…

高效互联,ModbusTCP转EtherCAT网关赋能新能源电缆智能制造

在新能源汽车快速发展的背景下&#xff0c;新能源电缆作为关键组件&#xff0c;需满足耐高低温、阻燃、耐老化等严苛要求&#xff0c;这对生产线的工艺与设备提出了更高标准。为提升制造效率&#xff0c;某领先设备制造商创新采用**ModbusTCP转EtherCAT网关**技术&#xff0c;实…

Java_多线程_生产者消费者模型_互斥锁,阻塞队列

生产者消费者模型(Producer-Consumer Model)是计算机科学中一个经典的并发编程模型&#xff0c;用于解决多线程/多进程环境下的协作问题。 基本概念 生产者&#xff1a;负责生成数据或任务的实体 消费者&#xff1a;负责处理数据或执行任务的实体 缓冲区&#xff1a;生产者与消…

Vue3实现视频播放弹窗组件,支持全屏播放,音量控制,进度条自定义样式,适配浏览器小窗播放,视频大小自适配,缓冲loading,代码复制即用

效果图组件所需VUE3代码<template><div class"video-dialog" :class"fullScreen && video-dialog-full-screen"><el-dialogv-model"props.visible"draggable:show-close"false"title""centeralign-c…

LLM层归一化:γβ与均值方差的协同奥秘

LLM层归一化参数均值和方差;缩放和平移参数是什么 层归一化(Layer Normalization,LN)是深度学习中用于稳定神经网络训练的一种归一化技术 均值和方差参数用于对输入数据进行标准化处理,即将输入数据转换为均值为0、方差为1的标准正态分布 缩放因子γ\gammaγ:标准化后…

智慧场景:定制开发开源AI智能名片S2B2C商城小程序赋能零售新体验

摘要&#xff1a;智慧场景作为零售行业创新发展的关键载体&#xff0c;正深刻改变着消费者的生活方式。本文聚焦智慧零售模式下智慧场景的构建&#xff0c;以定制开发开源AI智能名片S2B2C商城小程序为切入点&#xff0c;深入探讨其在零售企业选址布局、商业模式创新、经营理念转…

QML WorkerScript

WorkerScript是QML中实现多线程编程的关键组件&#xff0c;它允许开发者将耗时操作移至后台线程执行&#xff0c;避免阻塞主UI线程&#xff0c;从而提升应用响应速度和用户体验。本文将全面介绍WorkerScript的核心机制、使用方法和最佳实践。WorkerScript核心机制WorkerScript通…

锐浪报表 Grid++Report 表头表尾的隐藏

设计锐浪表格的模板时&#xff0c;可以通过设计多个表头、表尾&#xff0c;表头、表尾中放入打印控件&#xff0c;可以打印相关的数据。在真实打印时&#xff0c;可以通过打印时让表头、表尾隐藏或显示&#xff0c;实现用户的表格样式。一、表头的指定1、 表头可以多个&#xf…

低速信号设计之 QSPI 篇

一、引言​ 在服务器技术不断演进的当下,对高效、稳定的数据存储和传输需求日益增长。QSPI(Quad Serial Peripheral Interface)总线作为一种高速、串行的外围设备接口,在服务器领域中发挥着关键作用。它为服务器中的各类存储设备及部分外围芯片与主处理器之间提供了快速可…

别只知道暴力循环!我从用户名校验功能中领悟到的高效字符集判断法(1684. 统计一致字符串的数目)

别只知道暴力循环&#xff01;我从用户名校验功能中领悟到的高效字符集判断法 &#x1f60e; 大家好&#xff0c;日常开发中&#xff0c;我们经常会遇到一些看似不起眼&#xff0c;却能成为性能瓶颈的小模块。今天&#xff0c;我想和大家分享一个我亲身经历的故事&#xff0c;…

力扣面试150题--在排序数组中查找元素的第一个和最后一个位置

Day 85 题目描述思路 当 nums[mid] < target 时&#xff0c;说明目标值在右侧&#xff0c;移动左指针 left mid 1 当 nums[mid] > target 时&#xff0c;说明目标值可能在当前位置或左侧&#xff0c;移动右指针 right mid - 1 循环结束后&#xff0c;left 指针会指向第…

C++实战:人脸识别7大核心实例

计算机视觉实例应用 基于C++的人脸识别实例 以下是一些基于C++的人脸识别实例的示例和实现方法,涵盖了多种技术和库的应用。这些例子可以帮助开发者快速上手并实现人脸识别功能。 OpenCV 基础人脸检测 使用OpenCV的预训练模型进行人脸检测是入门级示例。OpenCV自带Haar级联…

Uniapp中使用vue3语法

在setup语法糖中调用uniapp的页面生命周期 <script setup>import { onShow } from "dcloudio/uni-app"onShow(() > {//hanlder...}) </script>vue2混入在vue3中建议使用组合式API 新建baseHook.js import { ref } from "vue"; export fu…

C++vector(2)

2.vector深度剖析及模拟实现 2.1std::vector的核心框架接口的模拟实现bit::vector vector的模拟实现 2.2 使用memcpy拷贝问题 假设模拟实现的vector中的reserve接口中&#xff0c;使用memcpy进行的拷贝&#xff0c;以下代码会发生什么问题&#xff1f; int main() {gxl::ve…

IPSec VPN -- 野蛮模式

一、野蛮模式简介野蛮模式VPN是指IPsec VPN中IKE协商采用野蛮模式&#xff08;Aggressive Mode&#xff09;的虚拟专用网络。它是IKE第一阶段协商的一种方式&#xff0c;与主模式相对&#xff0c;具有协商速度快但安全性稍低的特点。以下是具体介绍&#xff1a;1、工作原理&…

rk3588开发板使用硬件编码处理视频

开发板默认下载的ffmpeg是通用版&#xff0c;无法调用rk3588的硬件编码器&#xff0c;视频编码效率低。 nyanmisaka开发了用于jellyfin的ffmpeg&#xff0c;支持rk3588硬件编码器&#xff0c;编译方法&#xff1a; https://github.com/nyanmisaka/ffmpeg-rockchip/wiki/Compil…

`neutron router-gateway-set` 操作失败的可能原因及解决方案

根据提供的错误信息和搜索结果&#xff0c;neutron router-gateway-set 操作失败的可能原因及解决方案如下&#xff1a;一、常见错误原因数据库字符集配置问题&#xff08;中文名支持&#xff09; 表现&#xff1a;若路由器名称包含中文字符&#xff0c;可能因数据库字符集非UT…

(一)ZooKeeper 发展历史

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…