一篇文章搞定《LeakCanary源码详解(全)》

news2024/12/27 11:17:58

一篇文章搞定《LeakCanary源码解析》

  • 前言
  • LeakCanary和LeakCanary2区别
  • LeakCanary的快速使用
    • 第一步:添加依赖
    • 第二步:初始化LeakCanary
    • 第三步:运行应用程序并监测内存泄漏
  • LeakCanary基础铺垫
    • 四大引用
    • WeakReference和ReferenceQueue
      • Refercence
      • ReferenceQueue
      • WeakReference
  • LeakCanary源码解析
    • 初始化
      • LeakCanary1的install
      • LeakCanary2的install
      • AppWatcherInstaller.kt
      • AppWatcher.manualInstall()
      • appDefaultWatchers
      • InternalLeakCanary 引擎初始化
    • 注入对四种 Android 泄漏场景的监控
      • Activity监听:ActivityWatcher
      • Fragment 监听:FragmentAndViewModelWatcher
      • RootView 监控:RootViewWatcher
      • Service监听:ServiceWatcher
    • 重点:内存检测如何检测的
      • objectWatcher的创建
      • ObjectWatcher
    • LeakCanary的分析泄漏
  • LeakCanary的常见问题
    • IdleHandle在LeakCanay的应用
    • LeakCanary的泄漏是通过两次GC定位的
  • 总结

前言

首先内存泄漏前面也有讲解到,而且也是我们在工作中,常见并且一定会遇见的问题之一。
大家都知道,其实内存泄漏的原因就是:最根本的原因就是该回收的对象没有被及时回收掉,导致了内存泄露。
那么我们在处理这类问题的时候,一定会接触到我们的LeakCanary这个三方框架去帮助我们定位我们的内存泄漏问题。

本篇文章就来讲解一下LeakCanary中的源码,文章目录如下:
1、LeakCanary和LeakCanary2区别
2、LeakCanary的快速使用
3、LeakCanary基础铺垫
4、LeakCanary源码解析
5、LeakCanary的常见问题

LeakCanary和LeakCanary2区别

  • LeakCanary2完全使用 Kotlin 重写
  • LeakCanary2使用新的 Heap 分析工具 Shark,替换到之前的 haha,按官方的说法,内存占用减少了 10 倍
  • LeakCanary2只需要Gradle依赖就可以使用,LeakCanary1还需要进行手动安:LeakCanary.install(this);

LeakCanary的快速使用

第一步:添加依赖

在项目的build.gradle文件中,添加以下依赖:

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}

第二步:初始化LeakCanary

在Application的onCreate()方法中,添加以下代码进行LeakCanary的初始化:

if (LeakCanary.isInAnalyzerProcess(this)) {
    // This process is dedicated to LeakCanary for heap analysis.
    // You should not init your app in this process.
    return;
}
LeakCanary.install(this);

第三步:运行应用程序并监测内存泄漏

现在,每当你运行你的应用程序并存在内存泄漏时,LeakCanary将会在你的设备上显示一个通知。

当你观察到内存泄漏的通知时,你可以点击通知打开LeakCanary的分析结果。这将显示与泄漏相关的类、对象引用的路径、引用链和更多详细信息。通过仔细阅读这些信息,你可以确定内存泄漏的原因以及泄漏的位置。
具体的使用参考《一篇文章搞定《Android内存泄漏》》

LeakCanary基础铺垫

四大引用

这个在《一篇文章搞定《JVM的完美图解》》已经提及过,这里就直接粘贴过来了。
强引用(Strong Reference)
在代码中普遍存在的,类似Object obj = new Object()这类引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

软引用(Sofe Reference)
有用但并非必需的对象,可用SoftReference类来实现软引用。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

弱引用(Weak Reference)
非必需的对象,但它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,JDK提供了WeakReference类来实现弱引用。无论当前内存是否足够,用软引用相关联的对象都会被回收掉。(解决一些内存泄漏的问题都会利用到弱引用)

虚引用(Phantom Reference)
虚引用也称为幽灵引用或幻影引用,是最弱的一种引用关系,JDK提供了PhantomReference类来实现虚引用。为一个对象设置虚引用的唯一目的是:能在这个对象在垃圾回收器回收时收到一个系统通知。

WeakReference和ReferenceQueue

LeakCanary的核心原理是基于WeakReference(弱引用)和ReferenceQueue(引用队列)进行检测。因此在讲解LeakCanary之前,我们先来简单了解一下这些关键的字眼。

Refercence

首先我们先来说一些Reference,因为他是我们四大引用的父类。WeakReference也不例外。
Reference即引用,是一个泛型抽象类。Android中的SoftReference(软引用)、WeakReference(弱引用)、PhantomReference(虚引用)都是继承自Reference。
来看下Reference的几个主要成员变量。

public abstract class Reference<T> {

    // 引用对象,被回收时置null
    volatile T referent;
    //保存即将被回收的reference对象
    final ReferenceQueue<? super T> queue;

    //在Enqueued状态下即引用加入队列时,指向下一个待处理Reference对象,默认为null
    Reference queueNext;
    //在Pending状态下,待入列引用,默认为null
    Reference<?> pendingNext;
}

Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive。

  • Active 一般说来内存一开始被分配的状态都是 Active
  • Pending 快要放入队列(ReferenceQueue)的对象,也就是马上要回收的对象
  • Enqueued 对象已经进入队列,已经被回收的对象。方便我们查询某个对象是否被回收
  • Inactive 最终的状态,无法变成其他的状态。

ReferenceQueue

这个没什么好说的,就是存放引用的引用队列。

  • 在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中。
  • ReferenceQueue则是一个单向链表实现的队列数据结构,存储的是Reference对象。
  • 包含了入列enqueue、出列poll和移除remove操作(这个大家可以自己去看一下)。

WeakReference

为什么要说WeakReference呢,因为LeakCanay就是借助WeakReference的回收特点,再去对比引用队列中的引用从而去检查内存泄漏。
看一下WeakReference的源码吧:
WeakReference.java

public class WeakReference<T> extends Reference<T> {
    /**
     * 创建一个指向给定对象的新弱引用。
     * 新引用没有注册到任何队列。
     */
    public WeakReference(T referent) {
        super(referent);
    }
    /**
     * 创建一个指向给定对象的弱引用
     * 在给定队列中注册。
     */
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

可以看的出来,WeakReference主要是依赖Reference和ReferenceQueue的功能。

LeakCanary源码解析

这里就使用kotlin的版本去帮大家讲解了(没学习过Kotlin的同学,也可以往下看,流程原理很重要)
网上大多都是对2.0之前的版本,现在已经是2.12了。(2.0之后就已经是Kotlin了,所以还是看这个吧)

初始化

先上流程图:防止有些同学看到长篇大论就想跑
在这里插入图片描述
首先呢在2.0之前的版本需要通过在在Application的onCreate()方法中调用LeakCanary.install(this),来进行初始化工作。
在2.0之后的版本只需要在build.gradle引入项目就完事了

LeakCanary1的install

这里还是说一下之前的install都干了些什么吧:

  1. 检查设备和应用程序是否满足LeakCanary的要求。例如,LeakCanary需要在Android 4.0或以上版本的设备上使用,并且需要使用Android Gradle插件版本1.5或以上。
  2. 在应用程序中安装一个LeakCanary.RefWatcher对象。该对象将监视应用程序中的Activity和Fragment实例,并且在这些实例没有被正确释放时,将会触发通知。
  3. 启动一个后台线程来检测内存泄漏。这个后台线程会定期检查堆中的对象是否泄漏,并将结果报告给开发人员。
  4. 在应用程序崩溃时,LeakCanary将收集关于内存泄漏的详细信息,并将这些信息存储起来,以便开发人员进行分析和调试

LeakCanary2的install

那么LeakCanary是怎么省略了这个初始化操作呢?

  • 原理就是利用ContentProvider的特性,其onCreate方法会在Application的onCreate方法之前被系统调用。
  • 所以只需要在AndroidManifest.xml中配置一下这个ContentProvider,然后在ContentProvider的onCreate方法中进行初始化即可。
  • 这里注册了一个继承自ContentProvider的AppWatcherInstaller作为注册使用的ContentProvider

让我们来看一下代码
leakcanary-object-watcher-android/src/main/AndroidManifest.xml

<manifest>
  <application>
    <provider
        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
        android:authorities="${applicationId}.leakcanary-installer"
        android:exported="false"/>
  </application>
</manifest>

AppWatcherInstaller.kt

这里我们需要知道两个点。

  • 在onCreate中的AppWatcher.manualInstall(application)。帮助我们来注册了所以不用手动注册
  • 所有的CURD都是空实现,说明这个ContentProvider就是用来初始化的,很纯粹。这也是无入侵的初始化的一种方式(三方库比较常见)。
internal sealed class AppWatcherInstaller : ContentProvider() {
  internal class MainProcess : AppWatcherInstaller()
  
  internal class LeakCanaryProcess : AppWatcherInstaller() {
    override fun onCreate(): Boolean {
      super.onCreate()
      AppWatcher.config = AppWatcher.config.copy(enabled = false)
      return true
    }
  }

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }

  override fun query(
    uri: Uri,
    strings: Array<String>?,
    s: String?,
    strings1: Array<String>?,
    s1: String?
  ): Cursor? {
    return null
  }

  override fun getType(uri: Uri): String? {
    return null
  }

  override fun insert(
    uri: Uri,
    contentValues: ContentValues?
  ): Uri? {
    return null
  }

  override fun delete(
    uri: Uri,
    s: String?,
    strings: Array<String>?
  ): Int {
    return 0
  }

  override fun update(
    uri: Uri,
    contentValues: ContentValues?,
    s: String?,
    strings: Array<String>?
  ): Int {
    return 0
  }
}

继续往下看,AppWatcherInstaller也没啥说的。

AppWatcher.manualInstall()

@JvmOverloads
fun manualInstall(
  application: Application,
  // 默认保持5s延迟检测
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  // 主线程判断    
  checkMainThread()
  // 防止重复加载
  if (isInstalled) {
    throw IllegalStateException(
      "AppWatcher already installed, see exception cause for prior install call", installCause
    )
  }
  // 延时时间检测,必须大于0才可以,否则抛出异常
  check(retainedDelayMillis >= 0) {
    "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
  }
  installCause = RuntimeException("manualInstall() first called here")
  // 缓存延迟检测时间
  this.retainedDelayMillis = retainedDelayMillis
  // LogcatSharkLog只有在debug状态下才会打印
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // 获取InternalLeakCanary实例加载leakCanary
  LeakCanaryDelegate.loadLeakCanary(application)
  // 注册五种 Android 泄漏场景的监控 Hook 点
  watchersToInstall.forEach {
    it.install()
  }
}

其中的watchersToInstall循环注册的appDefaultWatchers内容如下,后面会讲解到

appDefaultWatchers

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher 
  //reachabilityWatcher默认值为objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

LeakCanary 的初始化工程可以概括为 2 项内容:

  • 1、初始化 LeakCanary 内部分析引擎;
  • 2、在 Android Framework 上注册四种 Android 泄漏场景的监控。

InternalLeakCanary 引擎初始化

这个就不细说了。

LeakCanaryDelegate.loadLeakCanary(application)

他是通过反射构建了内部引擎对象,之后主要的内容在invoke方法中。
invoke

override fun invoke(application: Application) {
    _application = application

    // 1. 检查是否运行在 debug 构建变体,否则抛出异常
    checkRunningInDebuggableBuild()

    // 2. 注册泄漏回调,在 ObjectWathcer 判定对象发生泄漏会后回调 onObjectRetained() 方法
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    // 3. 垃圾回收触发器(用于调用 Runtime.getRuntime().gc())
    val gcTrigger = GcTrigger.Default
    // 4. 配置提供器
    val configProvider = { LeakCanary.config }
    // 5. (主角) 创建 HeapDump 触发器
    heapDumpTrigger = HeapDumpTrigger(...)

    // 6. App 前后台切换监听
    application.registerVisibilityListener { applicationVisible ->
        this.applicationVisible = applicationVisible
        heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    // 7. 前台 Activity 监听(用于发送 Heap Dump 进行中的全局 Toast)
    registerResumedActivityListener(application)

    // 8. 增加可视化分析报告的桌面快捷入口
    addDynamicShortcut(application)
}

override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
    heapDumpTrigger.scheduleRetainedObjectCheck()
}
// App 前后台切换状态变化回调
fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
    if (applicationVisible) {
        // App 可见
        applicationInvisibleAt = -1L
    } else {
        // App 不可见
        applicationInvisibleAt = SystemClock.uptimeMillis()
        scheduleRetainedObjectCheck(delayMillis = AppWatcher.retainedDelayMillis)
    }
}

fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
    // 已简化:源码此处使用时间戳拦截,避免重复 postDelayed
    backgroundHandler.postDelayed({
        checkScheduledAt = 0
        checkRetainedObjects()
    }, delayMillis)
}

简单来说:就是创建 HeapDumpTrigger 触发器,并在 Android Framework 上注册前后台切换监听、前台 Activity 监听和 ObjectWatcher 的泄漏监听。
主要就是初始化我们dump heap的相关内容。

注入对四种 Android 泄漏场景的监控

上面也提到了分别是

  • ActivityWatcher(application, reachabilityWatcher)
  • FragmentAndViewModelWatcher(application, reachabilityWatcher)
  • RootViewWatcher(reachabilityWatcher)
  • ServiceWatcher(reachabilityWatcher)

Activity监听:ActivityWatcher

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
    
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        // 通过objectWatcher监视activity
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    // 注册应用activity生命周期回调(application里的)
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    // 反注册activity生命周期回调
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

总结:通过install方法注册了activity的生命周期回调,并在onActivityDestroyed()方法中通过ObjectWatcher对activity进行监视
那其他的几个是否和Actvity一样呢? 接着往下看吧别猜了。整的好像你挺能似儿的。

Fragment 监听:FragmentAndViewModelWatcher

这个源码看着比ActivityWatcher多的,我们拆开来看。下面的整合到一起是FragmentAndViewModelWatcher的完整代码
第一段:注册和反注册

override fun install() {
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}

override fun uninstall() {
  application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}

在install方法中注册Activity生命周期回调,因为Fragment是依附于Activity来添加,所以此处注册Activity生命周期回调
第二段:lifecycleCallbacks回调

private val lifecycleCallbacks =
  //注册Activity生命周期回调,在Activity的onActivityCreated()方法中遍历这些watcher方法类型,
  //实际调用的是对应的invoke方法
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityCreated(
      activity: Activity,
      savedInstanceState: Bundle?
    ) {
      for (watcher in fragmentDestroyWatchers) {
        watcher(activity)
      }
    }
  }

第三段:版本判断真正的fragmentDestroyWatchers

//fragmentDestroyWatchers列表,支持不同Fragment实例的检测;
//这里的watcher都继承自(Activity)->Unit表示方法类型/函数类型,
//参数为Activity,返回值为空;因为是方法类型所以需要重写invoke方法
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()

  //Android O后构建AndroidOFragmentDestroyWatcher
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }

  //如果Class.for(className)能找到androidx.fragment.app.Fragment和
  //leakcanary.internal.AndroidXFragmentDestroyWatcher
  //则添加AndroidXFragmentDestroyWatcher则添加
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME,
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    //  添加观察者
    fragmentDestroyWatchers.add(it)
  }

  //如果Class.for(className)能找到android.support.v4.app.Fragment和
  //leakcanary.internal.AndroidSupportFragmentDestroyWatcher
  //则添加AndroidSupportFragmentDestroyWatcher
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
    reachabilityWatcher
  )?.let {
    //  添加观察者
    fragmentDestroyWatchers.add(it)
  }
  fragmentDestroyWatchers
}
  • 如果系统是Android O以后版本,使用AndroidOFragmentDestroyWatcher
  • 如果app使用的是androidx中的fragment,则添加对应的AndroidXFragmentDestroyWatcher
  • 如果使用support库中的fragment,则添加AndroidSupportFragmentDestroyWatcher。

最终在invoke方法中使用对应的fragmentManager注册Fragment的生命周期回调,在onFragmentViewDestroyed()和onFragmentDestroyed()方法中使用ObjectWatcher来检测fragment。
第四段:通过反射获取当前Activity

private fun getWatcherIfAvailable(
  fragmentClassName: String,
  watcherClassName: String,
  reachabilityWatcher: ReachabilityWatcher
): ((Activity) -> Unit)? {
  // 通过类名获取类对象
  return if (classAvailable(fragmentClassName) &&
    classAvailable(watcherClassName)
  ) {       
    // 获取构造方法Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)   
    // 通过构造方法实例化activity
    val watcherConstructor =
      Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
    @Suppress("UNCHECKED_CAST")
    watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit
  } else {
    null
  }
}

这里获取了当前的Activity。在第三步中先判断Fragment版本。之后他当前的Activity加入到了生命周期的监听中。
这不就和之前的Activity的流程一样了吗。嘻嘻

最后都是回调相关的Fragment的Invoke上面代码的注释也有写。那看看其中的一个invoke吧。
AndroidOFragmentDestroyWatcher.invoke()

override fun invoke(activity: Activity) {
  //取得对应的FragmentManager
  val fragmentManager = activity.fragmentManager
  //注册生命周期回调
  fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}

其实其他版本的Fragment的invoke都是做了同样的事情,去注册生命周期的回调。

RootView 监控:RootViewWatcher

由于 Android Framework 未提供设置全局监听 RootView 从 WindowManager 中移除的方法,所以 LeakCanary 是通过 Hook 的方式实现的。
主要是利用以下两个hook步骤

  • Hook WMS 服务内部的 WindowManagerGlobal.mViews RootView 列表,获取 RootView 新增和移除的时机;
  • 检查 View 对应的 Window 类型,如果是 Dialog 或 DreamService 等类型,则在注册 View#addOnAttachStateChangeListener() 监听,在其中的 onViewDetachedFromWindow() 回调中将 View 对象交给 ObjectWatcher 监控。

这里我们也是分步的看一下具体的源码。
RootViewWatcher.kt 中install() uninstall和方法

override fun install() {
  //注册 RootView 监听
  Curtains.onRootViewsChangedListeners += listener
}

override fun uninstall() {
  Curtains.onRootViewsChangedListeners -= listener
}

我们看到install方法将listener添加到了Curtains的OnRootViewsChangedListener列表中,而Curtains中onRootViewsChangedListeners记录了root views的数量并在数量发生变更后调用onRootViewsChanged方法
RootViewWatcher.kt 中listener

private val listener = OnRootViewAddedListener { rootView ->
    // 根据windowType 类型不同判断trackDetached状态
    val trackDetached = when(rootView.windowType) {
        PHONE_WINDOW -> {
            when (rootView.phoneWindow?.callback?.wrappedCallback) {
                // Activities are already tracked by ActivityWatcher
                is Activity -> false
                is Dialog -> rootView.resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
                // Probably a DreamService
                else -> true
            }
        }
        // Android widgets keep detached popup window instances around.
        POPUP_WINDOW -> false
        TOOLTIP, TOAST, UNKNOWN -> true
    }

    // trackeDetached状态为true,添加rootView的状态变更监听
    if (trackDetached) {
        rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

            // 开启子线程检查rootView引用状态检测是否存在内存泄漏
            val watchDetachedView = Runnable {
                // 这里也是通过objectWatcher进行监视,前面2.1已经分析,同上
                reachabilityWatcher.expectWeaklyReachable(
                    rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
                )
            }

            // 根据生命周期回调通过mainHandler处理view事件
            override fun onViewAttachedToWindow(v: View) {
                mainHandler.removeCallbacks(watchDetachedView)
            }

            override fun onViewDetachedFromWindow(v: View) {
                mainHandler.post(watchDetachedView)
            }
        })
    }
}
  1. 根据rootView的windowType判断是否需要进行track跟踪
  2. 针对需要跟踪的rootView类型开启子线程检查其引用状态,判断是否存在内存泄漏
  3. 针对需要跟踪的rootView生命周期回调,通过mainHandler进行消息传递

Service监听:ServiceWatcher

由于 Android Framework 未提供设置 Service#onDestroy() 全局监听的方法,所以 LeakCanary 是通过 Hook 的方式实现的。
也是需要通过两步Hook去实现:

  • 1、Hook 主线程消息循环的 mH.mCallback 回调,监听其中的 STOP_SERVICE 消息,将即将 Destroy 的 Service 对象暂存起来(由于 ActivityThread.H 中没有 DESTROY_SERVICE 消息,所以不能直接监听到 onDestroy() 事件,需要第 2 步)
  • 2、使用动态代理 Hook AMS 与 App 通信的的 IActivityManager Binder 对象,代理其中的 serviceDoneExecuting() 方法,视为 Service#onDestroy() 的执行时机,拿到暂存的 Service 对象交给 ObjectWatcher 监控。

分布看一下源码吧:
ServiceWatcher.kt 中install()和uninstall()方法

override fun install() {
    // 1、Hook mH.mCallback
    swapActivityThreadHandlerCallback { mCallback /*原对象*/ ->
        // uninstallActivityThreadHandlerCallback:用于取消 Hook
        uninstallActivityThreadHandlerCallback = {
            swapActivityThreadHandlerCallback {
                mCallback
            }
        }
        // 新对象(lambda 表达式的末行就是返回值)
        Handler.Callback { msg ->
            // Service#onStop() 事件
            if (msg.what == STOP_SERVICE) {
                val key = msg.obj as IBinder
                // activityThreadServices:反射获取 ActivityThread mServices 映射表 <IBinder, CreateServiceData>
                activityThreadServices[key]?.let {
                    // 暂存即将 Destroy 的 Service
                    servicesToBeDestroyed[token] = WeakReference(service)
                }
            }
            // 继续执行 Framework 原有逻辑
            mCallback?.handleMessage(msg) ?: false
        }
    }
    // 2、Hook AMS IActivityManager
    swapActivityManager { activityManagerInterface, activityManagerInstance /*原对象*/ ->
        // uninstallActivityManager:用于取消 Hook
        uninstallActivityManager = {
            swapActivityManager { _, _ ->
                activityManagerInstance
            }
        }
        // 新对象(lambda 表达式的末行就是返回值)
        Proxy.newProxyInstance(activityManagerInterface.classLoader, arrayOf(activityManagerInterface)) { _, method, args ->
            // 代理 serviceDoneExecuting() 方法
            if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
                // 取出暂存的即将 Destroy 的 Service
                val token = args!![0] as IBinder
                if (servicesToBeDestroyed.containsKey(token)) {
                    servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
                        // 交给 ObjectWatcher 监控
                        serviceWeakReference.get()?.let { service ->
                            reachabilityWatcher.expectWeaklyReachable(service /*被监控对象*/, "${service::class.java.name} received Service#onDestroy() callback")
                        }
                    }
                }
            }
            // 继续执行 Framework 原有逻辑
            method.invoke(activityManagerInstance, *args)
        }
    }
}

总结:主要是讲当前服务通过Hook去进行监听
ServiceWatcher.kt 中onServicePreDestroy()

private fun onServicePreDestroy(
  token: IBinder,
  service: Service
) {
  //将服务变更成弱引用,便于GC的回收操作
  servicesToBeDestroyed[token] = WeakReference(service)
}

ServiceWatcher.kt 中onServiceDestroyed()进行委托给ObjectWatcher 进行监控

private fun onServiceDestroyed(token: IBinder) {
  // 将service从servicesToBeDestroyed集合中移除
  servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
    serviceWeakReference.get()?.let { service ->
      // 通过reachabilityWatcher监视内存泄漏的对象
      reachabilityWatcher.expectWeaklyReachable(
        service, "${service::class.java.name} received Service#onDestroy() callback"
      )
    }
  }
}

重点:内存检测如何检测的

先上图吧兄弟们。知道兄弟们爱看图。(本篇文章只说前两个阶段)
在这里插入图片描述
上面的初始化和四种Android的监听已经说完了。为什么要说这么多的提前工作呢?
因为为了更加容易的理解下面的检测的过程。
上面说了,当对象的生命周期结束后,都会交给ObjectWatcher 监控(下面代码可以清晰看到)
四种监控委托给ObjectWatcher 的证据:

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

// activity的监听
private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityDestroyed(activity: Activity) {
      reachabilityWatcher.expectWeaklyReachable(
        activity, "${activity::class.java.name} received Activity#onDestroy() callback"
      )
    }
  }
  
//Fragment的监听
private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityCreated(
      activity: Activity,
      savedInstanceState: Bundle?
    ) {
      for (watcher in fragmentDestroyWatchers) {
        watcher(activity)
      }
    }
  }
  
val watcherConstructor =
  Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit

//RootView的监听
val watchDetachedView = Runnable {
  reachabilityWatcher.expectWeaklyReachable(
    rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
  )
}

//Service的监听
private fun onServiceDestroyed(token: IBinder) {
  servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
    serviceWeakReference.get()?.let { service ->
      reachabilityWatcher.expectWeaklyReachable(
        service, "${service::class.java.name} received Service#onDestroy() callback"
      )
    }
  }
}

那么委托给ObjectWatcher ,并且调用了expectWeaklyReachable方法。他能干什么呢? 或者说他干了什么呢?
预热一下:他主要是完成了以下三步。

  • 第 1 步: 为被监控对象 watchedObject 创建一个 KeyedWeakReference 弱引用,并存储到 <UUID, KeyedWeakReference> 的映射表中
  • 第 2 步: postDelay 五秒后检查引用对象是否出现在引用队列中,出现在队列则说明被监控对象未发生泄漏。随后,移除映射表中未泄露的记录,更新泄漏的引用对象的 retainedUptimeMillis 字段以标记为泄漏
  • 第 3 步: 通过回调 onObjectRetained 告知 LeakCanary 内部发生新的内存泄漏。
    那就上源码吧兄弟们!

objectWatcher的创建

从AppWatcher.kt的appDefaultWatchers可以看到形参objectWatcher。找到他实例化的位置

/**
 * AppWatcher用来检测保留对象的[ObjectWatcher]
 * 仅当[isInstalled]为true时设置
 */
val objectWatcher = ObjectWatcher(
  //1、lambda 表达式获取当前系统时间
  clock = { SystemClock.uptimeMillis() },
  // 2、lambda 表达式实现 Executor SAM 接口
  checkRetainedExecutor = {
    check(isInstalled) {
      "AppWatcher not installed"
    }
    mainHandler.postDelayed(it, retainedDelayMillis)
  },
  // 3、lambda 表达式获取监控开关
  isEnabled = { true }
)

可以看到我们的入参包括:
1、clock:当前的系统时间
2、checkRetainedExecutor:延时的Handler(他和系统时间配合,来根据clock时间来判断时候被回收)
3、isEnabled:监控开关

ObjectWatcher

之后让我们来看看ObjectWatcher内部做了什么,关键的核心都在这里了。
我们也来拆解开看吧
第一步:初始化成员变量

// Object的监听集合
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
// 被监控的对象映射表 <UUID,KeyedWeakReference>
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
// KeyedWeakReference 关联的引用队列,用于判断对象是否泄漏
private val queue = ReferenceQueue<Any>()

来看一下具体的KeyedWeakReference对象

class KeyedWeakReference(
    // 被监控对象
    referent: Any,
    // 唯一 Key,根据此字段匹配映射表中的记录
    val key: String,
    // 描述信息
    val description: String,
    // 监控开始时间,即引用对象创建时间
    val watchUptimeMillis: Long,
    // 关联的引用队列
    referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(referent, referenceQueue) {

    // 记录实际对象 referent 被判定为泄漏对象的时间
    // -1L 表示非泄漏对象,或者还未判定完成
    @Volatile
    var retainedUptimeMillis = -1L

    override fun clear() {
        super.clear()
        retainedUptimeMillis = -1L
    }

    companion object {
        // 记录最近一次触发 Heap Dump 的时间
        @Volatile
        @JvmStatic var heapDumpUptimeMillis = 0L
    }
}

第二步:前面讲到的四种Android的监听都调用了expectWeaklyReachable来帮我们处理生命周期改变后的,内存泄漏的处理。让我们来具体看看。

// 一、处理四种Android的监控
@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  if (!isEnabled()) {
    return
  }
  
  // 移除 watchedObjects 中未泄漏的引用对象(相关方法都在下面介绍了)
  removeWeaklyReachableObjects()
  val key = UUID.randomUUID()
    .toString()
  val watchUptimeMillis = clock.uptimeMillis()
  // 新建一个 KeyedWeakReference 引用对象
  val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
  watchedObjects[key] = reference
  // 五秒后检查引用对象是否出现在引用队列中,否则判定发生泄漏
  // checkRetainedExecutor 相当于 postDelay 五秒后执行 moveToRetained() 方法
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

//二、移除 watchedObjects 中未泄漏的引用对象
private fun removeWeaklyReachableObjects() {
    // 
    var ref: KeyedWeakReference?
    do {
      // 循环引用队列(在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中)
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        // KeyedWeakReference 出现在引用队列中,说明未发生泄漏(因为被回收时的对象才添加到ReferenceQueue中)
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
}

//三、 五秒后检查引用对象是否出现在引用队列中,否则说明发生泄漏
@Synchronized private fun moveToRetained(key: String) {
  // 移除 watchedObjects 中未泄漏的引用对象
  removeWeaklyReachableObjects()
  // 5秒后再次获取当前的引用对象KeyedWeakReference
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    // 说明还在,也就是没被回收掉
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    // 回调通知 LeakCanary 内部处理进行dump
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}

第三步:那么已经泄漏的KeyedWeakReference送去LeakCanary 内部处理进行dump处理之后的对象。我们需要将他从队列中去除。不然不就重复的检查了吗。来看看是怎么操作的。

//Heap Dump 后移除所有监控时间早于 heapDumpUptimeMillis 的引用对象
@Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
  val weakRefsToRemove = watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
  weakRefsToRemove.values.forEach { it.clear() }
  watchedObjects.keys.removeAll(weakRefsToRemove.keys)
}

第四步:这个是暴露出去进行统计的。就是泄漏队列的计数和是否有泄漏对象。提供LeakCanary内部处理使用。

// 获取是否有内存泄漏对象
val hasRetainedObjects: Boolean
@Synchronized get() {
    // 移除 watchedObjects 中未泄漏的引用对象
    removeWeaklyReachableObjects()
    return watchedObjects.any { it.value.retainedUptimeMillis != -1L }
}

// 获取内存泄漏对象计数
val retainedObjectCount: Int
@Synchronized get() {
    // 移除 watchedObjects 中未泄漏的引用对象
    removeWeaklyReachableObjects()
    return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
}

上面的全部内容拼接起来就是我们的核心。ObjectWatcher的全部内容了。
大家都有疑问吧! 上面代码中讲到的onObjectRetainedListeners.forEach { it.onObjectRetained() }。送给LeakCanary进行内部处理。到底是怎么个内部处理,你也没说啊。别着急,下面这不就来了吗!!!!

LeakCanary的分析泄漏

上面说到了onObjectRetainedListeners.forEach { it.onObjectRetained() }。实际的实现内容在我们初始化的引擎中。InternalLeakCanary。
上面也说到了,他的作用主要就是分析和dump我们的泄漏内容。
LeakCanary 不会立即对每次发现内存泄漏对象都进行分析工作,而会进行两个拦截:

  • 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值;
  • 拦截 2:计算距离上一次 HeapDump 未超过 60s。
    来看看源码吧
    InternalLeakCanary.kt
// 从 ObjectWatcher 回调过来 声明的scheduleRetainedObjectCheck方法
override fun onObjectRetained() = scheduleRetainedObjectCheck()

private lateinit var heapDumpTrigger: HeapDumpTrigger

fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
        heapDumpTrigger.scheduleRetainedObjectCheck()
    }
}

HeapDumpTrigger.kt

fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
        return
    }
    // 通过时间戳拦截一下,避免重复的postDelayed
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
        checkScheduledAt = 0
        checkRetainedObjects()
    }, delayMillis)
}

private fun checkRetainedObjects() {
    val config = configProvider()

    // 泄漏对象计数
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    if (retainedReferenceCount > 0) {
        // 主动触发 GC,并等待 100 ms
        gcTrigger.runGc()
        // 重新获取泄漏对象计数
        retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    // 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值(config中的val retainedVisibleThreshold: Int = 5)
    if (retainedKeysCount < retainedVisibleThreshold) {
        // App 位于前台或者刚刚进入后台
        if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
            // 发送通知提醒
            showRetainedCountNotification("App visible, waiting until %d retained objects")
            // 延迟 2 秒再检查
            scheduleRetainedObjectCheck(WAIT_FOR_OBJECT_THRESHOLD_MILLIS)
            return;
        }
    }

    // 拦截 2:计算距离上一次 HeapDump 未超过 60s
    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
        // 发送通知提醒
        showRetainedCountNotification("Last heap dump was less than a minute ago")
        // 延迟 (60 - elapsedSinceLastDumpMillis)s 再检查
        scheduleRetainedObjectCheck(WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis)
        return
    }

    // 移除通知提醒
    dismissRetainedCountNotification()
    // 触发 HeapDump(此时,应用有可能在后台)
    dumpHeap(...)
}

// 真正开始执行 Heap Dump
private fun dumpHeap(...) {
    // 1. 获取文件存储提供器
    val directoryProvider = InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)

    // 2. 创建 .hprof File 文件
    val heapDumpFile = directoryProvider.newHeapDumpFile()

    // 3. 执行 Heap Dump
    // Heap Dump 开始时间戳
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    // heapDumper.dumpHeap:最终调用 Debug.dumpHprofData(heapDumpFile.absolutePath) 
    configProvider().heapDumper.dumpHeap(heapDumpFile)

    // 4. 清除 ObjectWatcher 中过期的监控
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)

    // 5. 分析堆快照
    InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
}

GcTrigger.kt

interface GcTrigger {
  fun runGc()

  object Default : GcTrigger {
    override fun runGc() {
      // Runtime.gc() 相比于 System.gc() 更有可能触发 GC
      Runtime.getRuntime()
        .gc()
      enqueueReferences()
      System.runFinalization()
    }

    private fun enqueueReferences() {
      try {
        // 暂停100ms等待 GC 
        Thread.sleep(100)
      } catch (e: InterruptedException) {
        throw AssertionError()
      }
    }
  }
}

总结:OK了这就是全部的内存检测流程了

LeakCanary的常见问题

IdleHandle在LeakCanay的应用

其实很多人有了误解,IdleHandle只在LeakCanay1的版本在使用。在LeakCanay2就已经去除了。
去除的原因:

  • 为了提高检测内存泄漏的准确性和性能。
  • 之前版本的LeakCanary使用的是IdleHandler来检测内存泄漏,但是这种方式可能会导致一些不准确的结果。
  • 比如在UI线程中执行一个异步任务时,IdleHandler可能无法正确地检测到内存泄漏。
    那就别白说了,来看看LeakCanary1中IdleHandle的使用吧。

为啥使用:为了不影响主线程其他事件呗。 大家都知道IdleHandle是在主现场空闲的时候,来处理事件的。具体的我在《一篇文章搞定〈Handler机制〉》中有详细说哦!!!!!
我们来看看 LeakCanary 的使用,是在AndroidWatchExecutor 这个类中

public final class AndroidWatchExecutor implements WatchExecutor {

  static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
  private final Handler mainHandler;
  private final Handler backgroundHandler;
  private final long initialDelayMillis;
  private final long maxBackoffFactor;

  public AndroidWatchExecutor(long initialDelayMillis) {
    mainHandler = new Handler(Looper.getMainLooper());
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper());
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }
    //初始调用
  @Override public void execute(Retryable retryable) {
    // 最终都会切换到主线程,调用waitForIdle()方法
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0);
    }
  }

  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        waitForIdle(retryable, failedAttempts);
      }
    });
  }
    //IdleHandler 使用
  void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }
  //最终的调用方法
    private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }
}

再来看看 execute() 这个方法在何处被调用,我们知道 LeakCancary 是在界面销毁 onDestroy 方法中进行 refWatch.watch() 的,而watch() -> ensureGoneAsync() -> execute()

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

而 ensureGone() 中会进行 GC 回收和一些分析等操作,所以通过这些分析后,我们可以知道 LeakCanary 进行内存泄漏检测并不是 onDestry 方法执行完成后就进行垃圾回收和一些分析的,而是利用 IdleHandler 在空闲的时候进行这些操作的,尽量不去影响主线程的操作。

LeakCanary的泄漏是通过两次GC定位的

这个其实细心阅读的同学在上面是发现的了。
答案也在上面,就不细说了。
第一次:组件的生命周期结束,ObjectWatcher为当前监听对象,创建的KeyedWeakReference弱引用加入到弱引用队列中,并发出一个5秒的延迟检测消息。(也就是LeakCanary的第一次检测)
第二次:dump文件生成之前,的一次主动GC。(可以认为给了他们最后一次机会,算是确认的过程吧)

// 泄漏对象计数
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
    // 主动触发 GC,并等待 100 ms
    gcTrigger.runGc()
    // 重新获取泄漏对象计数
    retainedReferenceCount = objectWatcher.retainedObjectCount
}

总结

源码能帮我们收获很多知识。但是前提是你有一定的基础了。
不然看起来总是不理解这里的内容是做什么的。
比如本篇文章,需要你对GC的回收、四大引用、引用队列都有一定的了解才行。
加油吧!兄弟们!

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

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

相关文章

【Spring】(二)从零开始的 Spring 项目搭建与使用

文章目录 前言一、Spring 项目的创建1.1 创建 Maven 项目1.2 添加 Spring 框架支持1.3 添加启动类 二、储存 Bean 对象2.1 创建 Bean2.1 将 Bean 注册到 Spring 容器 三、获取并使用 Bean 对象3.1 获取Spring 上下文3.2 ApplicationContext 和 BeanFactory 的区别3.3 获取指定的…

2023-02-03——2023-08-03,半年以来与客服交流的记录【CSND 文章撰写 网站使用求解】客服咨询交流记录(长期更新ing)

这世界上久处不厌,都是因为用心。 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌿[2] 2023年城市之星领跑者TOP1(哈尔滨)🌿 🌟[3] 2022年度博客之星人工智能领域TOP4🌟 🏅[4] 阿里云社区特邀专家博主🏅 🏆

Cesium 实战教程 - 调整 3dtiles 倾斜摄影大小

Cesium 实战教程 - 调整 3dtiles 倾斜摄影大小 核心代码完整代码在线示例 之前由于误解遇到一个特殊的需求&#xff1a;想要把三维球上叠加倾斜摄影进行自由放大缩小&#xff0c;跟随地图的缩放进行缩放。 后来经过搜索、尝试&#xff0c;终于实现了需求。 但是&#xff0c;后…

什么是强化学习?

&#x1f4dd;什么是强化学习&#xff1f; 1. &#x1f4dd;监督&#xff0c;非监督&#xff0c;强化2. &#x1f4dd;非 i.i.d3. &#x1f4dd;强化学习基本形式4. &#x1f4dd;马尔可夫过程 &#x1f31f; 强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#x…

windows安装kafka配置SASL-PLAIN安全认证

目录 1.Windows安装zookeeper&#xff1a; 1.1下载zookeeper 1.2 解压之后如图二 1.3创建日志文件 1.4复制 “zoo_sample.cfg” 文件 1.5更改 “zoo.cfg” 配置 1.6新建zk_server_jaas.conf 1.7修改zkEnv.cmd 1.8导入相关jar 1.9以上配置就配好啦&#xff0c;接下来启…

小红书博主排名丨狂揽近百万粉丝,女性议题成“爆款制造机”?

从上野千鹤子和北大女生的对谈&#xff0c;到电影《消失的她》&#xff0c;再到引爆“粉色狂潮”的电影《芭比》&#xff0c;近年来&#xff0c;女性话题、两性情感话题成为社会热门议题。“踩过恋爱所有坑&#xff0c;想给姑娘撑把伞”&#xff0c;近期&#xff0c;小红书博主…

2023年华数杯数学建模B题思路代码分析 - 不透明制品最优配色方案设计

# 1 赛题 B 题 不透明制品最优配色方案设计 日常生活中五彩缤纷的不透明有色制品是由着色剂染色而成。因此&#xff0c;不透明 制品的配色对其外观美观度和市场竞争力起着重要作用。然而&#xff0c;传统的人工配色 存在一定的局限性&#xff0c;如主观性强、效率低下等。因此…

docker容器学习笔记1

docker容器是干什么用的 docker就是一个轻量级的虚拟机&#xff0c;是一个容器&#xff0c;隔离性好&#xff0c;能够确保环境的统一&#xff0c;有效利用系统资源&#xff0c;轻松迁移和拓展。简单的可以理解为容器就是一个小型功能齐全的虚拟机。 实际上是如何使用的呢&…

RocketMQ发送消息超时异常

说明&#xff1a;在使用RocketMQ发送消息时&#xff0c;出现下面这个异常&#xff08;org.springframework.messging.MessgingException&#xff1a;sendDefaultImpl call timeout……&#xff09;&#xff1b; 解决&#xff1a;修改RocketMQ中broke.conf配置&#xff0c;添加下…

2023华数杯数学建模竞赛C题思路解析

如下为&#xff1a;2023华数杯数学建模竞赛C题 母亲身心健康对婴儿成长的影响 的思路解析 C题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一&#xff0c;她不仅为婴儿提供营养物质和身体保护&#xff0c;还为婴儿提供情感支持和安全感。母亲心理健康状态的不…

HCIP作业3

题目 配置IP地址 R1 [r1]int g0/0/1 [r1-GigabitEthernet0/0/1]ip add 192.168.1.1 24 [r1-Serial4/0/0]ip add 12.1.1.1 24 R2 [r2]int s4/0/0 [r2-Serial4/0/0]ip add 12.1.1.2 24 [r2-Serial4/0/0]int s4/0/1 [r2-Serial4/0/1]ip add 32.1.1.1 24 [r2-Serial4/0/1]in…

数据管理基础知识

数据管理原则 数据管理与其他形式的资产管理的共同特征&#xff0c;涉及了解组织拥有哪些数据以及可以使用这些数据完成哪些工作&#xff0c;然后确定如何最好的使用数据资产来实现组织目标与其他流程一样&#xff0c;他必须平衡战略和运营需求&#xff0c;通过遵循一套原则&a…

k8s nginx+ingress 配置

1 nginx> ingress 配置&#xff1a; 2 nginx >service 配置 3 nginx pod配置&#xff1a; 4 nginx.conf 配置文件&#xff1a; # web端v1server{listen 30006;add_header Strict-Transport-Security "max-age31536000; includeSubDomains";#add_header Content…

【练】要求定义一个全局变量 char buf[] = “1234567“,创建两个线程,不考虑退出条件,打印buf

要求定义一个全局变量 char buf[] "1234567"&#xff0c;创建两个线程&#xff0c;不考虑退出条件&#xff0c;另&#xff1a; A线程循环打印buf字符串&#xff0c;B线程循环倒置buf字符串&#xff0c;即buf中本来存储1234567&#xff0c;倒置后buf中存储7654321. 不…

方差分析||判断数据是否符合正态分布

方差分析练习题 练习学习笔记&#xff1a; &#xff08;1&#xff09; 标准差和标准偏差、均方差是一个东西。标准误差和标准误是一个东西。这两个东西有区别。 &#xff08;2&#xff09;单因素方差分析&#xff08;MATLAB求解&#xff09; &#xff08;3&#xff09;使用an…

重磅!EBImage包:为何如此火爆?它的图像处理到底有何不可思议之处?

一、简介 1.1 EBImage包简介 EBImage包是一个广受欢迎的用于图像处理和分析的R语言包。它提供了一套全面而强大的功能&#xff0c;支持多种图像格式的读取和写入&#xff0c;处理多维图像数据&#xff0c;并提供了各种先进的图像处理算法、特征提取和测量函数。 1.2 EBImage爆火…

STM32——LED内容补充(寄存器点灯及反转的原理)

文章目录 点灯流程开时钟配置IO关灯操作灯反转宏定义最后给自己说 本篇文章使用的是STM32F103xC系列的芯片&#xff0c;四个led灯在PE2,PE3,PE4,PE5上连接 点灯流程 1.开时钟 2.配置IO口 &#xff08;1&#xff09;清零指定寄存器位 &#xff08;2&#xff09;设置模式为推挽输…

【MyBatis】MyBatis 动态SQL

目录 动态标签&#xff1a;<if>动态标签&#xff1a;<trim>动态标签&#xff1a;<where>动态标签&#xff1a;<set>动态标签&#xff1a;<foreach> 动态标签&#xff1a;<if> 语法&#xff1a; <if test"photo!null"> .…

力扣 C++|一题多解之动态规划专题(2)

动态规划 Dynamic Programming 简写为 DP&#xff0c;是运筹学的一个分支&#xff0c;是求解决策过程最优化的过程。20世纪50年代初&#xff0c;美国数学家贝尔曼&#xff08;R.Bellman&#xff09;等人在研究多阶段决策过程的优化问题时&#xff0c;提出了著名的最优化原理&…

爬虫007_python中的输出以及格式化输出_以及输入---python工作笔记025

首先看输出 输出这里,注意不能直接上面这样,18需要转换成字符串 可以看到python中这个字符串和数字一起的时候,数字要转换一下成字符串. 然后这里要注意%s 和%d,这个s指的是字符串,d指的是数字 注意后面的内容前面要放个% ,然后多个参数的话,那么这里用(),里面用,号隔开 然…