一个由AndroidAutoSize导致获取状态栏高度不准确的问题

news/2024/9/23 6:38:58/

1. 问题描述

公司的项目中引入了JessYan大佬的AndriodAutoSize框架,作为适配设计图尺寸的解决方案。

由于项目是作为带UI的SDK提供给第三方客户集成,在客户集成的过程中发现他们自身的APP在获取状态栏高度时,获取的高度值变小了。下面是集成方的代码:

/*** context是Activity的实例*/
public static int getstatusBarHeight(context context) {// 获得状态栏高度int resourceId = context.getResources().getIdentifier( name: "status_bar_height", defype: "dimen", defpackage: "android");return context.getResources().getDimensionPixelSize(resourceId);
}

我第一时间就怀疑可能是AndroidAutoSize框架导致的,然后我就写了个demo去尝试复现问题

2. 问题复现

模拟项目中AutoSize的初始化方法:

    AutoSizeConfig.getInstance().unitsManager  .setSupportDP(false)  .setSupportSP(false).supportSubunits = Subunits.PT

设置不支持DP和SP,使用子单位PT,然后在一个Activity中获取状态栏高度

class StatusBarActivity : AppCompatActivity() {  override fun onCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)  setContentView(R.layout.activity_status_bar)  val resourceId: Int = resources.getIdentifier("status_bar_height", "dimen", "android")  if (resourceId > 0) {  val height = resources.getDimensionPixelSize(resourceId)  Log.i("Test", "status_bar_height: $height")  Log.i("Test", "status_bar_height: ${ScreenUtils.getStatusBarHeight()}")  }}
}

运行后的输出结果为:

status_bar_height:49 // 经过AutoSize适配过后的状态栏高度
status_bar_height:99 // 真实的状态栏高度

暂时先不管ScreenUtils.getStatusBarHeight()这个方法,只需要知道这个方法获取的状态栏高度是真实的高度即可,后面再解释原因。
接下来针对AndroidAutoSize的源码来定位问题

3. 问题分析

直接从源码入手,寻找问题根本原因。

首先,找初始化的位置

AutoSizeConfig init(final Application application, boolean isBaseOnWidth, AutoAdaptStrategy strategy) {// 这里只放了针对我遇到问题的关键代码,其他源码可以直接在github中查看mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(strategy == null ? new WrapperAutoAdaptStrategy(new DefaultAutoAdaptStrategy()) : strategy);application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks);
}

这里是初始化AutoSize的位置,具体调用该方法的地方在一个叫InitProviderContentProvider中,具体就不再展示了,不是解决本文中提到问题的重点。
先来看下ActivityLifecycleCallbacksImpl类,该类的部分源码如下:

public class ActivityLifecycleCallbacksImpl implements Application.ActivityLifecycleCallbacks {private AutoAdaptStrategy mAutoAdaptStrategy;public ActivityLifecycleCallbacksImpl(AutoAdaptStrategy autoAdaptStrategy) {  mFragmentLifecycleCallbacks = new FragmentLifecycleCallbacksImpl(autoAdaptStrategy);  mAutoAdaptStrategy = autoAdaptStrategy;  }@Override  public void onActivityCreated(Activity activity, Bundle savedInstanceState) {  // 这里就是实际进行适配的位置  if (mAutoAdaptStrategy != null) {  mAutoAdaptStrategy.applyAdapt(activity, activity);  }}@Override  public void onActivityStarted(Activity activity) {  if (mAutoAdaptStrategy != null) {  mAutoAdaptStrategy.applyAdapt(activity, activity);  }  }/**  * 设置屏幕适配逻辑策略类  *  * @param autoAdaptStrategy {@link AutoAdaptStrategy}  */  public void setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {  mAutoAdaptStrategy = autoAdaptStrategy;  mFragmentLifecycleCallbacks.setAutoAdaptStrategy(autoAdaptStrategy);  }
}
public interface AutoAdaptStrategy {  /**  * 开始执行屏幕适配逻辑  *  * @param target 需要屏幕适配的对象 (可能是 {@link Activity} 或者 {@link Fragment})  * @param activity 需要拿到当前的 {@link Activity} 才能修改 {@link DisplayMetrics#density}  */  void applyAdapt(Object target, Activity activity);  
}

ActivityLifecycleCallbacksImpl类实现了Activity生命周期的回调事件,在ActivityonCreate()事件回调中实际调用了applyAdapt方法,进行尺寸的适配。AutoAdaptStrategy是一个接口,只定义了一个applayAdapt方法,这里可以看到采用了适配器模式来进行屏幕的适配逻辑。

再来看AutoAdaptStrategy的初始化,在AutoSizeConfiginit方法中,创建ActivityLifecycleCallbacksImpl对象时,构造方法传入了一个WrapperAutoAdaptStrategy对象。

public class WrapperAutoAdaptStrategy implements AutoAdaptStrategy {  private final AutoAdaptStrategy mAutoAdaptStrategy;  public WrapperAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {  mAutoAdaptStrategy = autoAdaptStrategy;  }  @Override  public void applyAdapt(Object target, Activity activity) {  onAdaptListener onAdaptListener = AutoSizeConfig.getInstance().getOnAdaptListener();  if (onAdaptListener != null){  onAdaptListener.onAdaptBefore(target, activity);  }  if (mAutoAdaptStrategy != null) {  mAutoAdaptStrategy.applyAdapt(target, activity);  }  if (onAdaptListener != null){  onAdaptListener.onAdaptAfter(target, activity);  }  }  
}

该类的构造方法接受一个AutoAdaptStrategy对象,这里给出的是默认的DefaultAutoAdaptStrategy。这里的设计采用了装饰器模式,在实际的applyAdapt方法执行前后添加了两个事件回调,分别是适配前onAdaptBefore和适配后onAdaptAfter

再来看DefaultAutoAdaptStrategy类:

public class DefaultAutoAdaptStrategy implements AutoAdaptStrategy {  // 部分代码没有展示,与本文问题关系不大@Override  public void applyAdapt(Object target, Activity activity) {  //如果 target 实现 CancelAdapt 接口表示放弃适配, 所有的适配效果都将失效  if (target instanceof CancelAdapt) {  LogUtils.w(String.format(Locale.ENGLISH, "%s canceled the adaptation!", target.getClass().getName()));  AutoSize.cancelAdapt(activity);  return;  }  //如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果  if (target instanceof CustomAdapt) {  LogUtils.d(String.format(Locale.ENGLISH, "%s implemented by %s!", target.getClass().getName(), CustomAdapt.class.getName()));  AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target);  } else {  LogUtils.d(String.format(Locale.ENGLISH, "%s used the global configuration.", target.getClass().getName()));  AutoSize.autoConvertDensityOfGlobal(activity);  }}  
}

可以看到这里有三种情况,本文中遇到的问题是走了AutoSize.autoConvertDensityOfGlobal(activity)这行代码,其他两种情况是在全局适配的基础上针对某些特定的Activity执行适配或者不适配的操作,这里不再详细解析,github中有详细用法的解释。

我们继续看AutoSize.autoConvertDensityOfGlobal(activity)的执行,重点就在下面的代码里了:

public final class AutoSize {/**  * 使用 AndroidAutoSize 初始化时设置的默认适配参数进行适配 (AndroidManifest 的 Meta 属性)  * @param activity {@link Activity}  */  public static void autoConvertDensityOfGlobal(Activity activity) {  // 默认以屏幕的宽度作为适配基准if (AutoSizeConfig.getInstance().isBaseOnWidth()) {  autoConvertDensityBaseOnWidth(activity, AutoSizeConfig.getInstance().getDesignWidthInDp());  } else {  autoConvertDensityBaseOnHeight(activity, AutoSizeConfig.getInstance().getDesignHeightInDp());  }  }/**  * 以宽度为基准进行适配  *  * @param activity {@link Activity}  * @param designWidthInDp 设计图的总宽度  */  public static void autoConvertDensityBaseOnWidth(Activity activity, float designWidthInDp) {  autoConvertDensity(activity, designWidthInDp, true);  }/*** sizeInDp表示设计图的宽度,以dp为单位**/public static void autoConvertDensity(Activity activity, float sizeInDp, boolean isBaseOnWidth) {// designWidth表示设计图的宽度,副单位的表示,如果没有设置,则取sizeInDpfloat subunitsDesignSize = isBaseOnWidth ? AutoSizeConfig.getInstance().getUnitsManager().getDesignWidth()  
: AutoSizeConfig.getInstance().getUnitsManager().getDesignHeight();  subunitsDesignSize = subunitsDesignSize > 0 ? subunitsDesignSize : sizeInDp;DisplayMetricsInfo displayMetricsInfo = mCache.get(key);if (displayMetricsInfo == null) {  if (isBaseOnWidth) {  // 计算适配后的densitytargetDensity = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / sizeInDp;  } else {  targetDensity = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / sizeInDp;  }  float scale = AutoSizeConfig.getInstance().isExcludeFontScale() ? 1 : AutoSizeConfig.getInstance().  getInitScaledDensity() * 1.0f / AutoSizeConfig.getInstance().getInitDensity();  targetScaledDensity = targetDensity * scale;  targetDensityDpi = (int) (targetDensity * 160);  if (isBaseOnWidth) {// 计算适配后的xdpitargetXdpi = AutoSizeConfig.getInstance().getScreenWidth() * 1.0f / subunitsDesignSize; } else {  targetXdpi = AutoSizeConfig.getInstance().getScreenHeight() * 1.0f / subunitsDesignSize;  }// 放入缓存中mCache.put(key, new DisplayMetricsInfo(targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi));  setDensity(activity, targetDensity, targetDensityDpi, targetScaledDensity, targetXdpi);}
}

常规情况下,基于尺寸的转换公式:px = dp * (dpi / 160),需要关注targetDensitytargetDensityDpi

本文中demo的初始化,是默认以屏幕的宽度作为基准,采用了副单位作为适配适配标准,那么后续会主要关注subunitsDesignSize的使用。下面继续看setDensity方法:

        private static void setDensity(Activity activity, float density, int densityDpi, float scaledDensity, float xdpi) {// 忽略对MIUI兼容的代码// 针对Activity的displayMetrics进行适配DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();  setDensity(activityDisplayMetrics, density, densityDpi, scaledDensity, xdpi);// 针对Application的displayMetrics进行适配DisplayMetrics appDisplayMetrics = AutoSizeConfig.getInstance().getApplication().getResources().getDisplayMetrics();  setDensity(appDisplayMetrics, density, densityDpi, scaledDensity, xdpi);}private static void setDensity(DisplayMetrics displayMetrics, float density, int densityDpi, float scaledDensity, float xdpi) {  if (AutoSizeConfig.getInstance().getUnitsManager().isSupportDP()) {  displayMetrics.density = density;  displayMetrics.densityDpi = densityDpi;  }  if (AutoSizeConfig.getInstance().getUnitsManager().isSupportSP()) {  displayMetrics.scaledDensity = scaledDensity;  }  switch (AutoSizeConfig.getInstance().getUnitsManager().getSupportSubunits()) {  case NONE:  break;  case PT:  displayMetrics.xdpi = xdpi * 72f;  break;  case IN:  displayMetrics.xdpi = xdpi;  break;  case MM:  displayMetrics.xdpi = xdpi * 25.4f;  break;  default:  }  }

可以看到,直接修改了activityResources中的displayMetrics,修改了displayMetricsdensitydensityDpi,这样就直接影响了最终px的转换值,保证了不同屏幕尺寸下适配同样的设计度尺寸。

本文中demo设置了supportDp=falsesupportSp=false,使用副单位PT,这是由于我们公司的设计图是以iOS的屏幕尺寸为标准进行设计的。那么修改的就是displayMetrics.xdpi

AndroidAutoSize的源码先分析这里,大概了解了该框架的运行原理,其实就是在修改ActivitydisplayMetrics属性值,从而影响最终px的计算结果,达到适配不同屏幕尺寸的目的。

接下来,我们来看一下获取状态栏高度的代码:

package android.content.res;
// Resources.java
public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {  final TypedValue value = obtainTempTypedValue();  try {  final ResourcesImpl impl = mResourcesImpl;  impl.getValue(id, value, true);  if (value.type == TypedValue.TYPE_DIMENSION) {  return TypedValue.complexToDimensionPixelSize(value.data, impl.getDisplayMetrics());  }  throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)  + " type #0x" + Integer.toHexString(value.type) + " is not valid");  } finally {  releaseTempTypedValue(value);  }  
}
// android.util.TypedValue
public static int complexToDimensionPixelSize(int data, DisplayMetrics metrics) {  final float value = complexToFloat(data);  final float f = applyDimension(  (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,  value,  metrics);  final int res = (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));  if (res != 0) return res;  if (value == 0) return 0;  if (value > 0) return 1;  return -1;  
}public static float applyDimension(@ComplexDimensionUnit int unit, float value, DisplayMetrics metrics) {  switch (unit) {  case COMPLEX_UNIT_PX:  return value;  case COMPLEX_UNIT_DIP:  return value * metrics.density;  case COMPLEX_UNIT_SP:  return value * metrics.scaledDensity;  case COMPLEX_UNIT_PT:  return value * metrics.xdpi * (1.0f/72);  case COMPLEX_UNIT_IN:  return value * metrics.xdpi;  case COMPLEX_UNIT_MM:  return value * metrics.xdpi * (1.0f/25.4f);  }  return 0;  
}

首先通过resources.getIdentifier获取系统status_bar_height的资源值,然后通过调用resources.getDimensionPixelSize()获取该资源值对应的实际大小,该方法内调用了TypedValue.complexToDimensionPixelSize(),然后调用applyDimension()方法进行实际尺寸的转换。

这里会执行COMPLEX_UNIT_MM这个分支,可以看到它是使用metrics.xdpi进行计算的,那么由于AndroidAutoSize对该值做了修改,就会导致计算得到的值有偏差。

4. 解决方案

解决问题的思路是,能不能告诉AutoSize框架只针对某种Activity进行适配,而不是全局所有Activity都进行适配,这样可以避免影响到集成方的Activity

如何实现呢,由于AutoSize的主要适配逻辑都发生在DefaultAutoAdaptStrategyapplyAdapt方法中,那么我们能不能自己定义一个AutoAdaptStrategy来实现上面的解决思路呢?答案是可以的。

回到ActivityLifecycleCallbacksImpl这个类,其中有一个setAutoAdaptStrategy方法,接收一个AutoAdaptStrategy类型的参数,直接修改ActivityLifecycleCallbacksImplmAutoAdaptStrategy属性,mAutoAdaptStrategyapplyAdapt方法也是实际执行了适配操作。那我们可以把自定义实现的AutoAdaptStrategy实例设置进来。

再来看如何调用ActivityLifecycleCallbacksImplsetAutoAdaptStrategy方法:

    public AutoSizeConfig setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy) {  mActivityLifecycleCallbacks.setAutoAdaptStrategy(new WrapperAutoAdaptStrategy(autoAdaptStrategy));  return this;  }

AutoSizeConfig类中提供了一个方法来实现自定义的适配策略类,太棒了!

接下来实现我自己的适配策略类,这里直接展示新的AndroidAutoSize初始化的代码:

    val myStrategy = object: DefaultAutoAdaptStrategy() {  override fun applyAdapt(target: Any?, activity: Activity?) {  Log.i("Test", "target: $target, activity: $activity")  if (target is IAutoSizeAdaptActivity) {super.applyAdapt(target, activity)  }  }  }  AutoSizeConfig.getInstance()  .setAutoAdaptStrategy(myStrategy)  .unitsManager  .setSupportDP(false)  .setSupportSP(false)  .supportSubunits = Subunits.PT  

新增一个接口IAutoSizeAdaptActivity,并让StatusBarActivity实现这个接口,标识该Activity需要进行适配:

interface IAutoSizeAdaptActivity {  // 不需要定义任何方法
}class StatusBarActivity : AppCompatActivity(), IAutoSizeAdaptActivity {  override fun onResume() {  super.onResume()  val resourceId: Int = resources.getIdentifier("status_bar_height", "dimen", "android") if (resourceId > 0) {  val height = resources.getDimensionPixelSize(resourceId)  Log.i("Test", "StatusBarActivity status_bar_height: $height")  }  }
}

我直接继承了AndroidAutoSizeDefaultAutoAdaptStrategy类,这样可以只针对实现了IAutoSizeAdaptActivity接口的Activity进行适配,也就是调用super.applyAdapt方法,执行DefaultAutoAdaptStrategy中的默认适配逻辑。实际项目中,IAutoSizeAdaptActivity也可以替换为一个通用的基类Activity,只要能覆盖所有需要适配的Activity即可。

到此为止,我以为问题解决了。。。然后,并没有,看下面的demo

写一个CustomerActivity模拟客户的使用场景,CustomerActivity不实现IAutoSizeAdaptActivity接口,添加一个按钮,点击后跳转到StatusBarActivity

class CustomerActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)findViewById<Button>(R.id.btn_jump).setOnClickListener {startActivity(Intent(this, StatusBarActivity::class.java))}}override fun onResume() {  super.onResume()  val resourceId: Int = resources.getIdentifier("status_bar_height", "dimen", "android") if (resourceId > 0) {  val height = resources.getDimensionPixelSize(resourceId)  Log.i("Test", "CustomerActivity status_bar_height: $height")  }  }
}

运行Demo,默认进入CustomerActivity,点击跳转到StatusBarActivity,看控制台打印:

CustomerActivity status_bar_height:99
StatusBarActivity status_bar_height:49

目前来看,是没有问题的,当按返回键从StatusBarActivity回到CustomerActivity时,控制台打印了如下日志:

CustomerActivity status_bar_height:49

状态栏高度不对了,这是怎么回事?

经过一系列的源码分析,问题原因找到了。虽然不同的Activity持有的Resources对象不同,但是Resources对象内部的DisplayMetrics属性却是同一个对象,这就导致当AutoSize修改了StatusBarActivityDisplayMetrics时,应用内其他Activity的DisplayMetrics也都被更改了。

那还有没有其他解决方案呢?继续研究AutoSize的源码,发现有两个方法,一个是AutoSizeConfig.getInstance().restart(),一个是AutoSizeConfig.getInstance().stop(activity)
stop()方法,可以停止AutoSize的适配,将Activity的DisplayMetrics恢复到初始状态,restart()方法可以重新启动AutoSize的适配。

基于我的项目是提供给客户的带UI SDK,那么一旦进入SDK的范围内,就不再出现集成方的Activity,那么我可以记录Activity栈内的IAutoSizeAdaptActivity的实例数量,当实例从0变为1时,调用AutoSizerestart()方法启动AutoSize的适配,当最后一个IAutoSizeAdaptActivity的实例finish的时候,调用AutoSizestop()方法,停止AutoSize的适配,恢复到初始状态,这样就可以解决问题了。

  1. 修改IAutoSizeAdaptActivity,把它改为一个抽象类,让StatusBarActivity继承它。
  2. 新建一个AutoSizeActivityManager类,用于存放IAutoSizeAdaptActivity的实例,并控制AutoSize的启动和停止。

object AutoSizeActivityManager {  private val mStack: Stack<IAutoSizeAdaptActivity> = Stack()  fun addActivity(activity: IAutoSizeAdaptActivity) {  if (mStack.isEmpty()) {  AutoSizeConfig.getInstance().restart()  }  mStack.add(activity)  }  fun removeActivity(activity: IAutoSizeAdaptActivity) {  mStack.remove(activity)  if (mStack.isEmpty()) {  AutoSizeConfig.getInstance().stop(activity)  }  }  
}abstract class IAutoSizeAdaptActivity: AppCompatActivity() {  override fun onCreate(savedInstanceState: Bundle?) {  super.onCreate(savedInstanceState)  Log.i("Test", "onCreate, $this")  AutoSizeActivityManager.addActivity(this)  }  override fun finish() {  Log.i("Test", "finish, $this")  AutoSizeActivityManager.removeActivity(this)  super.finish()  }  
}

再次运行Demo程序,看控制台输出已经正常了:

CustomerActivity status_bar_height:99
StatusBarActivity status_bar_height:49
CustomerActivity status_bar_height:99

到此为止,问题真正解决了。

不过这个方案还不是完美的,因为这里我假定了一旦进入我的UI SDK的范围内,就不会再出现集成方的Activity。如果这个假设不成立,举个例子,CustomerActivity1 -> StatusBarActivity -> CustomerActivity2,当Activity栈中出现这样的调用顺序时,CustomerActivity2获取的状态栏高度也是被AutoSize适配过的。不过这种情况不多见,先作为一个遗留问题吧,如果大家有更好的处理方案,也请在留言区一起讨论。

6. 其他

上面留了一个小尾巴,就是ScreenUtils.getStatusBarHeight()获取到的是真实的系统状态栏高度,是为什么呢?
ScreenUtilsAutoSize提供的一个工具类,还是看源码:

public static int getStatusBarHeight() {  int result = 0;  try {  int resourceId = Resources.getSystem().getIdentifier("status_bar_height", "dimen", "android");  if (resourceId > 0) {  result = Resources.getSystem().getDimensionPixelSize(resourceId);  }  } catch (Resources.NotFoundException e) {  e.printStackTrace();  }  return result;  
}

原来跟我们写的代码差不多,唯一的差别处就是,他的resources用的是Resources.getSystem(),这又是个啥呢?

/**  
* Return a global shared Resources object that provides access to only  
* system resources (no application resources), is not configured for the  
* current screen (can not use dimension units, does not change based on  
* orientation, etc), and is not affected by Runtime Resource Overlay.  
*/  
public static Resources getSystem() {  synchronized (sSync) {  Resources ret = mSystem;  if (ret == null) {  ret = new Resources();  mSystem = ret;  }  return ret;  }  
}

Resources是Android系统源码,在android/content/res目录下。getSystem方法返回的是mSystem实例,这个实例是一个全局共享的Resources实例,只用来访问系统资源,它并不是applicationresources对象,它不是针对具体某一个Activity的,也不会被修改,哪怕是AutoSize也没法修改它。

原来如此,是AutoSize也动不了的东西。以后获取系统状态栏高度,可以使用这个方法。

5. 总结

当自己开发SDK给第三方集成时,需要注意以下两点

  1. 尽量避免在自己开发的SDK中引入第三方开源的SDK。由于我们公司的项目是提供的带UI的SDK,所以难免会引入一些第三方的开源框架,那么这种情况下也要注意一定要选一些业内非常热门的开源框架,否则容易和集成方产生冲突
  2. 警惕在自己开发的SDK中引入包含全局修改的开源框架。如果必须得引入,一定要注意控制影响范围,否则一旦到了客户集成时发现问题,会显得你很不专业

本文到这里就结束啦,主要还是记录自己分析问题和解决问题的过程,如果刚好能帮到你,那真是我莫大的荣幸,期待下次再见。


http://www.ppmy.cn/news/1435910.html

相关文章

提升工作效率必备,桌面待办事项提醒软件

在快节奏的现代社会&#xff0c;提升工作效率成为众多上班族的共同追求。有效的时间管理、合理的工作计划和正确的工具选择&#xff0c;是实现高效工作的三大关键。尤其是选择一款优秀的待办事项管理软件&#xff0c;能够极大地助力我们提升工作效率。 而我在网上找到了一款提…

异步并发怎么做?

异步并发 1、flask的异步并发问题解决办法实现方案&#xff08;1&#xff09;flask 异步视图装饰器&#xff08;2&#xff09;WSGI启动服务 2、fastapi异步编程 1、flask的异步并发 问题 flask在开发环境下是单线程的&#xff0c;如果某个请求长时间无响应&#xff08;阻塞&…

介绍TCP协议标志位

TCP协议中的控制位&#xff08;Flags&#xff09;是TCP头部中的6个标志位&#xff0c;用于控制TCP连接的建立、维护和终止过程&#xff0c;以及在数据传输中的一些特定行为。以下是对每个标志位的详细介绍&#xff1a; SYN (Synchronize)&#xff1a; 功能&#xff1a;用于建立…

MATLAB实现蚁群算法栅格路径优化

蚁群算法是一种模拟自然界中蚂蚁觅食行为的优化算法&#xff0c;常用于解决路径规划问题。在栅格路径优化中&#xff0c;蚁群算法可以帮助找到从起点到终点的最优路径。以下是蚁群算法栅格路径优化的基本流程步骤&#xff1a; 初始化参数&#xff1a; (1)设置蚂蚁数量&#xff…

常用的MQ有哪些?

1. 背景 最近有新同事接触了项目中使用的RocketMQ&#xff0c;问了一个问题&#xff1a;MQ有哪几种&#xff1f;基于此&#xff0c;本文介绍一下目前市面上常用的消息队列&#xff08;MQ&#xff09;有哪些。 2. 五种主流消息队列&#xff08;MQ&#xff09; 2.1 RocketMQ …

Ansible 中的copy 复制模块应用详解

作者主页&#xff1a;点击&#xff01; Ansible专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月25日13点40分 Ansible 中的 copy 模块用于将文件或目录从本地计算机或远程主机复制到远程主机上的特定位置。它是一个功能强大的模块&#xff0c;可用于各种文…

【C++】从零开始认识泛型编程 — 模版

送给大家一句话&#xff1a; 尽管眼下十分艰难&#xff0c;可日后这段经历说不定就会开花结果。总有一天我们都会成为别人的回忆&#xff0c;所以尽力让它美好吧。 – 岩井俊二 &#xff3c;&#xff3c;\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 //&#xff0f;&#xff0f; &#…

获取公募基金持仓【数据分析系列博文】

摘要 从指定网址获取公募基金持仓数据&#xff0c;快速解析并存储数据。 &#xff08;该博文针对自由学习者获取数据&#xff1b;而在投顾、基金、证券等公司&#xff0c;通常有Wind、聚源、通联等厂商采购的数据&#xff09; 1. 导入必要的库&#xff1a; pandas 用于数据处理…