Android-Glide详解

news2024/12/19 4:23:12

目录

一,介绍

二,使用

三,源码分析思路

四,with源码分析

五,模拟Glide生命周期管理


一,介绍

Glide目前是安卓最主流的加载图片的框架,也是源码最为复杂的框架之一。 要想完完全全吃透Glide的源码,可能需要半年甚至更多的时间。

但是如果分析过Glide的源码,不仅对我们的架构能力有很大提高,也会对面试中的关于Glide的问题知其然更知其所以然。

Glide的精髓,在于它的生命周期的管理和缓存策略

二,使用

Glide的使用及其简单

 // 项目的build.gradle添加依赖
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'

在清单文件添加网络权限:

 <uses-permission android:name="android.permission.INTERNET"/>

布局文件很简单就一个imageview:

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />

</androidx.constraintlayout.widget.ConstraintLayout>

使用:

class MainActivity : AppCompatActivity() {
    //图片url
    private val url ="https://q7.itc.cn/q_70/images03/20240714/93ce0113753a4ff4b5f4e48f27ed3739.jpeg"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val iv =findViewById<ImageView>(R.id.iv)
        Glide.with(this).load(url).into(iv)

    }
}

效果:

三,源码分析思路

Glide的源码分析,我们可以从使用角度,拆分为三部分去分析。

class MainActivity : AppCompatActivity() {
    //图片url
    private val url ="https://q7.itc.cn/q_70/images03/20240714/93ce0113753a4ff4b5f4e48f27ed3739.jpeg"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val iv =findViewById<ImageView>(R.id.iv)
        //第一步 调用with  得到RequestManager
        val requestManager:RequestManager = Glide.with(this)
       
        //第二步 RequestManager调用load 得到requestBulider
        val requestBulider:RequestBuilder<Drawable> =requestManager.load(url)
        
        //第三步 requestBulider调用into
        requestBulider.into(iv)
    }
}

这样拆分的话,思路会比较清晰。

四,with源码分析

首先我们看一下with的源码:

@NonNull
public static RequestManager with(@NonNull Context context) {
  return getRetriever(context).get(context);
}

@NonNull
public static RequestManager with(@NonNull Activity activity) {
  return getRetriever(activity).get(activity);
}

@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
  return getRetriever(activity).get(activity);
}

@NonNull
public static RequestManager with(@NonNull Fragment fragment) {
  return getRetriever(fragment.getContext()).get(fragment);
}

@SuppressWarnings("deprecation")
@Deprecated
@NonNull
public static RequestManager with(@NonNull android.app.Fragment fragment) {
  return getRetriever(fragment.getActivity()).get(fragment);
}

@NonNull
public static RequestManager with(@NonNull View view) {
  return getRetriever(view.getContext()).get(view);
}

它可以接受的参数类型很多,确保多种场景下的使用

然后继续往下看getRetriever这个函数:

@NonNull
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
  // Context could be null for other reasons (ie the user passes in null), but in practice it will
  // only occur due to errors with the Fragment lifecycle.
  Preconditions.checkNotNull(
      context,
      "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
          + "returns null (which usually occurs when getActivity() is called before the Fragment "
          + "is attached or after the Fragment is destroyed).");
  return Glide.get(context).getRequestManagerRetriever();
}

判空不用看,直接看get(context)这个函数:

@NonNull
public static Glide get(@NonNull Context context) {
  if (glide == null) {
    GeneratedAppGlideModule annotationGeneratedModule =
        getAnnotationGeneratedGlideModules(context.getApplicationContext());
    synchronized (Glide.class) {
      if (glide == null) {
        checkAndInitializeGlide(context, annotationGeneratedModule);
      }
    }
  }
  return glide;
}

这个就是我们熟悉的双重检测单例模式,不了解的可以参考文章:Android 设计模式--单例模式_android开发饿汉单例-CSDN博客

然后我们看看 checkAndInitializeGlide(context, annotationGeneratedModule):

@GuardedBy("Glide.class")
private static void checkAndInitializeGlide(
    @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {
  // In the thread running initGlide(), one or more classes may call Glide.get(context).
  // Without this check, those calls could trigger infinite recursion.
  if (isInitializing) {
    throw new IllegalStateException(
        "You cannot call Glide.get() in registerComponents(),"
            + " use the provided Glide instance instead");
  }
  isInitializing = true;
  initializeGlide(context, generatedAppGlideModule);
  isInitializing = false;
}

这里好像没有什么具体的信息,我们接着往下看initializeGlide(context,generatedAppGlideModule)这个函数:

@GuardedBy("Glide.class")
private static void initializeGlide(
    @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {
  initializeGlide(context, new GlideBuilder(), generatedAppGlideModule);
}

好的,依然继续往下看initializeGlide(context, new GlideBuilder(), generatedAppGlideModule)

@GuardedBy("Glide.class")
@SuppressWarnings("deprecation")
private static void initializeGlide(
    @NonNull Context context,
    @NonNull GlideBuilder builder,
    @Nullable GeneratedAppGlideModule annotationGeneratedModule) {
    //获取应用级别的上下文
  Context applicationContext = context.getApplicationContext();
  List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
  if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
    manifestModules = new ManifestParser(applicationContext).parse();
  }
  if (annotationGeneratedModule != null
      && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
    Set<Class<?>> excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses();
    Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
    while (iterator.hasNext()) {
      com.bumptech.glide.module.GlideModule current = iterator.next();
      if (!excludedModuleClasses.contains(current.getClass())) {
        continue;
      }
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "AppGlideModule excludes manifest GlideModule: " + current);
      }
      iterator.remove();
    }
  }
  if (Log.isLoggable(TAG, Log.DEBUG)) {
    for (com.bumptech.glide.module.GlideModule glideModule : manifestModules) {
      Log.d(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass());
    }
  }
//通过注解生成的代码拿到 RequestManagerFactory
  RequestManagerRetriever.RequestManagerFactory factory =
      annotationGeneratedModule != null
          ? annotationGeneratedModule.getRequestManagerFactory()
          : null;
//将拿到的工厂添加到 GlideBuilder
  builder.setRequestManagerFactory(factory);
  for (com.bumptech.glide.module.GlideModule module : manifestModules) {
    module.applyOptions(applicationContext, builder);
  }
  if (annotationGeneratedModule != null) {
    annotationGeneratedModule.applyOptions(applicationContext, builder);
  }
//通过 Builder 建造者模式,构建出 Glide 实例对象
  Glide glide = builder.build(applicationContext);
//注册组件回调
  for (com.bumptech.glide.module.GlideModule module : manifestModules) {
    try {
      module.registerComponents(applicationContext, glide, glide.registry);
    } catch (AbstractMethodError e) {
      throw new IllegalStateException(
          "Attempting to register a Glide v3 module. If you see this, you or one of your"
              + " dependencies may be including Glide v3 even though you're using Glide v4."
              + " You'll need to find and remove (or update) the offending dependency."
              + " The v3 module name is: "
              + module.getClass().getName(),
          e);
    }
  }
  if (annotationGeneratedModule != null) {
    annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
  }
  applicationContext.registerComponentCallbacks(glide);
//将构建出来的 glide 赋值给 Glide 的静态变量
  Glide.glide = glide;
}

这个函数里面的信息量是非常大的,主要是进行了一系列的初始化操作,通过 Builder 建造者模式,构建出 Glide 实例对象,接着我们看看builder模式进行了哪些构建:

@NonNull
Glide build(@NonNull Context context) {
//网络请求的线程池
  if (sourceExecutor == null) {
    sourceExecutor = GlideExecutor.newSourceExecutor();
  }
//本地磁盘缓存的线程池
  if (diskCacheExecutor == null) {
    diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
  }
//加载图片动画的一个线程池
  if (animationExecutor == null) {
    animationExecutor = GlideExecutor.newAnimationExecutor();
  }
//对图片加载到内存的一个计算
  if (memorySizeCalculator == null) {
    memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
  }
//默认网络连接监控的工厂
  if (connectivityMonitorFactory == null) {
    connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
  }
//Bitmap 对象池
  if (bitmapPool == null) {
    int size = memorySizeCalculator.getBitmapPoolSize();
    if (size > 0) {
      bitmapPool = new LruBitmapPool(size);
    } else {
      bitmapPool = new BitmapPoolAdapter();
    }
  }
//数组对象池
  if (arrayPool == null) {
    arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
  }
//资源内存缓存
  if (memoryCache == null) {
    memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
  }
//磁盘缓存的工厂
  if (diskCacheFactory == null) {
    diskCacheFactory = new InternalCacheDiskCacheFactory(context);
  }
//构建执行缓存策略跟线程池的引擎
  if (engine == null) {
    engine =
        new Engine(
            memoryCache,
            diskCacheFactory,
            diskCacheExecutor,
            sourceExecutor,
            GlideExecutor.newUnlimitedSourceExecutor(),
            animationExecutor,
            isActiveResourceRetentionAllowed);
  }
  if (defaultRequestListeners == null) {
    defaultRequestListeners = Collections.emptyList();
  } else {
    defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
  }
//RequestManagerRetriever 请求管理类
  RequestManagerRetriever requestManagerRetriever =
      new RequestManagerRetriever(requestManagerFactory);
  return new Glide(
      context,
      engine,
      memoryCache,
      bitmapPool,
      arrayPool,
      requestManagerRetriever,
      connectivityMonitorFactory,
      logLevel,
      defaultRequestOptionsFactory,
      defaultTransitionOptions,
      defaultRequestListeners,
      isLoggingRequestOriginsEnabled,
      isImageDecoderEnabledForBitmaps);
}

这里就完成了Glide的构建。

然后我们回到最开始的getRetriever(activity).get(activity),看看这个函数里面都干了些什么:

@NonNull
public RequestManager get(@NonNull FragmentActivity activity) {
//首先判断当前是否在子线程中
  if (Util.isOnBackgroundThread()) {
//在子线程中 就通过Application级别的上下文加载
    return get(activity.getApplicationContext());
  } else {
//检查Activity是否已经销毁
    assertNotDestroyed(activity);
//拿到当前activity的fragmentmanager
    FragmentManager fm = activity.getSupportFragmentManager();
//生成一个fragment 然后绑定一个请求管理类RequestManager
    return supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
  }
}

看到这里,我们就应该意识到一个问题,如果我们在子线程中去调用Glide的话,就会使用应用级别的上下文去加载图片,这样大量使用,可能会导致内存泄漏。

然后在主线程中使用,就是Glide的精髓之一了,加载一个透明fragment来感应当前页面的生命周期的变化。

接着看看supportFragmentGet是怎么实现的:

@NonNull
private RequestManager supportFragmentGet(
    @NonNull Context context,
    @NonNull FragmentManager fm,
    @Nullable Fragment parentHint,
    boolean isParentVisible) {
//在当前的 Acitivty 添加一个 Fragment 用于管理请求的生命周期
  SupportRequestManagerFragment current =
      getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
//拿到当前请求的管理类
  RequestManager requestManager = current.getRequestManager();
//如果为空 就创建一个
  if (requestManager == null) {
    // TODO(b/27524013): Factor out this Glide.get() call.
    Glide glide = Glide.get(context);
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    current.setRequestManager(requestManager);
  }
  return requestManager;
}

这里就是添加一个fragment管理生命周期。然后返回一个RequestManager对象。

接下来再具体看看getSupportRequestManagerFragment是怎么添加的:

@NonNull
private SupportRequestManagerFragment getSupportRequestManagerFragment(
    @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
//通过tag拿到fragment实例 避免重复
  SupportRequestManagerFragment current =
      (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
//没有实例的话 就去缓存找
  if (current == null) {
    current = pendingSupportRequestManagerFragments.get(fm);
//缓存也没有 就创建一个
    if (current == null) {
      current = new SupportRequestManagerFragment();
      current.setParentFragmentHint(parentHint);
      if (isParentVisible) {
        current.getGlideLifecycle().onStart();
      }
      pendingSupportRequestManagerFragments.put(fm, current);
      fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
      handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
    }
  }
  return current;
}

这里主要是拿到fragment实例。

从上面我们可以看出Glide主要是做了一些初始化的工作,缓存,线程池,复用池的构建等等。

然后绑定了一个fragment来管理生命周期

with的源码差不多就是这些,可能比较繁琐,但是不像rxjava那样绕来绕去的,主线还是比较清晰的。

五,模拟Glide生命周期管理

首先创建一个接口,监听生命周期回调:

public interface LifecycleListener {
    
    void onCreate();


    void onStart();


    void onStop();


    void onDestroy();

}

然后创建一个用来管理生命周期监听的接口:

public interface Lifecycle {

    void addListener(@NonNull LifecycleListener listener);

    void removeListener(@NonNull LifecycleListener listener);

}

通过上面的分析,我们知道Glide传入的上下文有应用级别的和非应用级别的。所以我们首先创建一个应用级别的Lifecycle:

public class ApplicationLifecycle implements Lifecycle {

    @Override
    public void addListener(@NonNull LifecycleListener listener) {
        /**
         * APP启动的时候,执行onCreate
         */
        listener.onCreate();
    }

    @Override
    public void removeListener(@NonNull LifecycleListener listener) {
        
       //APP销毁的时候,啥也不用做了
    }
}

然后创建一个非应用级别的lifecycle:

public class ActivityFragmentLifecycle implements Lifecycle {

    private final Set<LifecycleListener> lifecycleListeners =
            Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
    private boolean isStarted; 
    private boolean isDestroyed; 

    @Override
    public void addListener(@NonNull LifecycleListener listener) {
        lifecycleListeners.add(listener);

        if (isDestroyed) {
            listener.onDestroy();
        } else if (isStarted) {
            listener.onCreate();
        } else {
            listener.onStop();  
        }
    }

    @Override
    public void removeListener(@NonNull LifecycleListener listener) {
        lifecycleListeners.remove(listener);
    }

    void onStart() {
        isStarted = true;
        for (LifecycleListener lifecycleListener : lifecycleListeners) {
            lifecycleListener.onCreate();
        }
    }

    void onStop() {
        isStarted = false;
        for (LifecycleListener lifecycleListener : lifecycleListeners) {
            lifecycleListener.onStop();
        }
    }

    void onDestroy() {
        isDestroyed = true;
        for (LifecycleListener lifecycleListener : lifecycleListeners) {
            lifecycleListener.onDestroy();
        }
    }
}

创建一个具体的RequestManager 管理生命周期:

public class RequestManager implements LifecycleListener {

    private Lifecycle lifecycle;

    public RequestManager(Lifecycle lifecycle, Context applicationContext) {
        this.lifecycle = lifecycle;
        this.lifecycle.addListener(this);
    }

    @Override
    public void onCreate() {
        Log.d(LOG.TAG, "yh-----onCreate");
    }

    @Override
    public void onStart() {
        Log.d(LOG.TAG, "yh-----onStart");
    }

    @Override
    public void onStop() {
        Log.d(LOG.TAG, "yh-----onStop");
    }

    @Override
    public void onDestroy() {
        Log.d(LOG.TAG, "yh-----onDestroy");
        this.lifecycle.removeListener(this);
    }
}

创建一个空白的fragment:

public class RequestManagerFragment extends Fragment {

    
    private final ActivityFragmentLifecycle lifecycle;
    @Nullable
    private RequestManager requestManager;

    public RequestManagerFragment() {
        this(new ActivityFragmentLifecycle());
    }

    @VisibleForTesting
    @SuppressLint("ValidFragment")
    public RequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {
        this.lifecycle = lifecycle;
    }

    public void setRequestManager(@Nullable RequestManager requestManager) {
        this.requestManager = requestManager;
    }

    @NonNull
    public ActivityFragmentLifecycle getGlideLifecycle() {
        return lifecycle;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        this.lifecycle.addListener(requestManager);
    }

    @Nullable
    public RequestManager getRequestManager() {
        return requestManager;
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    @Override
    public void onStart() {
        super.onStart();
        lifecycle.onStart();
    }

    @Override
    public void onStop() {
        super.onStop();
        lifecycle.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        lifecycle.onDestroy();
    }
}

简单写一个Glide管理类:

public class Glide {

    private static volatile Glide glide;

    @NonNull
    public static Glide get(@NonNull Context context) {
        if (glide == null) {
            synchronized (Glide.class) {
                if (glide == null) {
                    glide = new Glide();
                }
            }
        }
        return glide;
    }

    @NonNull
    public static RequestManager with(@NonNull Context context) {
        return new RequestManager(new ActivityFragmentLifecycle(),context);
    }

    @NonNull
    public static RequestManager withApplication(@NonNull Context context) {
        return new RequestManager(new ApplicationLifecycle(),context);
    }

}

使用也很简单:

public class DemoActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Glide.with(this);
        Glide.withApplication(this.getApplicationContext());
    }
}

这样当DemoActivity创建时,会创建一个ActivityFragmentLifecycle,执行它的onStart方法,从而调用RequestManager的onStart方法,onDestory也是同样的道理。这样就实现了生命周期的检测。这里只是简单写下有助于理解Glide内部是怎么做的。

关于load和into以及缓存机制 改天再写。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

服务器Ubuntu22.04系统下 ollama的详细部署安装和搭配open_webui使用

服务器Ubuntu22.04系统下 ollama的详细部署安装和搭配open_webui使用 一、ubuntu和docker基本环境配置 1.更新包列表&#xff1a;2. 安装docker依赖3. 添加docker密钥4.添加阿里云docker软件源5.安装docker6.安装完成docker测试7. docker配置国内镜像源 二、安装英伟达显卡…

【视频生成模型】——Hunyuan-video 论文及代码讲解和实操

&#x1f52e;混元文生视频官网 | &#x1f31f;Github代码仓库 | &#x1f3ac; Demo 体验 | &#x1f4dd;技术报告 | &#x1f60d;Hugging Face 文章目录 论文详解基础介绍数据预处理 &#xff08;Data Pre-processing&#xff09;数据过滤 (Data Filtering)数据标注 (Data…

Node的学习以及学习通过Node书写接口并简单操作数据库

Node的学习 Node的基础上述是关于Node的一些基础&#xff0c;总结的还行&#xff1b; 利用Node书写接口并操作数据库 1. 初始化项目 创建新的项目文件夹&#xff0c;并初始化 package.json mkdir my-backend cd my-backend npm init -y2. 安装必要的依赖 安装Express.js&…

git diff 查看差异

一.查看工作区和暂存区之间文件的差异 git diff 命令&#xff0c;默认查看的就是 工作区 和 暂存区之间文件的差异 1.git diff : 查看工作区和暂存区之间所有的文件差异 2.git diff -- 文件名&#xff1a;查看具体某个文件 在工作区和暂存区之间的差异 3.git diff -- 文件名…

linux网络编程 | c | epoll实现IO多路转接服务器

epoll实现IO多路转接服务器 可通过以下视频学习 06-opell函数实现的多路IO转接_哔哩哔哩_bilibili 通过响应式–多路IO转接实现 文章目录 epoll实现IO多路转接服务器1.思路&功能核心思路 2.代码实现multi_epoll_sever.c运行图 1.思路&功能 **功能&#xff1a;**客…

Java全体系精华(上):从基础到框架,构建坚实开发技能

爱的故事.上集 1. Java 基础1.1 常用集合数据结构 Array List Map Set Tree1.1.1 常用集合在JDK中的结构1.1.2 List 底层是数组1.1.3 Map键值对结存储结构1.1.3.1 为什么HashMap的Key、Value都允许为 null1.1.3.2 为什么ConcurrentHashMap的Key、Value都不允许为null1.1.3.3 Ha…

基于Clinical BERT的医疗知识图谱自动化构建方法,双层对比框架

基于Clinical BERT的医疗知识图谱自动化构建方法&#xff0c;双层对比框架 论文大纲理解1. 确认目标2. 目标-手段分析3. 实现步骤4. 金手指分析 全流程核心模式核心模式提取压缩后的系统描述核心创新点 数据分析第一步&#xff1a;数据收集第二步&#xff1a;规律挖掘第三步&am…

ctfshow--web入门之爆破篇

知识点&#xff1a; 暴力破解原理 暴力破解实际就是疯狂的输入密码进行尝试登录&#xff0c;针对有的人喜欢用一些个人信息当做密码&#xff0c;有的人喜欢用一些很简单的低强度密码&#xff0c;我们就可以针对性的生成一个字典&#xff0c;用脚本或者工具挨个去尝试登录。 …

Web项目图片视频加载缓慢/首屏加载白屏

Web项目图片视频加载缓慢/首屏加载白屏 文章目录 Web项目图片视频加载缓慢/首屏加载白屏一、原因二、 解决方案2.1、 图片和视频的优化2.1.1、压缩图片或视频2.1.2、 选择合适的图片或视频格式2.1.3、 使用图片或视频 CDN 加速2.1.4、Nginx中开启gzip 三、压缩工具推荐 一、原因…

Go 语言与时间拳击理论下的结对编程:开启高效研发编程之旅

一、引言 结对编程作为一种软件开发方法&#xff0c;在提高代码质量、增强团队协作等方面具有显著优势。而时间拳击理论为结对编程带来了新的思考角度。本文将以 Go 语言为中心&#xff0c;深入探讨时间拳击理论下的结对编程。 在当今软件开发领域&#xff0c;高效的开发方法和…

SpringBoot集成ENC对配置文件进行加密

在线MD5生成工具 配置文件加密&#xff0c;集成ENC 引入POM依赖 <!-- ENC配置文件加密 --><dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.2</ver…

el-table 多表头+跨行跨列案例

效果&#xff1a; 代码&#xff1a; index.vue <template><div class"my-table"><el-tablev-loading"table.loading":data"table.data"bordersize"mini":header-cell-style"headerCellStyle":span-method&qu…

基线检查:Windows安全基线.【手动 || 自动】

基线定义 基线通常指配置和管理系统的详细描述&#xff0c;或者说是最低的安全要求&#xff0c;它包括服务和应用程序设置、操作系统组件的配置、权限和权利分配、管理规则等。 基线检查内容 主要包括账号配置安全、口令配置安全、授权配置、日志配置、IP通信配置等方面内容&…

【electron】electron forge + vite + vue + electron-release-server 自动更新客户端

基本信息 electron forge vue页面&#xff08;中文&#xff09;&#xff1a;https://forge.electron.js.cn/guides/framework-integration/vue-3 electron forge vue页面&#xff08;英文&#xff0c;中文版下面的tab无法点击&#xff09;&#xff1a;https://www.electronfor…

ubuntu+ros新手笔记(二):古月·ROS2入门21讲学习笔记

系统ubuntu22.04 ros2 humble 按照如下视频教程学习的&#xff1a;【古月居】古月ROS2入门21讲 | 带你认识一个全新的机器人操作系统 此处仅记录我报错的地方&#xff0c;以及相应的解决方案&#xff0c;没有出错的略过&#xff01; 对应的古月居ROS2入门21讲源码下载地址&a…

IDEA 可视化使用 git rebase 合并分支步骤 使git分支树保持整洁

模拟环境 dev 分支开发完一个功能&#xff0c;需要合并到 master 分支&#xff0c;如果现在直接 merge 合并的话 git分支树会出现杂乱分叉&#xff0c;先把 master 分支 rebase 到 dev git分支树就会是整洁的一条直线 git rebase介绍 rebase:翻译成中文是重新设定&#xff0c;…

Windows环境 (Ubuntu 24.04.1 LTS ) 国内镜像,用apt-get命令安装RabbitMQ,java代码样例

一、环境 Windows11 WSL(Ubuntu 24.04.1) 二、思路 1 用Windows中的Ubuntu安装RabbitMQ&#xff0c;贴近Linux的线上环境&#xff1b; 2 RabbitMQ用erlang语言编写的&#xff0c;先安装erlang的运行环境&#xff1b; 2 用Linux的apt-get命令安装&#xff0c;解决软件依赖…

使用ElasticSearch实现全文检索

文章目录 全文检索任务描述技术难点任务目标实现过程1. java读取Json文件&#xff0c;并导入MySQL数据库中2. 利用Logstah完成MySQL到ES的数据同步3. 开始编写功能接口3.1 全文检索接口3.2 查询详情 4. 前端调用 全文检索 任务描述 在获取到数据之后如何在ES中进行数据建模&a…

CTFHUB-web(SSRF)

内网访问 点击进入环境&#xff0c;输入 http://127.0.0.1/flag.php 伪协议读取文件 /?urlfile:///var/www/html/flag.php 右击查看页面源代码 端口扫描 1.根据题目提示我们知道端口号在8000-9000之间,使用bp抓包并进行爆破 POST请求 点击环境&#xff0c;访问flag.php 查看页…

数据结构 ——前缀树查词典的实现

数据结构 ——前缀树查词典的实现 一、前缀树的概念 前缀树是一种多叉树结构&#xff0c;主要用于存储字符串。每个节点代表一个字符&#xff0c;路径从根节点到叶节点表示一个完整的字符串。前缀树的关键特征是 共享前缀&#xff0c;也就是说&#xff0c;如果两个字符串有相…