Android setContentView执行流程(1)-生成DecorView

setContentView的流程主要就是讲在Activity的onCreate方法中调用setContentView方法之后,我们自定义的xml文件加载的过程,学习它可以让我们对整个View树的理解更加透彻,并且通过源码的学习,我们可以从根本上理解一些问题,比如

  • 为什么requestWindowFeature要放到setContentView前面
  • merge、include、ViewStub是如何工作的,尤其是include如何通过id查找布局的根View
  • View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true)中inflate方法的参数有什么用呢?

我们在平时的开发中可能也经常会遇到,遇到之后百度一下ctrl+c ctr+v就解决了,但是其根本原因可能不是特别清楚,那么今天我们就通过源码来看下setContentView的流程,相信通过本篇的学习你一定会有收获。

随着Android版本的升级我们在声明一个Activity时可以继承自Activity也可以继承自AppCompatActivity,因此对于setContentView的流程也同样分为继承自Activity和继承自AppCompatActivity。这两种方式的大致流程差不多,都可以分为两大步骤

(1) installDecor 生成DecorView并获取到mContentParent对象

(2) 将我们Activity的xml布局添加到mContentParent中

继承自Activity和AppCompatActivity的区别主要在第一步,因此本篇的思路就是对第(1)步分别讲解,对于第(2)步两者相同就共同来讲,另外说明一下我这里采用的是API 30的源码。

一、继承自Activity DecorView生成过程

1 setContentView整体运行时序图

我们先来看下继承自Activity的setContentView的调用流程,我画了个时序图方便大家从整体上掌握各个模块的交互流程,学习一个知识一定要先从整体上了解,然后再去了解细节
在这里插入图片描述

可以看到整个流程其实就是刚提到的两步:

  • 初始化DecorView——获取到mContentParent,并将系统的xml布局文件添加到DecorView
  • 将开发者自定义的Acitivity的xml布局添加到mContentParent中

2 源码执行分析

接下来就根据上述流程来看下源码,我们看源码不应该扣细节,只关注主流程就行,因此对于源码我会进行精简,只把主流程中用到的留下。首先从Activity#setContentView的源码开始

    public void setContentView(View view) {getWindow().setContentView(view);initWindowDecorActionBar();}public Window getWindow() {return mWindow;}

可以看到在第二行调用了getWindow方法,而getWindow返回一个mWindow,那么这个mWindow是怎么来的呢?我们知道Activity启动(后续会写一篇Activity启动流程的文章,这里大家先了解下)的时候有个非常重要的类ActivityThread,当我们点击应用图标启动应用时Android系统会创建一个新的进程,并且在启动Activity的时候会调用ActivityThread的performLaunchActivity方法,来看下它的源码ActivityThread#performLaunchActivity

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {。。。Activity activity = null;// 初始化activityactivity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);。。。// 调用activity的attach方法activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken, r.shareableActivityToken);。。。// 调用activity的onCreate方法		if (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);} else {mInstrumentation.callActivityOnCreate(activity, r.state);}return activity;}

可以看到在第5行初始化了一个activity,然后调用了activity的attach方法,并且从调用顺序上也能看到attach方法要早于onCreate方法执行,来看下Activity的attach方法的源码Activity#attach

    final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,IBinder shareableActivityToken) {。。。mWindow = new PhoneWindow(this, window, activityConfigCallback);。。。}

Activity中的attach方法参数很多,我们不需要关注,可以看到第10行它初始化了mWindow,这个mWindow其实就是PhoneWindow,因为它是在activity的attach方法中初始化的,并且attach方法早于onCreate方法执行,所以在onCreate->setContentView->getWindow获取window的时候,这个window已经初始化好了,可以直接拿到这个PhoneWindow对象。
因此在Activity中的onCreate方法中调用setContentView的时候,首先会调用getWindow().setContentView而这个getWindow就是在Activity中的attach方法中初始化的PhoneWindow对象。

所以接下来我们就要分析PhoneWindow的setContentView方法,它的源码如下PhoneWindow#setContentView

    @Overridepublic void setContentView(int layoutResID) {if (mContentParent == null) {// 初始化DecorView 并获取到mContentParentinstallDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {。。。} else {// 将我们自定义Activity的布局添加到mContentParentmLayoutInflater.inflate(layoutResID, mContentParent);}。。。// setContentView执行完的标识mContentParentExplicitlySet = true;}

这里就比较重要了,它主要进行了两个操作,分别是第5行和第14行

  • installDecor:初始化DecorView 并获取到mContentParent
  • mLayoutInflater.inflate(layoutResID, mContentParent):将我们自定义Activity的布局添加到mContentParent

在最后它还会将mContentParentExplicitlySet置为true,随后我们也会讲到它的用处

我们一个一个来看,首先来看PhoneWindow#installDecor

public class PhoneWindow extends Window implements MenuBuilder.Callback {private void installDecor() {。。。if (mDecor == null) {// 初始化DecorView对象mDecor = generateDecor(-1);。。。} else {mDecor.setWindow(this);}if (mContentParent == null) {// 将id为ID_ANDROID_CONTENT的布局赋值给mContentParentmContentParent = generateLayout(mDecor);。。。}}}protected DecorView generateDecor(int featureId) {。。。// 将new的DecorView返回return new DecorView(context, featureId, this, getAttributes());}
}

可以看到第6行调用了generateDecor方法,而generateDecor方法其实就是根据各种特性new一个DecorView并返回。第13行调用了generateLayout,我们来看下它的源码PhoneWindow#generateLayout

    protected ViewGroup generateLayout(DecorView decor) {TypedArray a = getWindowStyle();。。。// Inflate the window decor.int layoutResource;int features = getLocalFeatures();。。。省略多个if分支判断if ((features & (1 << FEATURE_NO_TITLE)) == 0) {。。。if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {layoutResource = a.getResourceId(R.styleable.Window_windowActionBarFullscreenDecorLayout,R.layout.screen_action_bar);} else {layoutResource = R.layout.screen_title;}。。。} else {// Embedded, so no decoration is needed.layoutResource = R.layout.screen_simple;}mDecor.startChanging();// 将layoutResource布局添加到DecorViewmDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// 调用findViewById将layoutResource布局中id为ID_ANDROID_CONTENT的ViewGroup赋值给contentParentViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);。。。return contentParent;}

generateLayout它其实就是根据我们设置的特性去加载不同的布局,比如FEATURE_NO_TITLE、FEATURE_ACTION_BAR等,下面我们就假设进入了最后一个分支即第19行layoutResource = R.layout.screen_simple,然后到第23行调用DecorView的onResourcesLoaded方法将R.layout.screen_simple布局添加到DecorView,然后第25行获取R.layout.screen_simple这个布局的ID_ANDROID_CONTENT,并将其返回。接着来看下DecorView中onResourcesLoaded的源码,DecorView#onResourcesLoaded

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {。。。final View root = inflater.inflate(layoutResource, null);。。。// Put it below the color views.addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

可以看到DecorView是继承自FrameLayout的,第4行调用LayoutInflater方法将R.layout.screen_simple转化成View,然后第7行调用自身的addView方法将刚生成的View添加到DecorView。到这里大家可能有个疑惑就是R.layout.screen_simple,这里面的内容是啥呢?R.layout.screen_simple.xml内容如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"android:orientation="vertical"><ViewStub android:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="?attr/actionBarTheme" /><FrameLayoutandroid:id="@android:id/content"android:layout_width="match_parent"android:layout_height="match_parent"android:foregroundInsidePadding="false"android:foregroundGravity="fill_horizontal|top"android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

它是android系统里的布局,根据不同的特性会有不同的布局,R.layout.screen_simple算是比较简单的,讲解原理的话用这种简单的布局更好理解。到这里installDecor的流程就走完了,也就是setContentView的第一步:installDecor 生成DecorView并获取到mContentParent对象,我们来看下此时的布局是怎样的,如下图所示
在这里插入图片描述

3 requestWindowFeature使用

再回头看下PhoneWindow的setContentView的源码最后一个行mContentParentExplicitlySet = true这个作用是什么呢?还记得在刚开始做Android的时候想去掉顶部的Title需要做如下处理

    @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);}

第4行调用requestWindowFeature(Window.FEATURE_NO_TITLE)并且这个方法只能放到setContentView之前,如果放到setContentView之后的话会报错

在这里插入图片描述

为什么会报错呢?这需要看下requestWindowFeature的源码,Activity#requestWindowFeature

    public final boolean requestWindowFeature(int featureId) {return getWindow().requestFeature(featureId);}

这里的getWindow同样是PhoneWindow,因此最终调用的是PhoneWindow#requestFeature

    @Overridepublic boolean requestFeature(int featureId) {if (mContentParentExplicitlySet) {throw new AndroidRuntimeException("requestFeature() must be called before adding content");}。。。return super.requestFeature(featureId);}

可以看到第3行if语句的条件就是如果mContentParentExplicitlySet为true就会抛异常,而刚才提到setContentView的最后一句将mContentParentExplicitlySet置为了true,所以如果requestWindowFeature方法放到setContentView之后就会抛异常,为什么这么设计呢?回到上面的PhoneWindow#generateLayout的源码可以看到DecorView的布局会用到requestWindowFeature设置的特性,根据不同的特性来加载不同的布局。好了到这里继承自Activity生成DecorView的过程就介绍完了。

二、继承自AppCompatActivity DecorView生成过程

1 源码执行流程分析

接下来看下继承自AppCompatActivity的相关内容,同样我们先看它的调用流程,AppCompatActivity#setContentView

public class AppCompatActivity extends FragmentActivity implements ...{@Overridepublic void setContentView(@LayoutRes int layoutResID) {initViewTreeOwners();getDelegate().setContentView(layoutResID);}public AppCompatDelegate getDelegate() {if (mDelegate == null) {mDelegate = AppCompatDelegate.create(this, this);}return mDelegate;}
}

可以看到第6行调用了getDelegate,在getDelegate方法中第11行调用了AppCompatDelegate.create方法

    public static AppCompatDelegate create(@NonNull Activity activity,@Nullable AppCompatCallback callback) {return new AppCompatDelegateImpl(activity, callback);}

这个方法会返回一个AppCompatDelegateImpl对象,因此在AppCompatActivity的setContentView方法中调用getDelegate().setContentView(layoutResID)其实就是调用AppCompatDelegateImpl中的setContentView,我们来看下它的源码AppCompatDelegateImpl#setContentView

class AppCompatDelegateImpl extends AppCompatDelegate implements ... {@Overridepublic void setContentView(View v) {ensureSubDecor();ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);contentParent.removeAllViews();contentParent.addView(v);。。。}private void ensureSubDecor() {if (!mSubDecorInstalled) {mSubDecor = createSubDecor();。。。}} 
}

在第4行调用了ensureSubDecor方法,在ensureSubDecor中调用createSubDecor来创建subDecor对象,重点来看下AppCompatDelegateImpl#createSubDecor

class AppCompatDelegateImpl extends AppCompatDelegate implements ... {private ViewGroup createSubDecor() {。。。ensureWindow();mWindow.getDecorView();final LayoutInflater inflater = LayoutInflater.from(mContext);ViewGroup subDecor = null;// 。。。此处省略很多if分支判断if (mOverlayActionMode) {subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple_overlay_action_mode, null);} else {subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);}final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);if (windowContentView != null) {while (windowContentView.getChildCount() > 0) {final View child = windowContentView.getChildAt(0);windowContentView.removeViewAt(0);contentView.addView(child);}// Change our content FrameLayout to use the android.R.id.content id.// Useful for fragments.windowContentView.setId(View.NO_ID);contentView.setId(android.R.id.content);}// Now set the Window's content view with the decormWindow.setContentView(subDecor);return subDecor;}private void ensureWindow() {// We lazily fetch the Window for Activities, to allow DayNight to apply in// attachBaseContextif (mWindow == null && mHost instanceof Activity) {attachToWindow(((Activity) mHost).getWindow());}if (mWindow == null) {throw new IllegalStateException("We have not been given a Window");}}
}

在createSubDecor中首先调用了ensureWindow()这个方法的作用在第43行,通过Activity获取PhoneWindow对象,拿到PhoneWindow对象之后在第5行会调用mWindow.getDecorView(),mWindow其实就是PhoneWidow,我们就来看下它的代码

public class PhoneWindow extends Window implements MenuBuilder.Callback {@Overridepublic final @NonNull View getDecorView() {if (mDecor == null || mForceDecorInstall) {installDecor();}return mDecor;}
}

看到没,它调用了installDecor()方法,这个installDecor的流程跟继承自Activity的流程是一样的。再回到createSubDecor方法的源码,刚才我们讲了第4行和第5行的代码,接着往下看它会根据不同的特性加载不同的布局,这里为了让主流程更加清晰我省略了很多if分支判断,只留了第10~15行代码。可以看到在第15行会加载abc_screen_simple.xml布局(假设if分支走到这里),abc_screen_simple.xml如下

<?xml version="1.0" encoding="utf-8"?><androidx.appcompat.widget.FitWindowsLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/action_bar_root"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:fitsSystemWindows="true"><androidx.appcompat.widget.ViewStubCompatandroid:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/abc_action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content" /><include layout="@layout/abc_screen_content_include" /></androidx.appcompat.widget.FitWindowsLinearLayout>

abc_screen_content_include.xml

<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android"><androidx.appcompat.widget.ContentFrameLayoutandroid:id="@id/action_bar_activity_content"android:layout_width="match_parent"android:layout_height="match_parent"android:foregroundGravity="fill_horizontal|top"android:foreground="?android:attr/windowContentOverlay" />
</merge>

再回到createSubDecor方法的源码,加载好abc_screen_simple.xml之后会执行如下代码

        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);if (windowContentView != null) {while (windowContentView.getChildCount() > 0) {final View child = windowContentView.getChildAt(0);windowContentView.removeViewAt(0);contentView.addView(child);}// Change our content FrameLayout to use the android.R.id.content id.// Useful for fragments.windowContentView.setId(View.NO_ID);contentView.setId(android.R.id.content);}// Now set the Window's content view with the decormWindow.setContentView(subDecor);return subDecor;

这个方法很重要,需要重点理解下,可以看到第一行根据id找到contentView,这个id:action_bar_activity_content就是上面abc_screen_content_include.xml中的id,接下来第4行中的android.R.id.content其实是继承自Acitivity的时候xml中的布局,将此id赋值给windowContentView,然后第6行的while循环是判断如果windowContentView有子View会把它们移除并添加到contentView中,最后第13行将windowContentView的id设置为NO_ID,然后将contentView的id设置为android.R.id.content,这么做的原因是为了兼容之前的版本。然后会执行第17行代码将subDecor添加到PhoneWindow。同样我们也来画一下继承自AppComaptActivity的层级图
在这里插入图片描述

跟继承自Activity的层级图差不太多,只是这里为了兼容老版本底层做了一些处理,确保我们应用层不受影响。

2 supportRequestWindowFeature使用注意事项

同样的继承自AppCompatActivity进行 Window特性设置时不再调用requestWindowFeature而是调用supportRequestWindowFeature并且也是要放到setContentView前面。

    @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);supportRequestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);}

到这里DecorView的生成过程就讲完了

三、总结

(1) 我们activity中自定义的xml文件的根布局是DecorView,DecorView是继承自FrameLayout的

(2) 在DecorView的子View中有个id为R.id.content的布局,将其转化为mContentParent

(3) 最后使用LayoutInflater.from(mContext).inflate(resId, contentParent)将我们定义的xml文件resId添加到mContentParent中

篇幅原因第(3)点我们下篇博客讲解。

如果大家有疑问或者发现错误,欢迎在下方留言,我会在第一时间回答!!
如果觉得文章对你有帮助就帮忙点个赞吧。

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

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

相关文章

《操作系统 - 清华大学》2 -1:操作系统的启动

文章目录 0. 内容摘要1. 计算机体系机构概述2.启动2.1 启动时计算机内存和磁盘布局2.2. 内存映射 3. 系统调用、异常、中断3.1 定义3.2 背景3.3 中断、异常和系统调用的不同点3.3.1 源头3.3.2 处理时间3.3.3 响应 0. 内容摘要 两部分的内容 第一部分是启动。知道操作系统怎么是…

在服务器里安装2个conda

1、安装新的conda 下载地址&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 本文选择&#xff1a;Anaconda3-2023.03-1-Linux-x86_64.sh 安装&#xff1a;Ubuntu安装Anaconda详细步骤&#xff08;Ubuntu22.04.1&#xff…

【系统集成项目管理工程师】知识点汇总

十五矩阵图 ITTO&#xff08;Input - Tools & Techniques - Output&#xff09;一览图 整合管理、范围管理 进度管理、成本管理 成本管理&#xff08;续&#xff09;、质量管理、资源管理 沟通管理、风险管理 风险管理&#xff08;续&#xff09;、采购管理、干系人管理

Java | Leetcode Java题解之第552题学生出勤记录II

题目&#xff1a; 题解&#xff1a; class Solution {static final int MOD 1000000007;public int checkRecord(int n) {long[][] mat {{1, 1, 0, 1, 0, 0},{1, 0, 1, 1, 0, 0},{1, 0, 0, 1, 0, 0},{0, 0, 0, 1, 1, 0},{0, 0, 0, 1, 0, 1},{0, 0, 0, 1, 0, 0}};long[][] re…

极品模板内容付费管理系统(PHP内容知识付费系统)

极品模板内容付费管理系统是一款基于PHP和MySQL技术开发的源码产品&#xff0c;旨在为用户提供一个功能全面、易于管理和扩展的内容付费平台。该系统支持多种付费模式&#xff0c;具备强大的内容管理功能&#xff0c;适合各类内容创作者和企业家构建知识付费、会员服务、在线教…

设计者模式之策略模式

前言 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将这些算法都写在对象中&#xff0c;将会使对象变得异常复杂&#xff1b;而且有时候支持不频繁使用的算法也是一个性能负担。 如何在运行时根据需要透明地更改对象的算…

【AIGC】如何通过ChatGPT轻松制作个性化GPTs应用

创建个性化的GPTs应用是一个涉及技术、设计和用户体验的过程。以下是详细步骤&#xff1a; ###1.确定应用目标和用户群体 在开始之前&#xff0c;你需要明确你的应用的目标和目标用户。这将帮助你在设计、开发和个性化方面做出相应的决策。例如&#xff0c;如果你的应用是为了…

LeetCode 热题 100之 堆

1.数组中第k个最大元素 和Acwing 786 第k个数一模一样 排序 思路分析1&#xff1a;此题要求时间复杂度未为O(n)。虽然库函数sort和快速排序都能过&#xff0c;但是时间复杂度不满足条件。下面优化快速排序&#xff0c;写一个快速选择算法。我们可以引入随机化来加速这个过程&…

redis笔记-数据结构

zset zset一方面它是一个 set&#xff0c;保证了内部value 的唯一性&#xff0c;另一方面它可以给每个 value 赋予一个 score&#xff0c;代表这个 value 的排序权重。 zset的底层是由字典和跳表实现。 字典主要用来存储value和score的对应关系。跳表这个数据结构主要用来提…

day20-21之间的项目实战:若依ruoyi开发(可以跳过)

一&#xff0c;项目概述 官网文档地址&#xff1a;http://doc.ruoyi.vip/ rouyi是一个后台管理系统&#xff0c;基于经典技术组合&#xff08;spring boot&#xff0c;apache shiro&#xff0c;mybatis&#xff0c;thymeleaf&#xff09;主要是让开发者注重专注业务&#xff0…

【Android】名不符实的Window类

1.“名不符实”的Window类 Window 是一个窗口的概念&#xff0c;是所有视图的载体&#xff0c;不管是 Activity&#xff0c;Dialog&#xff0c;还是 Toast&#xff0c;他们的视图都是附加在 Window 上面的。例如在桌面显示一个悬浮窗&#xff0c;就需要用到 Window 来实现。Wi…

SDL事件相关

文章目录 事件相关的函数和数据结构用户自定义事件代码相关&#xff1a; 事件相关的函数和数据结构 SDL_WaitEvent :等待一个事件SDL_PushEvent 发送一个事件SDL_PumpEvents(): 将硬件设备产生的时间放入事件队列 &#xff0c;用于读取事件&#xff0c;在调用该函数之前&#…

机器人课程——使用TIA Portal V15博图软件进行西门子组态——带显示屏

一.打开TIA Portal V15博图软件创建项目 1.选择创建新项目 创建完成后选择PLC 二.创建完成后选择设备PLC (此处以S7-1200 1214FC DC/DC/DC 为例) 三.添加扩展板&#xff08;如有——这里以223-1BL32-0XB0为例&#xff09; 四.更改扩展版地址 五.添加触摸屏&#xff08;这里以…

【数据集】【YOLO】【目标检测】道路结冰数据集 1527 张,YOLO目标检测实战训练教程!

数据集介绍 【数据集】道路结冰数据集 1527 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。数据集中包含2种分类&#xff1a;“clear_road, ice_road”。数据集来自国内外图片网站和视频截图&#xff0c;部分数据经过数据增强处理。检测范围监控视角检测、无人机视…

【Mysql NDB Cluster 集群(CentOS 7)安装笔记一】

Mysql NDB Cluster 集群(CentOS 7)安装笔记 NDB集群核心概念 NDBCLUSTER(也称为NDB)是一个内存存储引擎,提供高可用性和数据保存功能。 NDBCLUSTER存储引擎可以配置一系列故障转移和负载平衡选项,但从集群级别的存储引擎开始是最容易的。NDB集群的NDB存储引擎包含一整套…

利用京东API接口实现商品详情数据获取与表格化展示

在电商数据分析与运营过程中&#xff0c;获取商品详情数据是至关重要的一环。京东作为国内领先的电商平台&#xff0c;其开放平台提供了丰富的API接口&#xff0c;使得开发者能够高效地获取商品信息。本文将详细介绍如何通过京东API接口获取商品详情数据&#xff0c;并将其整理…

数据结构-并查集专题(1)

一、前言 因为要开始准备年底的校赛和明年年初的ACM、蓝桥杯、天梯赛&#xff0c;于是开始按专题梳理一下对应的知识点&#xff0c;先从简单入门又值得记录的内容开始&#xff0c;并查集首当其冲。 二、我的模板 虽然说是借用了jiangly鸽鸽的板子&#xff0c;但是自己也小做…

博奥龙/诊断原料抗体对

在ELISA中&#xff0c;抗体与抗原的结合精确度依赖于抗体的特异性和灵敏度。特异性较差的抗体可能导致显著的非特异性背景信号&#xff0c;而特异好但亲和力弱的抗体可能会被洗掉&#xff0c;从而产生假阴性数据。因此&#xff0c;选择合适的可避免交叉反应和确保检测结果的准确…

OceanBase详解及如何通过MySQL的lib库进行连接

OceanBase详解及如何通过MySQL的lib库进行连接 一、引言二、OceanBase概述1. 起源与发展2. 核心技术特点3. 应用场景三、OceanBase架构解析1. 系统架构2. 存储引擎3. 分布式架构四、如何使用MySQL的lib库连接OceanBase1. 前提条件2. 安装MySQL Connector/C3. 编写连接代码4. 编…

java导出word文件(手绘)

文章目录 代码细节效果图参考资料 代码细节 使用的hutool的WordUtil&#xff0c;WordUtil对poi进行封装&#xff0c;但是这一块的官方封装的很少&#xff0c;很多细节都没有。代码中是常见的绘制段落&#xff0c;标题、表格等常用api Word07Writer writer WordUtil.getWriter(…