Android项目实战搭建 MVVM架构
View层
具体代码:
activity:
/*** @description:* 普通Activity基类,不带ViewModel,显示基本加载状态* 需要获取到子类的布局id用于databinding的绑定* @author YL Chen* @date 2024/9/4 21:34* @version 1.0*/
abstract class BaseActivity<VB : ViewDataBinding>(@LayoutRes layoutID: Int) :AppCompatActivity() { //此处不能将layoutId传递进去,否则会导致fragment加载但不显示open lateinit var mRefreshLayout: SmartRefreshLayout//仅供直接继承本类的子类调用,继承于BaseVMActivity的调用此对象的方法无效,// 因为setSuccessView方法先于getLoadView方法执行,导致加载状态无法被移除,继承于BaseVMActivity的想改变状态需使用mViewModel调用changeStateView方法open lateinit var mMultiplyStateView: MultiplyStateView//子view的布局idprivate var mLayoutId: Int = layoutID//子View的dataBindinglateinit var mBinding: VBoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//设置基础布局setContentView(R.layout.base_load_more_layout)//初始化界面initView()//初始化数据initData()}//初始化界面open fun initView() {//获取加载成功DataViewBindingmBinding = DataBindingUtil.inflate(layoutInflater,mLayoutId,null,false)//刷新框架mRefreshLayout = findViewById(R.id.refreshLayout)//找到基础布局中的自定义多状态View控件mMultiplyStateView = findViewById(R.id.multiply_state_view)// 将加载成功View布局添加到自定义多状态View中mMultiplyStateView.setSuccessView(mBinding.root)}//初始化数据abstract fun initData()
}/*** @description: 携带ViewModel的Activity基类,继承自BaseActivity* @author YL Chen* @date 2024/9/6 14:16* @version 1.0*/
abstract class BaseVMActivity<VB : ViewDataBinding, VM : BaseViewModel>(@LayoutRes layoutId: Int) :BaseActivity<VB>(layoutId) {//子类ViewModel实例lateinit var mViewModel: VM/*** 获取对应的ViewModel,并初始化数据*/override fun initData() {//dataLoading()mViewModel = getViewModel()!!//将子类的ViewModel和dataBinding联系起来,实现界面数据的自动更新//将xml布局对应的viewModel对象赋值到xml布局中声明的viewModel变量 即实现如:mBinding.viewModel = ViewModel() 的效果val variableId = getVariableId()if (variableId != -1) {mBinding.setVariable(variableId, mViewModel)//立即执行 Data Binding 中的挂起绑定//即Data Binding 会立即将 ViewModel 的属性和方法更新到布局文件中mBinding.executePendingBindings()}//初始化视图状态initViewState()//初始化ViewModel数据initVMData()//监听liveDataobserveLiveData()//设置状态页点击重新加载监听mMultiplyStateView.setOnReLodListener(mViewModel)}/*** 监听ViewModel中的LiveData*/open fun observeLiveData() {}/*** 初始化状态*/private fun initViewState() {mViewModel.mStateViewLiveData.observe(this) {when (it) {ViewStateEnum.VIEW_LOADING -> {LogUtils.d(this, "StateLayoutEnum.DATA_LOADING")dataLoading()}ViewStateEnum.VIEW_EMPTY -> {LogUtils.d(this, "StateLayoutEnum.DATA_ERROR")dataEmpty()}ViewStateEnum.VIEW_NET_ERROR -> {LogUtils.d(this, "StateLayoutEnum.NET_ERROR")netError()}ViewStateEnum.VIEW_LOAD_SUCCESS -> {LogUtils.d(this, "StateLayoutEnum.LOAD_SUCCESS")loadSuccess()}ViewStateEnum.VIEW_NONE -> {LogUtils.d(this, "StateLayoutEnum.NONE")}}}}/*** 数据加载成功*/open fun loadSuccess() {mMultiplyStateView.showSuccess()}/*** 网络加载失败*/open fun netError() {mMultiplyStateView.showNetError()}/*** 数据加载为空*/open fun dataEmpty() {mMultiplyStateView.showEmpty()}/*** 数据加载中*/open fun dataLoading() {mMultiplyStateView.showLoading()val loadingView = mMultiplyStateView.getLoadingView()val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)myLoadingView.startRotate()}/*** 初始化ViewModel数据*/abstract fun initVMData()/*** 获取xml绑定的variable* @return Int*///子类通过重写此方法返回子类对应xml文件中绑定的viewModel变量的idopen fun getVariableId(): Int {return -1}/*** 通过反射获取子类的ViewModel* @return VM?*/private fun getViewModel(): VM? {//这里获得到的是类的泛型的类型val type = javaClass.genericSuperclassif (type != null && type is ParameterizedType) {val actualTypeArguments = type.actualTypeArgumentsval tClass = actualTypeArguments[1]return ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application)).get(tClass as Class<VM>)}return null}
}
fragment:
/*** @description: 普通Fragment基类,不带ViewModel* @author YL Chen* @date 2024/9/6 16:19* @version 1.0*/
abstract class BaseFragment<VB : ViewDataBinding>(@LayoutRes layoutId: Int = 0) : Fragment() {open lateinit var mRefreshLayout: SmartRefreshLayoutopen lateinit var mMultiplyStateView: MultiplyStateView//子类的布局idprivate val mLayoutId: Int = layoutId//子View的dataBindinglateinit var mBinding: VBlateinit var mRootView: Viewoverride fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {//设置根布局mRootView = layoutInflater.inflate(R.layout.base_load_more_layout, container, false)return mRootView}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)//初始化视图initView()//初始化数据initData()}//初始化视图open fun initView() {//获取加载成功View的DataViewBindingmBinding = DataBindingUtil.inflate(layoutInflater, mLayoutId, null, false)//找到根布局的baseFrameLayoutmMultiplyStateView = mRootView.findViewById(R.id.multiply_state_view)//将子类加载成功View布局添加进去mMultiplyStateView.setSuccessView(mBinding.root)//获取到刷新框架mRefreshLayout = mRootView.findViewById(R.id.refreshLayout)}//初始化数据abstract fun initData()
}/*** @description: 携带ViewModel的fragment* @author YL Chen* @date 2024/9/6 21:52* @version 1.0*/
abstract class BaseVMFragment<VB : ViewDataBinding, VM : BaseViewModel>(@LayoutRes layoutId: Int) :BaseFragment<VB>(layoutId) {lateinit var mViewModel: VMoverride fun initData() {mViewModel = getViewModel()!!val variableId = getVariableId()if (variableId != -1) {mBinding.setVariable(getVariableId(), mViewModel)mBinding.executePendingBindings()}initState()initVMData()observeLiveData()//设置状态页点击重新加载监听mMultiplyStateView.setOnReLodListener(mViewModel)}/*** 获取子类xml 的Variable* @return Int*/open fun getVariableId(): Int {return -1}/*** 初始化状态*/private fun initState() {mViewModel.mStateViewLiveData.observe(this) {when (it) {ViewStateEnum.VIEW_LOADING -> {LogUtils.d(this, "StateLayoutEnum.DATA_LOADING")dataLoading()}ViewStateEnum.VIEW_EMPTY -> {LogUtils.d(this, "StateLayoutEnum.DATA_ERROR")dataEmpty()}ViewStateEnum.VIEW_NET_ERROR -> {LogUtils.d(this, "StateLayoutEnum.NET_ERROR")netError()}ViewStateEnum.VIEW_LOAD_SUCCESS -> {LogUtils.d(this, "StateLayoutEnum.LOAD_SUCCESS")loadSuccess()}ViewStateEnum.VIEW_NONE -> {LogUtils.d(this, "StateLayoutEnum.NONE")}}}}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)LogUtils.d(this, "onViewCreated")}/*** 数据加载成功*/open fun loadSuccess() {mMultiplyStateView.showSuccess()}/*** 网络加载失败*/open fun netError() {mMultiplyStateView.showNetError()}/*** 数据加载错误*/open fun dataEmpty() {mMultiplyStateView.showEmpty()}/*** 数据加载中*/open fun dataLoading() {mMultiplyStateView.showLoading()val loadingView = mMultiplyStateView.getLoadingView()val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)myLoadingView.startRotate()}/*** 通过反射获取子类的ViewModel* @return VM?*/private fun getViewModel(): VM? {//这里获得到的是类的泛型的类型val type = javaClass.genericSuperclassif (type != null && type is ParameterizedType) {val actualTypeArguments = type.actualTypeArgumentsval tClass = actualTypeArguments[1]return ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application)).get(tClass as Class<VM>)}return null}/*** 初始化ViewModel数据*/abstract fun initVMData()/*** 监听ViewModel中的LiveData*/open fun observeLiveData() {}}
思路:
BaseXXX:
通过泛型接收子类视图的ViewDataBInding类型,并通过构造方法获取子类的视图ID。
在onCreate()方法中,设置基础视图base_load_more_layout.xml,base_load_more_layout.xml中携带SmartRefreshLayout刷新框架和自定义的MultiplyStateView状态视图切换View,以实现刷新、加载更多视图和基础的状态视图切换功能;调用initView(),initData()方法
initView(): 初始化ViewDataBinding,使用构造方法中传递过来的子类视图ID进行初始化,并将其声明为公开的成员变量(mBinding),子类可直接调用,此ViewDataBinding为成功视图,并初始化SmartRefreshLayout和MultiplyStateView,声明为成员变量(mRefreshLayout和mMultiplyStateView),设置初始化的ViewDataBinding.root为MultiplyStateView的成功视图。
initData抽象方法强制子类重写,用于初始化数据。
BaseVMXXX:
通过泛型接受子类的ViewDataBinding和ViewModel,并通过构造方法获取子类视图ID,继承自BaseXXX,将泛型ViewDataBinding和视图ID传递给BaseXXX。
重写BaseXXX的initData()方法:
-
通过反射获取通过泛型传递进来的子类ViewModel的对象,并将其声明为公开的成员变量,子类可直接调用(mViewModel)
-
调用getVariableId()方法获取子类传递过来的在XML布局中绑定的ViewModel变量,并通过mBinding调用setsetVariable()方法将其与mViewModel绑定到一起,使得可以在子类的视图的一些事件可以绑定对应的ViewModel中。
-
调用initViewState()初始化当前View的视图状态,默认是Loding(加载中)状态。
-
调用initVMData()初始化ViewModel数据
-
调用observeLiveData()监听ViewMode中的LiveData
-
lifecycle.addObserver(mViewModel),使ViewModel监听对应View类的生命周期,相当于将ViewModel与View类进行绑定,使其只在View生命周期内响应,避免内存泄漏或者资源的错误释放。
-
mMultiplyStateView.setOnReLodListener(mViewModel):为状态视图设置重新加载监听,监听者为View类对应的ViewModel。
getVariableId():公开的方法,默认返回-1,供子类重写返回XML视图中绑定的ViewModel变量
initViewState():观察对应ViewModel中的切换视图LiveData变量(mStateViewLiveData),实现状态视图的初始化和切换。
observeLiveData():公开的方法,供子类重写监听观察对应ViewMode中的LiveData变量。
补充:
自定义多状态视图View代码:
package com.yl.wanandroid.ui.customimport android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.LayoutRes
import com.yl.wanandroid.R
import com.yl.wanandroid.model.ViewStateEnum
import com.yl.wanandroid.utils.LogUtils/*** @description: 自定义多状态View* @author YL Chen* @date 2024/9/29 10:47* @version 1.0*/open class MultiplyStateView : FrameLayout {private var mOnReLodListener: OnReLodListener? = nullprivate lateinit var params: LayoutParamsprivate lateinit var mInflater: LayoutInflaterprivate var mSuccessViewId: Int = 0private var mEmptyViewId: Int = 0private var mNetErrorViewId: Int = 0private var mLoadingViewId: Int = 0//四种展示的viewprivate var mLoadingView: View? = nullprivate var mSuccessView: View? = nullprivate var mNetErrorView: View? = nullprivate var mEmptyView: View? = null//当前视图状态private var currentState: ViewStateEnum = ViewStateEnum.VIEW_NONEconstructor(context: Context) : this(context, null)constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)constructor(context: Context, attrs: AttributeSet?, defy: Int) : super(context, attrs, defy) {initView(context, attrs)}private fun initView(context: Context, attrs: AttributeSet?) {//获取自定义属性val a =context.obtainStyledAttributes(attrs, R.styleable.MultiplyStateView)mLoadingViewId =a.getResourceId(R.styleable.MultiplyStateView_msv_loadingView, R.layout.view_loading)mNetErrorViewId =a.getResourceId(R.styleable.MultiplyStateView_msv_netErrorView, R.layout.view_net_error)mEmptyViewId =a.getResourceId(R.styleable.MultiplyStateView_msv_emptyView, R.layout.view_empty)mSuccessViewId = a.getResourceId(R.styleable.MultiplyStateView_msv_successView, 0)a.recycle()mInflater = LayoutInflater.from(context)params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)setLoadingView(mLoadingViewId)setEmptyView(mEmptyViewId)setNetErrorView(mNetErrorViewId)}//在 XML 布局文件中的视图被加载并且所有的子视图都被添加到父视图中之后执行
/* override fun onFinishInflate() {super.onFinishInflate()//展示加载页面LogUtils.d(this,"onFinishInflate-->showLoading")showLoading()val loadingView = getLoadingView()val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)myLoadingView.startRotate()}*///++++++++++++++++++++++++++++++++加载页面++++++++++++++++++++//动态加载并展示加载页面fun showLoading() {if (mLoadingView == null) {mLoadingView = mInflater.inflate(mLoadingViewId, null)}if (mLoadingView != null ) {removeAllViews()addView(mLoadingView, 0, params)} else {throw NullPointerException("you have to set loading view before that")}}/*** 提供方法给外界设置自定义加载页面* @param layoutId Int 布局Id*/open fun setLoadingView(@LayoutRes layoutId: Int) {setLoadingView(mInflater.inflate(layoutId, null))}open fun setLoadingView(view: View) {mLoadingView = view}/*** 获取加载页面*/fun getLoadingView(): View {if (null == mLoadingView) {mLoadingView = mInflater.inflate(mLoadingViewId, null)}return mLoadingView!!}//++++++++++++++++++++++++++++++++成功页面++++++++++++++++++++/*** 显示成功状态*/fun showSuccess() {if (null == mSuccessView) {mSuccessView = mInflater.inflate(mSuccessViewId, null)}if (mSuccessView != null) {LogUtils.d(this,"childCount-->${childCount}")val loadingView = getLoadingView()val myLoadingView = loadingView.findViewById<MyLoadingView>(R.id.my_loading_view)myLoadingView.stopRotate()removeAllViews()LogUtils.d(this,"childCount-->${childCount}")addView(mSuccessView, 0, params)LogUtils.d(this,"childCount-->${childCount}")currentState = ViewStateEnum.VIEW_LOAD_SUCCESS} else {throw NullPointerException("you have to set success view before that")}}/*** 设置自定义的成功页面** @param layoutResID*/fun setSuccessView(@LayoutRes layoutResID: Int) {setSuccessView(mInflater.inflate(layoutResID, null))}/*** 设置自定义的成功页面** @param view*/fun setSuccessView(view: View) {mSuccessView = viewLogUtils.d(this,"setSuccessView-->${view}")}/*** 获取成功页面*/fun getSuccessView(): View {if (null == mSuccessView) {mSuccessView = mInflater.inflate(mSuccessViewId, null)}return mSuccessView!!}//++++++++++++++++++++++++++++++++网络错误页面++++++++++++++++++++/*** 显示加载失败(网络错误)状态 带监听器的*/fun showNetError() {if (null == mNetErrorView) {mNetErrorView = mInflater.inflate(mNetErrorViewId, null)}if (mNetErrorView != null) {removeAllViews()addView(mNetErrorView, 0, params)currentState = ViewStateEnum.VIEW_NET_ERRORmNetErrorView!!.setOnClickListener { showReLoading() }} else {throw java.lang.NullPointerException("you have to set unknown view before that")}}/*** 设置自定义的网络异常** @param layoutResID*/fun setNetErrorView(@LayoutRes layoutResID: Int) {setNetErrorView(mInflater.inflate(layoutResID, null))}/*** 设置自定义的网络异常** @param view*/fun setNetErrorView(view: View) {mNetErrorView = view}/*** 设置获取网络错误页面*/fun getNetErrorView(): View {if (null == mNetErrorView) {mNetErrorView = mInflater.inflate(mNetErrorViewId, null)}return mNetErrorView!!}//++++++++++++++++++++++++++++++++空页面页面++++++++++++++++++++/*** 显示无数据状态*/fun showEmpty() {if (null == mEmptyView) {mEmptyView = mInflater.inflate(mEmptyViewId, null)}if (mEmptyView != null) {removeAllViews()addView(mEmptyView, 0, params)currentState = ViewStateEnum.VIEW_EMPTY} else {throw java.lang.NullPointerException("you have to set empty view before that")}}/*** 设置自定义的空页面** @param layoutResID*/fun setEmptyView(@LayoutRes layoutResID: Int) {setEmptyView(mInflater.inflate(layoutResID, null))}/*** 设置自定义的空页面** @param view*/fun setEmptyView(view: View) {mEmptyView = view}/*** 设置获取空页面*/fun getEmptyView(): View {if (null == mEmptyView) {mEmptyView = mInflater.inflate(mEmptyViewId, null)}return mEmptyView!!}/*** 再次加载数据*/private fun showReLoading() {//第一步重新loadingif (mOnReLodListener != null) {showLoading()mOnReLodListener!!.onReLoad()} else {//未设置重新加载回调LogUtils.e(this, "请设置重新加载监听")}}/*** 外部回调** @param onReLodListener*/fun setOnReLodListener(onReLodListener: OnReLodListener) {this.mOnReLodListener = onReLodListener}/*** 重新加载页面的回调接口*/interface OnReLodListener {fun onReLoad()}override fun onAttachedToWindow() {super.onAttachedToWindow()updateVisibility()}private fun updateVisibility() {// 获取父布局的可见性val parentVisibility = (parent as? View)?.visibility ?: View.VISIBLEvisibility = parentVisibility}// 如果需要监听父布局的可见性变化,可以重写这个方法override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)updateVisibility()}override fun onDetachedFromWindow() {super.onDetachedFromWindow()mOnReLodListener = null // 清除监听器引用}}
ViewModel层
BaseViewModel具体代码:
/*** @description: ViewModel基类* @author YL Chen* @date 2024/9/6 14:17* @version 1.0*/
abstract class BaseViewModel : ViewModel(),MultiplyStateView.OnReLodListener {/*** 控制状态视图的LiveData*/val mStateViewLiveData = MutableLiveData(ViewStateEnum.VIEW_LOADING)/*** 切换到UI线程* @param errorCallback SuspendFunction0<Unit> 错误回调* @param requestCall SuspendFunction0<Unit> 网络请求函数*/fun launchUI(errorCallback: suspend (Int?, String?) -> Unit,requestCall: suspend () -> Unit) {viewModelScope.launch(Dispatchers.Main) {//统一进行异常捕获safeApiCall(errorCallback, requestCall)}}/*** 对网络请求进行统一异常捕获* @param errorCallback SuspendFunction2<Int?, String?, Unit> 错误回调* @param requestCall SuspendFunction0<Unit> 网络请求函数* @return T? 网络请求成功数据*/private suspend fun<T> safeApiCall(errorCallback: suspend (Int?, String?) -> Unit,requestCall: suspend () -> T?): T? {try {//返回网络请求结果return requestCall()}catch (e: Exception){LogUtils.e(this@BaseViewModel,e.message.toString())e.printStackTrace()//统一异常处理//将异常转为ApiExceptionval apiException = ExceptionHandler.handleException(e)if (apiException.errCode == ERROR.UNKNOW_HOST.code || apiException.errCode == ERROR.NETWORD_ERROR.code){changeStateView(ViewStateEnum.VIEW_NET_ERROR)}errorCallback(apiException.errCode,apiException.errMsg)}return null}/*** 更改状态视图的状态*/fun changeStateView(state: ViewStateEnum) {// 对参数进行校验when (state) {ViewStateEnum.VIEW_LOADING -> {mStateViewLiveData.postValue(ViewStateEnum.VIEW_LOADING)}ViewStateEnum.VIEW_EMPTY -> {mStateViewLiveData.postValue(ViewStateEnum.VIEW_EMPTY)}ViewStateEnum.VIEW_NET_ERROR -> {mStateViewLiveData.postValue(ViewStateEnum.VIEW_NET_ERROR)}ViewStateEnum.VIEW_LOAD_SUCCESS -> {mStateViewLiveData.postValue(ViewStateEnum.VIEW_LOAD_SUCCESS)}ViewStateEnum.VIEW_NONE -> {mStateViewLiveData.postValue(ViewStateEnum.VIEW_NONE)}}}//错误视图点击回调函数override fun onReLoad() {//调用方法由子类实现onReload()}//子类可实现此方法实现界面重新加载open fun onReload(){}
}
思路:
继承自ViewModel,实现MultiplyStateView.OnReLodListener接口,重写onReload()方法,当用户点击重新加载状态视图时,回调此方法
声明mStateViewLiveData成员变量,给View层观察监听
封装changeStateView()方法,用于在网络请求数据相关操作后进行手动调用,改变mStateViewLiveData变量的值,View基层对此变量进行观察监听,实现状态视图的实时切换。
onReload()方法:公开的方法,用户自己选择是否重新,不是MultiplyStateView.OnReLodListener强制重写的,而是自定义的,在强制重写的onReload中调用。
封装safeApiCall()方法,传入网络请求函数和错误回调函数,用于调用Modle层中的网络请求方法,并对返回结果进行统一的异常处理,如当网络异常时,调用ViewModel.changStateView(ViewStateEnum.VIEW_NET_ERROR)改变状态视图。
封装launchUI()方法,用于切换到UI线程,在其中调用safeApiCall()方法。
Model层
BaseBean具体代码:网络请求返回的基础数据Bean类(与接口返回的数据结构有关)
data class BaseBean<T>(val data: T,val errorCode: Int, val errorMsg: String)/*** 网络返回数据类* @param T* @property errorCode String 0:正常,非0异常* @property errorMsg String* @property data T* @constructor*/data class BaseResult<T>(val data: T,val errorCode: Int, val errorMsg: String, ) {fun isFailed(): Boolean {return errorCode != 0}
}
BaseRepository具体代码:网络请求基础仓库
/*** @description: 网络请求基础仓库* @author YL Chen* @date 2024/9/10 17:06* @version 1.0*/
open class BaseRepository {/*** IO中处理请求,请求错误抛出自定义异常* @param requestCall SuspendFunction0<BaseResult<T>?>* @return T?*/suspend fun <T> requestResponse(requestCall: suspend () -> BaseResult<T>?): T? {val result = withContext(Dispatchers.IO) {withTimeout(Constant.CONNECT_TIME_OUT * 1000) {requestCall()}} ?: return nullLogUtils.e(this@BaseRepository,"result-->$result")if (result.isFailed()) {throw ApiException(result.errorCode, result.errorMsg)}return result.data}
}
相关文章:
Android项目实战搭建 MVVM架构
View层 具体代码: activity: /*** description:* 普通Activity基类,不带ViewModel,显示基本加载状态* 需要获取到子类的布局id用于databinding的绑定* author YL Chen* date 2024/9/4 21:34* version 1.0*/ abstract class BaseActivity<VB : ViewD…...
Mybatis的基础操作——03
写mybatis代码的方法有两种: 注解xml方式 本篇就介绍XML的方式 使用XML来配置映射语句能够实现复杂的SQL功能,也就是将sql语句写到XML配置文件中。 目录 一、配置XML文件的路径,在resources/mapper 的目录下 二、写持久层代码 1.添加mappe…...
React:React主流组件库对比
1、Material-UI | 官网 | GitHub | GitHub Star: 94.8k Material-UI 是一个实现了 Google Material Design 规范的 React 组件库。 Material UI 包含了大量预构建的 Material Design 组件,覆盖导航、滑块、下拉菜单等各种常用组件,并都提供了高度的可定制…...
奇迹科技:蓝牙网关赋能少儿篮球教育的创新融合案例研究
一、引言 本文研究了福建奇迹运动体育科技有限公司(简称‘奇迹科技’)如何利用其创新产品体系和桂花网蓝牙网关M1500,与少儿篮球教育实现深度融合。重点分析其在提升教学效果、保障训练安全、优化个性化教学等方面的实践与成效,为…...
分享最近前端面试遇到的一些问题
前情提要(分享个人情况,可以直接跳过) 先说一下我的个人情况,我是2026届的,目前是在找前端实习。 3月初,从3月3日开始在Boss上投简历。 分享我的个人故事,不想看可以直接滑到下面,…...
嵌入式基础知识学习:SPI通信协议是什么?
SPI(Serial Peripheral Interface)是串行外设接口的缩写,是一种广泛应用于嵌入式系统的高速同步串行通信协议,由摩托罗拉公司于20世纪80年代提出。以下是其核心要点: 一、SPI的核心定义与特点 基本特性 全双工同步通信…...
python每日十题(6)
】函数定义:函数是指一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需要调用其函数名即可。函数能提高应用的模块性和代码的重复利用率 在Python语言中,用关键字class来定义类 在Python语…...
1.Go - Hello World
1.安装Go依赖 https://go.dev/dl/ 根据操作系统选择适合的依赖,比如windows: 2.配置环境变量 右键此电脑 - 属性 - 环境变量 PS: GOROOT:Go依赖路径; GOPATH:Go项目路径; …...
优先队列 priority_queue详解
说到,priority_queue优先队列。必须先要了解啥是堆与运算符重载(我在下方有解释)。 否则只知皮毛,极易忘记寸步难行。 但在开头,还是简单的说下怎么用 首先,你需要调用 #include <queue> 在main函数中,声明…...
《信息系统安全》(第一次上机实验报告)
实验一 :网络协议分析工具Wireshark 一 实验目的 学习使用网络协议分析工具Wireshark的方法,并用它来分析一些协议。 二实验原理 TCP/IP协议族中网络层、传输层、应用层相关重要协议原理。网络协议分析工具Wireshark的工作原理和基本使用规则。 三 实…...
C++实现求解24点游戏
力扣原题:679. 24 点游戏 - 力扣(LeetCode) 判断四个数字能否通过加减乘除得到24点 使用回溯遍历四个数字的每一种组合,具体来说,每次从数组中选取两个数字以加减乘除四种方式得到一个新的数字,这样数组的…...
Java-腾讯云短信模板兼容阿里云短信模板-短信模板参数生成
最新版本更新 https://code.jiangjiesheng.cn/article/362?fromcsdn 模板 腾讯云:您好!{}的${},有{}发生{} 阿里云:您好!${orgName}的${monitorName},有${equipName}发生${status} 原腾讯云短信发送的代码…...
简要分析IPPROTO_TCP参数
IPPROTO_TCP是操作系统或网络编程中定义的一个 协议号常量,用于标识 传输控制协议(TCP)。其核心作用是 在传输层指定使用TCP协议,确保数据通过TCP的可靠传输机制进行通信。 一、定义与值 头文件:定义在<netinet/in.…...
SOFABoot-06-健康检查
前言 大家好,我是老马。 sofastack 其实出来很久了,第一次应该是在 2022 年左右开始关注,但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…...
如何理解java中Stream流?
在Java中,Stream 是 Java 8 引入的一个强大API,用于处理集合(如 List、Set、Map 等)数据的流式操作。它提供了一种声明式、函数式的编程风格,可以高效地进行过滤、映射、排序、聚合等操作。 Stream 的核心概念 流&…...
Android使用RxHttp进行国密4加密解密
国密SM4加解密问题汇总 前言国密4加解密工具类RxHttp统一加解密处理解密前言 为了网络安全需要对app内请求数据接口使用SM4国密4进行加解密操作,在实施的过程中遇到了些问题 也收获颇丰,特此记录 在线SM4加密测试网址: 点击此进入网址. 国密4加解密工具类 这里我使用的是b…...
【自学笔记】Linux基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 Linux 基础知识点总览目录Linux 简介文件和目录结构常用命令文件操作目录操作权限管理文本处理 Shell 脚本基础进程管理用户和组管理网络配置 总结 Linux 基础知识点…...
JavaScript与客户端开发
1、简介 简单的讲,JavaScript是一种脚本语言,为网站提供了一种在客户端运行程序的手段,通过它可以实现客户端数据验证、网页特效等功能。 JavaScript是一种基于对象和事件驱动(不懂啥意思,暂不管它)&…...
基于CNN的FashionMNIST数据集识别5——GoogleNet模型
源码 import torch from torch import nn from torchsummary import summaryclass Inception(nn.Module):def __init__(self, in_channels, c1, c2, c3, c4):super().__init__()self.ReLu nn.ReLU()#路径1self.p1_1 nn.Conv2d(in_channelsin_channels, out_channelsc1, kern…...
JVM垃圾回收笔记01-垃圾回收算法
文章目录 前言1. 如何判断对象可以回收1.1 引用计数法1.2 可达性分析算法查看根对象哪些对象可以作为 GC Root ?对象可以被回收,就代表一定会被回收吗? 1.3 引用类型1.强引用(StrongReference)2.软引用(SoftReference…...
【初探数据结构】树与二叉树
💬 欢迎讨论:在阅读过程中有任何疑问,欢迎在评论区留言,我们一起交流学习! 👍 点赞、收藏与分享:如果你觉得这篇文章对你有帮助,记得点赞、收藏,并分享给更多对数据结构感…...
numpy学习笔记10:arr *= 2向量化操作性能优化
numpy学习笔记10:arr * 2向量化操作性能优化 在 NumPy 中,直接对整个数组进行向量化操作(如 arr * 2)的效率远高于显式循环(如 for i in range(len(arr)): arr[i] * 2)。以下是详细的解释: 1. …...
蓝桥杯备考:二分答案之路标设置
最大距离,找最小空旷指数值,我们是很容易想到用二分的,我们再看看这个答案有没有二段性 是有这么个二段性的,我们只要二分就行了,但是二分的check函数是有点不好想的,我们枚举空旷值的时候,为了…...
回调方法传参汇总
文章目录 0. 引入问题1. 父子组件传值1.1 父传子:props1.2 子传父:$emit1.3 双向绑定:v-model 2. 多个参数传递3. 父组件监听方法传递其他值3.1 $event3.2 箭头方法 4. 子组件传递多个参数,父组件传递本地参数4.1 箭头函数 … 扩…...
在 Linux下使用 Python 3.11 和 FastAPI 搭建带免费证书的 HTTPS 服务器
在当今数字化时代,保障网站数据传输的安全性至关重要。HTTPS 协议通过使用 SSL/TLS 加密技术,能够有效防止数据在传输过程中被窃取或篡改。本教程将详细介绍如何在 Ubuntu 22.04 系统上,使用 Python 3.11 和 FastAPI 框架搭建一个带有免费 SS…...
XSS基础靶场练习
目录 1. 准备靶场 2. PASS 1. Level 1:无过滤 源码: 2. level2:转HTML实体 htmlspecialchars简介: 源码 PASS 3. level3:转HTML深入 源码: PASS 4. level4:过滤<> 源码: PASS: 5. level5:过滤on 源码…...
Redis核心机制(一)
目录 Redis的特性 1.速度快 2.以键值对方式进行存储 3.丰富的功能 4.客户端语言多 5.持久化 6.主从复制 7.高可用和分布式 Redis使用场景 Redis核心机制——持久化 RDB bgsave执行流程 编辑 AOF AOF重写流程 3.混合持久化(RDBAOF) Red…...
QGroupBox取消勾选时不禁用子控件
默认情况下,QGroupBox取消勾选会自动禁用子控件,如下图所示 那么如何实现取消勾选时不禁用子控件呢? 实现很简单,直接上代码了 connect(ui->groupBox, &QGroupBox::toggled, this, [](bool checked){if (checked false){…...
Go语言中package的使用规则《二》
在 Go 语言中,包(Package) 是代码组织和复用的核心单元。以下是其定义、引用规则及使用习惯的详细说明: 一、包的定义规则 目录与包名 一个包对应一个目录(文件夹),目录名通常与包名一致。 包名…...
MyBatis-Plus 自动填充:优雅实现创建/更新时间自动更新!
目录 一、什么是 MyBatis-Plus 自动填充? 🤔二、自动填充的原理 ⚙️三、实际例子:创建时间和更新时间字段自动填充 ⏰四、注意事项 ⚠️五、总结 🎉 🌟我的其他文章也讲解的比较有趣😁,如果喜欢…...
