Jetpack ViewBinding
文章目录
- Jetpack ViewBinding
- 概述
- ViewBinding优点
- 配置ViewBinding
- 使用
- 在Activity中使用
- 在Fragment中使用
- 在RecyclerView adapter中使用
- 在include标签中使用
- 不使用merge标签
- 使用merge标签
- 封装使用
- 基类封装,不使用反射
- 基类封装,使用反射
- 委托实现
- 源码分析
- 代码下载
Jetpack ViewBinding
概述
官网文档
通过视图绑定功能,您可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
ViewBinding优点
ViewBinding优点
- Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。
- 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。因此不存强制转换导致的异常风险。
与findViewById区别
- findViewById编写过于冗余。
- 类型仍然不安全。
与ButterKnife区别
- 官宣不维护,推荐使用ViewBinding。
- 类型仍然不安全。
- 对组件化项目不友好。
与Kotlin Android Extensions
- JetBrains废弃该插件。
- 类型仍然不安全。
- 性能偏低。
配置ViewBinding
Android Studio3.6以上
android {viewBinding {enabled = true}
}
Android Studio4.0以上
android {buildFeatures {viewBinding = true}
}
如果需要忽略某个布局文件,需要添加tools:viewBindingIgnore="true"
属性到布局中
<LinearLayout...tools:viewBindingIgnore="true" >...
</LinearLayout>
使用
当开启ViewBinding后,系统会为该模块中每个XML布局文件生成一个绑定类(转换为驼峰命名并在末尾添加Binding),每个绑定类均包含根视图已交具有id的所有视图的引用。
例如:布局文件名为activity_main.xml
,生成绑定类为ActivityMainBinding
在Activity中使用
使用流程
- 开启视图绑定功能后,系统会为该模块中的XML布局生成一个绑定类,每个绑定类都包含根布局和具有ID布局的引用。
- 调用绑定类的
inflate()
方法获取绑定类对象。 - 调用绑定类对象的
getRoot()
方法获取根布局传递到setContentView()
。
XML布局
activity_view_binding_simple.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center_horizontal"android:orientation="vertical"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content" /><TextViewandroid:id="@+id/tv_age"android:layout_width="wrap_content"android:layout_height="wrap_content" /><ImageViewandroid:id="@+id/iv_avatar"android:layout_width="wrap_content"android:layout_height="wrap_content" /><Buttonandroid:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="确定" />
</LinearLayout>
Activity类
class ViewBindingSimpleActivity : BaseActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val viewBinding = ActivityViewBindingSimpleBinding.inflate(layoutInflater)setContentView(viewBinding.root)viewBinding.tvName.text = "hello world"viewBinding.tvAge.text = 18.toString()viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}}
}
在Fragment中使用
方式一
- 调用绑定类的
inflate()
方法,获取绑定类对象。 - 再调用
getRoot()
方法获取根布局。 - 在
onCreateView()
方法返回根布局,使其成为屏幕上的活动视图。 - 由于Fragment的存在时间比视图长。因此需要在Fragment的
onDestroyView()
方法中清除对绑定类对象的所有引用。
class MyFragment : BaseFragment() {private var _viewBinding: FragmentMyBinding? = nullprivate val viewBindingget() = _viewBinding!!override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {_viewBinding = FragmentMyBinding.inflate(inflater, container, false)return viewBinding.root}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)viewBinding.tvName.text = "hello world"viewBinding.tvAge.text = 18.toString()viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}}override fun onDestroyView() {super.onDestroyView()_viewBinding = null}
}
方式二
class MyFragment : BaseFragment() {private var _viewBinding: FragmentMyBinding? = nullprivate val viewBindingget() = _viewBinding!!override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {return inflater.inflate(R.layout.fragment_my, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)_viewBinding = FragmentMyBinding.bind(view)viewBinding.tvName.text = "hello world"viewBinding.tvAge.text = 18.toString()viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}}override fun onDestroyView() {super.onDestroyView()_viewBinding = null}
}
在RecyclerView adapter中使用
class MyAdapter(context: Context, private val data: ArrayList<String>) :RecyclerView.Adapter<MyAdapter.ViewHolder>() {val layoutInflater: LayoutInflater = LayoutInflater.from(context)class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {val text: TextView = itemView.findViewById(R.id.text)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val viewBinding = ItemTextBinding.inflate(layoutInflater, parent, false)return ViewHolder(viewBinding.root)}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.text.text = data[position]}override fun getItemCount(): Int {return data.size}
}
在include标签中使用
ViewBinding可以与<include>
标签一起使用
不使用merge标签
- 一定要给
<include>
标签定义id,使用该id访问布局中的控件。
XML布局
title_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#2196F3"android:minHeight="50dp"android:padding="10dp"><TextViewandroid:id="@+id/back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:text="返回" /><TextViewandroid:id="@+id/title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="标题位置" /><TextViewandroid:id="@+id/confirm"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:text="确定" /></RelativeLayout>
Activity的XML布局
activity_vbinclude.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"android:layout_height="match_parent"tools:context=".IncludeActivity"><includeandroid:id="@+id/titleBar"layout="@layout/titlebar" /></LinearLayout>
在include标签中使用
public class IncludeActivity extends AppCompatActivity {private Context context;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);context = this;ActivityIncludeBinding binding = ActivityIncludeBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());binding.titleBar.title.setText("这是一个标题");binding.titleBar.back.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(context, "返回", Toast.LENGTH_SHORT).show();}});binding.titleBar.confirm.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(context, "确定", Toast.LENGTH_SHORT).show();}});}
}
使用merge标签
<merge>
标签有利于减少布局层次。- 需要使用
bind()
方法绑定根视图。 - 不能给
<include>
标签设置id。
布局:detail_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"><ImageViewandroid:id="@+id/ivDetail"android:layout_width="100dp"android:layout_height="100dp"android:scaleType="fitXY" />
</merge>
Activity的XML布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".IncludeActivity"><include layout="@layout/detail_layout" /></LinearLayout>
在include标签中使用
package com.example.viewbindingdemo;import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import com.example.viewbindingdemo.databinding.ActivityIncludeBinding;
import com.example.viewbindingdemo.databinding.DetailLayoutBinding;public class IncludeActivity extends AppCompatActivity {private Context context;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);context = this;ActivityIncludeBinding binding = ActivityIncludeBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());DetailLayoutBinding detailBinding = DetailLayoutBinding.bind(binding.getRoot());detailBinding.ivDetail.setImageResource(R.mipmap.ic_launcher);}
}
封装使用
基类封装,不使用反射
基类封装
abstract class BindingActivity<VB : ViewBinding> : BaseActivity() {private lateinit var _viewBinding: VBprotected val mViewBinding get() = _viewBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)_viewBinding = getViewBinding()setContentView(_viewBinding.root)}abstract fun getViewBinding(): VB
}abstract class BindingFragment<VB : ViewBinding> : BaseFragment() {private lateinit var _viewBinding: VBprotected val mViewBinding get() = _viewBindingoverride fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {_viewBinding = getViewBinding(inflater, container)return _viewBinding.root}abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
}
使用
class OneActivity : BindingActivity<ActivityOneBinding>() { override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 18.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}}override fun getViewBinding(): ActivityOneBinding {return ActivityOneBinding.inflate(layoutInflater)}
}class OneFragment : BindingFragment<FragmentOneBinding>() {override fun getViewBinding(inflater: LayoutInflater,container: ViewGroup?): FragmentOneBinding {return FragmentOneBinding.inflate(inflater, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 18.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}}
}
基类封装,使用反射
基类封装
abstract class BindingActivity<VB : ViewBinding> : BaseActivity() {private lateinit var _viewBinding: VBprotected val mViewBinding get() = _viewBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mContext = thisval type = javaClass.genericSuperclassif (type is ParameterizedType) {val clz = type.actualTypeArguments[0] as Class<*>val method = clz.getMethod("inflate", LayoutInflater::class.java)_viewBinding = method.invoke(null, layoutInflater) as VBsetContentView(_viewBinding.root)}}
}abstract class BindingFragment<VB : ViewBinding> : BaseFragment() {private var _viewBinding: VB? = nullprotected val mViewBinding get() = _viewBinding!!override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {val type = javaClass.genericSuperclassif (type is ParameterizedType) {val clz = type.actualTypeArguments[0] as Class<*>val method = clz.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)_viewBinding = method.invoke(null, inflater, container, false) as VB}return mViewBinding.root}override fun onDestroyView() {super.onDestroyView()_viewBinding = null}
}
使用
class TwoActivity : BindingActivity<ActivityTwoBinding>() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 28.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello2", Toast.LENGTH_SHORT).show()}}
}class TwoFragment : BindingFragment<FragmentTwoBinding>() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 28.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello2", Toast.LENGTH_SHORT).show()}}
}
委托实现
封装
//Activity ViewBindinginline fun <reified VB : ViewBinding> ComponentActivity.viewBindings(noinline factory: (LayoutInflater) -> VB,setContentView: Boolean = true
) = ActivityViewBindingDelegate1(factory, setContentView)inline fun <reified VB : ViewBinding> ComponentActivity.viewBindings(setContentView: Boolean = true) =ActivityViewBindingDelegate2(VB::class.java, setContentView)class ActivityViewBindingDelegate1<VB : ViewBinding>(private val factory: (LayoutInflater) -> VB,private val setContentView: Boolean,
) : ReadOnlyProperty<ComponentActivity, VB> {private var viewBinding: VB? = nulloverride fun getValue(thisRef: ComponentActivity, property: KProperty<*>): VB {viewBinding?.let { return it }viewBinding = factory(thisRef.layoutInflater).also { viewBinding ->if (setContentView) thisRef.setContentView(viewBinding.root)}return viewBinding!!}
}class ActivityViewBindingDelegate2<VB : ViewBinding>(private val clazz: Class<VB>,private val setContentView: Boolean,
) : ReadOnlyProperty<ComponentActivity, VB> {private var viewBinding: VB? = nulloverride fun getValue(thisRef: ComponentActivity, property: KProperty<*>): VB {viewBinding?.let { return it }val inflateMethod = clazz.getMethod("inflate", LayoutInflater::class.java)viewBinding =(inflateMethod.invoke(null, thisRef.layoutInflater) as VB).also { viewBinding ->if (setContentView) thisRef.setContentView(viewBinding.root)}return viewBinding!!}
}
//Fragment ViewBindinginline fun <reified VB : ViewBinding> Fragment.viewBindings(noinline factory: (View) -> VB) =FragmentViewBindingDelegate1(factory)inline fun <reified VB : ViewBinding> Fragment.viewBindings() =FragmentViewBindingDelegate2(VB::class.java)class FragmentViewBindingDelegate1<VB : ViewBinding>(private val factory: (View) -> VB,
) : ReadOnlyProperty<Fragment, VB> {private var viewBinding: VB? = nulloverride fun getValue(thisRef: Fragment, property: KProperty<*>): VB {viewBinding?.let { return it }val lifecycle = thisRef.viewLifecycleOwner.lifecycleviewBinding = factory(thisRef.requireView())if (lifecycle.currentState == Lifecycle.State.DESTROYED) {Log.w("TAG","Access to viewBinding after Lifecycle is destroyed or hasn't created yet. The instance of viewBinding will be not cached.")} else {thisRef.viewLifecycleOwnerLiveData.observe(thisRef) { viewLifecycleOwner ->viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {override fun onDestroy(owner: LifecycleOwner) {viewBinding = null}})}}return viewBinding!!}
}class FragmentViewBindingDelegate2<VB : ViewBinding>(clazz: Class<VB>,
) : ReadOnlyProperty<Fragment, VB> {private var viewBinding: VB? = nullprivate val bindMethod = clazz.getMethod("bind", View::class.java)override fun getValue(thisRef: Fragment, property: KProperty<*>): VB {viewBinding?.let { return it }val lifecycle = thisRef.viewLifecycleOwner.lifecycleviewBinding = bindMethod.invoke(null, thisRef.requireView()) as VBif (lifecycle.currentState == Lifecycle.State.DESTROYED) {Log.w("TAG","Access to viewBinding after Lifecycle is destroyed or hasn't created yet. The instance of viewBinding will be not cached.")} else {thisRef.viewLifecycleOwnerLiveData.observe(thisRef) { viewLifecycleOwner ->viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {override fun onDestroy(owner: LifecycleOwner) {viewBinding = null}})}}return viewBinding!!}
}
使用
class ThreeActivity : BaseActivity() {//方式一private val viewBinding: ActivityThreeBinding by viewBindings(ActivityThreeBinding::inflate)//方式二// private val viewBinding: ActivityThreeBinding by viewBindings()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.tvName.text = "小白"viewBinding.tvAge.text = 38.toString()viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello3", Toast.LENGTH_SHORT).show()}}
}class ThreeFragment : BaseFragment(R.layout.fragment_three) {//方式一
// private val viewBinding: FragmentThreeBinding by viewBindings(FragmentThreeBinding::bind)//方式二private val viewBinding: FragmentThreeBinding by viewBindings()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.tvName.text = "小白33"viewBinding.tvAge.text = 338.toString()viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello33", Toast.LENGTH_SHORT).show()}}
}
源码分析
代码下载
Jetpack ViewBinding相关推荐
- Jetpack——LiveData与ViewBinding
这篇文章主要实现一下上篇文章中Jetpack-LiveData关于数据粘性的问题与ViewBinding两大部分. 一.LiveData去除数据粘性 上篇文章简单分析了 LiveData的使用和源码执 ...
- 【JetPack】视图绑定 ( ViewBinding ) 各种应用 ( 视图绑定两种方式 | Activity 布局 | 对话框布局 | 自定义组件布局 | RecyclerView 列表布局 )
文章目录 I . 视图绑定 ( ViewBinding ) 界面的两种方式 II . Activity 界面中 应用 视图绑定 ( ViewBinding ) III . Dialog 对话框界面中 ...
- 【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )
文章目录 I . 为现有项目配置 视图绑定 ( ViewBinding ) 应用 II . 视图绑定 ( ViewBinding ) 定制 III . 视图绑定 ( ViewBinding ) 对于正 ...
- 【JetPack】ViewBinding 视图绑定组件 ( 启用模块 | 视图绑定定制 | 绑定类名称生成规则 | 绑定类字段生成规则 | 绑定类获取根视图 | 绑定类获取布局组件 )
文章目录 I . 视图绑定组件简介 II . 视图绑定 ViewBinding 使用前提 ( Android Studio 3.6 ) III . 视图绑定组件启用 IV . 定制视图绑定 ( 启用视 ...
- Android MVVM框架搭建(十)Hilt、ViewBinding、Activity Result API
Android MVVM框架搭建(十)Hilt.ViewBinding.Activity Result API 前言 正文 一.依赖 二.Hilt使用 1. Hilt 应用类 2. ViewModel ...
- Jetpack Compose入门详解(实时更新)
Jetpack Compose入门详解 前排提醒 前言(Compose是什么) 1.实战准备 一.优势与缺点 二.前四课 三.标准布局组件 1.Column 2.Row 3.Box 四.xml和com ...
- 优雅地封装和使用 ViewBinding
/ 今日科技快讯 / 近日,有网友在社交平台展示使用筋膜枪抢茅台的操作.对此天猫超市官方作出回应,表示此方法不可靠,并存在身体受伤的可能,希望广大网友理性购物. / 作者简介 / 明天 ...
- 大型Android项目架构:基于组件化+模块化+Kotlin+协程+Flow+Retrofit+Jetpack+MVVM架构实现WanAndroid客户端
前言:苟有恒,何必三更眠五更起:最无益,莫过一日曝十日寒. 前言 之前一直想写个 WanAndroid 项目来巩固自己对 Kotlin+Jetpack+协程 等知识的学习,但是一直没有时间.这里重新行 ...
- Android ABC 取其精华去其糟粕、JetPack好用的组件推荐
JetPack主流组件对比 jetpack组件名 推荐指数 槽点指数 解析 LiveData ★★★★★ 配合ViewModel和数据可以实现界面的动态更新,内部使用version版本控制和观察者模式 ...
- 【背上Jetpack之DataBinding】数据驱动魔法师 何时迎来翻身日?
系列文章 [背上Jetpack]Jetpack 主要组件的依赖及传递关系 [背上Jetpack]AdroidX下使用Activity和Fragment的变化 [背上Jetpack之Fragment]你 ...
最新文章
- Android 悬浮窗口
- python 3下对stm32串口数据做解析
- ASP.NET - 截取固定长度字符串显示在页面,多余部分显示为省略号
- AI and logistics Patent
- ICCV 2019 最佳论文和最佳学生论文下载
- SQL2005 数据库数据同步
- WebShop WebSocket server 和WebSocket客户端的一对多关系维护
- lua transliterate实现(lua程序设计10.6练习10.3题)
- OpenResty(nginx)限流配置实现
- 【100题】第三十二 数组、规划
- 2021大同高考成绩查询,大同高考分数查询(查询方法+入口)
- KMP模式匹配 三(弦)
- 邱锡鹏:为什么相比于CV,NLP领域的发展要缓慢?
- C#属性默认值设置(model实体类)
- java计算机毕业设计足球队管理系统源码+数据库+系统+lw文档+mybatis+运行部署
- 关于数组中的大括号{}和数组的遍历
- jsp+css实现图片自动轮换
- 腾讯马化腾:公司拥有大量探索和开发元宇宙的技术和能力
- Unity小地图的放大缩小
- pubwin2009服务端 修改系统时间方法
热门文章
- 怎么样有效防电脑辐射
- 软件测试行业发展现状及前景
- 5 Linux系统编程之网络编程--学习笔记
- 数据结构与算法复习第一弹(快速排序)
- CCF中学生计算机程序设计入门篇练习2.4.2(NOI 1002 三角形) pascal
- 数学速算法_计算总是出算?小学数学常用的25种快速口算窍门,学好算数必备...
- 虚拟地址与虚拟内存的理解
- 零基础学习UI设计,有哪些软件推荐
- tk.mybatis.mapper.MapperException: 无法获取 com.zhao.mapper.BIllTypeMapper.selectCountByExample 方法的泛型信息
- 关于给青轴润轴消除弹簧音[误]