LeakCanary 源码深挖

Android内存优化(六)LeakCanary使用详解 如果使用MAT来分析内存问题,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比。 为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary。 阅读详情
一、前言

LeakCanary 是 Android 端用于自动检测内存泄漏的开源库,使用这个工具可以方便的监控 Activity 和 Fragment 的内存泄漏情况, 并且提供了可视化界面, 可以在开发过程中很好的暴露和排查问题。基于 LeakCanary 1.5.4 源码。

二、怎样使用?

添加依赖

dependencies {
    ......
    implementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    ......
}

编写 LeakCanary 使用工具类 LeakCanaryUtil.java

public class LeakCanaryUtil {

    private static RefWatcher sRefWatcher;

    public static void init(@NonNull Application application) {
        if (!BuildConfig.DEBUG) {
            return;
        }
        if (LeakCanary.isInAnalyzerProcess(application)) {
            return;
        }
        sRefWatcher = LeakCanary.install(application);
    }

    public static void watch(@Nullable Object obj) {
        if (sRefWatcher == null || obj == null) {
            return;
        }
        sRefWatcher.watch(obj);
    }

}

在 Application 中初始化

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LeakCanaryUtil.init(this);
    }
}

设置内存泄漏监控 BaseFragment.java

public abstract class BaseFragment extends Fragment {
   
    @Override
    public void onDetach() {
        LeakCanaryUtil.watch(this);
    }

}
三、怎样检测到内存泄漏的?

在 Application 中, LeakCanary 的 install 方法的构建一个 RefWatcher 对象, 并且初始化它

public final class LeakCanary {
    
  public static RefWatcher install(Application application) {
    return refWatcher(application)
               .listenerServiceClass(DisplayLeakService.class)
               .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
               .buildAndInstall();	// 分析 1
  }
    
  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);	// 分析 2
  }
  
}

分析 1 可以看到这是一个构建者的链式调用, 最终通过 buildAndInstall 来完成对 RefWatcher 的构建和安装。分析 2 可以看到这个 RefWatcher 对象通过 AndroidRefWatcherBuilder.buildAndInstall 创建,那么,我们看一下 AndroidRefWatcherBuilder.buildAndInstall 做了什么。

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
    
  public RefWatcher buildAndInstall() {
    // 通过 build 构建实例对象
    RefWatcher refWatcher = build();	// 分析 1
    if (refWatcher != DISABLED) {
      // 注册内存泄漏监听
      ActivityRefWatcher.install((Application) context, refWatcher);	// 分析 2
    }
    return refWatcher;
  }  
    
}

可以看到 AndroidRefWatcherBuilder.buildAndInstall 中, 主要做了两步操作

  • 通过 build 方法, 创建 RefWatcher 对象
  • 调用 ActivityRefWatcher.install 注册来监听内存泄漏

下面我们接着看分析 1 中 build() 方法,其使用了 builder 设计模式去构建初始化对象

public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
    
  /** Creates a {@link RefWatcher}. */
  public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    ......
    // 构建 RefWatcher 对象
    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }  
    
}

public final class RefWatcher {

  public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();

  private final WatchExecutor watchExecutor;
  private final DebuggerControl debuggerControl;
  private final GcTrigger gcTrigger;
  private final HeapDumper heapDumper;
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;
  private final HeapDump.Listener heapdumpListener;
  private final ExcludedRefs excludedRefs;

  RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
      HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {
    // 用于执行监听引用
    this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
    // 判断是否在调试中
    this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
    // 用于执行 GC
    this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
    // 用于将 Heap dump 到文件中
    this.heapDumper = checkNotNull(heapDumper, "heapDumper");
    // 用于接收并分析 heap 信息
    this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
    // 排除系统引起的内存泄露
    this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs");
    // 创建集合, 用于保存待分析对象弱引用对应的 key
    retainedKeys = new CopyOnWriteArraySet<>();
    // 创建引用队列, 用于存储待 GC 的元素
    queue = new ReferenceQueue<>();
  }
    
}

在上面 AndroidRefWatcherBuilder.buildAndInstall 的分析 2 中
AndroidRefWatcherBuilder.java

// 注册内存泄漏监听
ActivityRefWatcher.install((Application) context, refWatcher);	// 分析 2
public final class ActivityRefWatcher {

  public static void install(Application application, RefWatcher refWatcher) {
    // 1. 创建 ActivityRefWatcher 对象
    // 2. 调用 ActivityRefWatcher.watchActivities
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }
  
  private final Application application;
  private final RefWatcher refWatcher;
  
  public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
    this.application = checkNotNull(application, "application");
    this.refWatcher = checkNotNull(refWatcher, "refWatcher");
  }

}

可以看到 ActivityRefWatcher.install 中, 首先创建了 ActivityRefWatcher 实例, 然后调用了它的 watchActivities 方法。
接着看下去 ActivityRefWatcher.watchActivities() 的实现

public final class ActivityRefWatcher {
    
    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        @Override public void onActivityStarted(Activity activity) {
        }

        @Override public void onActivityResumed(Activity activity) {
        }

        @Override public void onActivityPaused(Activity activity) {
        }

        @Override public void onActivityStopped(Activity activity) {
        }

        @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        @Override public void onActivityDestroyed(Activity activity) {
          // 调用 onActivityDestroyed 来处理 Destroy 之后的 Activity
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };
      
  public void watchActivities() {
    // 确保不会注册两个 lifecycle
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  
  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);	// 分析 1,重点
  }
  
}

可以看到当 Activity 被销毁的回调后, 便会调用 onActivityDestroyed 方法, 进而调用 refWatcher.watch 方法,看到这里,总算知道工作流程了,那么怎样检测到内存泄漏,就是重点内容了,所以我们将目光重点投向 refWatcher.watch(activity) 这一操作。

四、内存泄漏检测原理
public final class RefWatcher {
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;

  public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

  public void watch(Object watchedReference, String referenceName) {
    ......
    // 1. 构建 要监听对象(Activity) 的 key
    String key = UUID.randomUUID().toString();
    // 2. 添加到 key 缓存
    retainedKeys.add(key);
    // 3. 构建要监听对象(Activity)的弱引用
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 4. 确认一下被弱引用的对象是否被回收了
    ensureGoneAsync(watchStartNanoTime, reference);
  }

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        // 通过线程池去执行确认操作
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }
}

可以看出,refWatcher.watch(activity) 为监听对象构建了一个 key, 并且为它构建了一个 弱引用 ,我们都知道,弱引用要是对象要被回收,会将其弱引用添加到引用队列 ReferenceQueue 中,而 LeakCanary 正是也利用了这一原理,进行内存泄漏检测,具体我们继续看下去,看到 ensureGone 方法。

public final class RefWatcher {

  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    ......
    removeWeaklyReachableReferences();
    ......
    if (gone(reference)) {
      return DONE;
    }
    // 执行 GC
    gcTrigger.runGc();
    // 尝试移除弱可及的引用, 即即将被 GC 的对象
    removeWeaklyReachableReferences();
    // 判断弱引用是否已经被移除了
    if (!gone(reference)) {
      ......
      // Dump Hrof 文件
      File heapDumpFile = heapDumper.dumpHeap();
      ......
      // 分析 Hrof 的文件
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

  private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    // 从引用队列中获取即将被 GC 的对象
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      // 既然即将被 GC 了, 那么说明就不会内存泄漏
      // 尝试从 retainedkey 中移除
      retainedKeys.remove(ref.key);
    }
  }
    
}

执行 GC 之后, 这个对象的弱引用依旧存在, 那么就说明可能发生内存泄漏了。
到此,已经可以解析得到 LeakCanary 是怎样检测发生了内存泄漏的了。 发生泄漏后,LeakCanary 会将相关信息显示给开发者,这就需要对 Dump Hrof 文件进行分析了。

五、泄漏分析

从上面 ensureGone 方法内部,检测到内存泄漏时执行了如下的操作:

  1. 通过 heapDumper.dumpHeap() 获取此刻的内存镜像 HPROF 文件
  2. 最终会调用获取 Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
  3. 通过 heapdumpListener.analyze 来分析内存泄漏的引用链

还记得在 install 的时候

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
public AndroidRefWatcherBuilder listenerServiceClass(
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

这里的 listenerServiceClass(DisplayLeakService.class) 中,heapDumpListener 将 ServiceHeapDumpListener 实例构造了参数,heapdumpListener.analyze 的 heapdumpListener 正是 ServiceHeapDumpListener。
那 heapdumpListener.analyze 是怎样分析的呢?我们看到

public final class ServiceHeapDumpListener implements HeapDump.Listener {

  ......

  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
  
}

public final class HeapAnalyzerService extends IntentService {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    // 通知 HeapAnalyzerService 远程服务处理这个 Intent
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

heapdumpListener.analyze 会通知远程的 HeapAnalyzerService 服务, 解析 HPROF 文件,接着看 HeapAnalyzerService

public final class HeapAnalyzerService extends IntentService {

  @Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
   
    // 1. 创建了 HeapAnalyzer 对象, 用于分析 HPROF 文件
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    // 2. 执行泄漏引用链的分析
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    ......
  }
  
}

HeapAnalyzer 就是内存泄漏引用链分析的核心所在了, 下面我们看看它的 heapAnalyzer.checkForLeak 方法实现

public final class HeapAnalyzer {
    
  /**
   * Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
   * and then computes the shortest strong reference path from that instance to the GC roots.
   */
  public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();
    ......
    try {
      // square haha 提供技术实现
      // 1. 将文件映射到内存, 内部实现很有意思, 将文件流读入 ByteBuffer 的数组集合
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      // 2. 构建 HPROF 数据到 Snapshot 对象中
      HprofParser parser = new HprofParser(buffer);
      Snapshot snapshot = parser.parse();
      // 3. 从 Snapshot 获取 Java 的 GC Roots
      deduplicateGcRoots(snapshot);
      // 校验在我们 dump Hrof 期间, 这个对象是否被回收了
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);
      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }
      // 4. 从 GC Roots 中找寻到泄漏对象的引用链
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }
    
}

可以看到,HeapAnalyzer 中 checkForLeak 的实现主要做了以下工作

  1. 解析 Hprof 文件到 Snapshot 对象中
  2. 在 Snapshot 中找寻 GC Roots
  3. 从 GC Roots 中找寻到泄漏对象的引用链

那么 GC Roots 中如何找寻到泄漏对象的引用链呢?

六、GC Roots 中找寻到泄漏对象的引用链
public class Snapshot {

    public final void addRoot(@NonNull RootObj root) {
        mCurrentHeap.addRoot(root);
        root.setHeap(mCurrentHeap);
    }

   // addRoot 调用的地方是以下六个方法
    private int loadJniLocal(){...}

    private int loadJniMonitor() {...}

    private int loadThreadBlock() {...}
    
    private int loadBasicObj(RootType type) {...}
    
    private int loadNativeStack() {...}
    
    private int loadJavaFrame() {...}

}

public class Heap {

    //  Root objects such as interned strings, jni locals, etc
    @NonNull
    ArrayList<RootObj> mRoots = new ArrayList<RootObj>();

    public final void addRoot(@NonNull RootObj root) {
        root.mIndex = mRoots.size();
        mRoots.add(root);
    }
    
}

在 Snapshot snapshot = parser.parse(); 这一步操作中 将 GC Roots 对象添加到了 Snapshot 其中。
我们再回归到 HeapAnalyzer 中 checkForLeak 方法中,调用了 findLeakTrace 方法,这个方法是查找引用链

public final class HeapAnalyzer {

  private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    // 找寻引用链
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    ......
  }
  
}

可以看到调用了 ShortestPathFinder 的 findPath 查找引用链

final class ShortestPathFinder {
    
    
  Result findPath(Snapshot snapshot, Instance leakingRef) {
    clearState();
    canIgnoreStrings = !isString(leakingRef);
    // 将 snapshot 构建成邻接表的图结构
    enqueueGcRoots(snapshot);

    boolean excludingKnownLeaks = false;
    LeakNode leakingNode = null;
    // 使用图的广度优先遍历, 搜索距离 GC Roots 最近的引用链
    while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
      LeakNode node;
      if (!toVisitQueue.isEmpty()) {
        node = toVisitQueue.poll();
      } else {
        node = toVisitIfNoPathQueue.poll();
        ......
        excludingKnownLeaks = true;
      }

      // 找到了目标的结点, 终止搜索
      if (node.instance == leakingRef) {
        leakingNode = node;
        break;
      }

      if (checkSeen(node)) {
        continue;
      }

      if (node.instance instanceof RootObj) {
        visitRootObj(node);
      } else if (node.instance instanceof ClassObj) {
        visitClassObj(node);
      } else if (node.instance instanceof ClassInstance) {
        visitClassInstance(node);
      } else if (node.instance instanceof ArrayInstance) {
        visitArrayInstance(node);
      } else {
        throw new IllegalStateException("Unexpected type for " + node.instance);
      }
    }
    return new Result(leakingNode, excludingKnownLeaks);
  }
    
}

此处使用图的广度优先遍历, 搜索距离 GC Roots 最近的引用链,找到了则结束搜索。

七、总结

在我们实际开发过程中,会发现 LeakCanary 还是会出现误判的情况,监测逻辑是异步的,如果判断 Activity 是否可回收时某个 Activity 正好还被某个方法的局部变量持有,就会引起误判,误判应该怎样解决呢?这就要理解了其实现原理了才好下手解决了,改进的方案有很多种,例如可以在发现某个 Activity 无法被回收,再重复判断多次,以防在判断时该 Activity 被局部变量持有导致误判,具体的实现方式文章不作详细介绍了。到此,LeakCanary 源码分析完毕啦,欢迎交流、指正。

参考:

https://square.github.io/leakcanary/
https://github.com/Tencent/matrix/wiki/Matrix-Android-ResourceCanary
https://sharrychoo.github.io/blog/

2022联想Android校招复盘:从源码到系统定制的完整面试攻略 在移动应用开发中,性能优化与系统定制能力是衡量Android工程师水平的重要标尺。从Handler消息机制到Activity启动流程,从内存泄漏定位到AGP构建链路,每一个底层原理都直接影响用户体验与工程交付效率。对于面向手机、平板、车机等多终端场景的厂商而言,系统级定制与OTA升级更是核心需求。本文基于2022届联想校招Android岗位的真实面试经历,系统复盘了从笔试到技术面、HR面的高频考点与实战经验,覆盖性能优化、R8混淆、MVVM架构等关键内容,帮助求职者构建完整知识体系,从容应对Android校 阅读详情

相关推荐

Android面试核心指南:从Java/Kotlin基础到架构设计深度解析

在Android开发领域,掌握核心原理与工程实践是构建高质量应用的基础。理解Java/Kotlin的内存模型、并发编程机制,以及Android系统的组件生命周期、View绘制流程等底层原理,是解决复杂性能问题的关键。这些技术不仅关系到应用的稳定性和流畅度,更直接影响开发效率和架构可维护性。在实际工程中,内存泄漏检测、UI卡顿优化、多级缓存设计等高频热词,正是这些原理的具体体现。通过合理运用架构模式如MVVM,结合Retrofit、Glide等流行框架的源码级理解,开发者能够设计出高性能、易维护的移动应用。本

weixin_34050005的博客 411

内存泄漏分析框架LeakCanary的使用与原理解析

文章目录1. 常见内存泄漏1.1 “单例模式” 造成的内存泄漏1.2 “静态实例” 造成内存泄漏1.3 “Handler” 造成的内存泄漏1.4 “线程” 造成的内存泄漏1.5 “使用WebView” 造成的内存泄漏2. LeakCanary使用与原理解析2.1 LeakCanary使用方法2.2 LeakCanary原理解析  在《Android性能优化(1):常见内存泄漏与优化(一)》和《An...

老蒋也疯狂 1万+

Android面试通关指南:从基础到源码的体系化复习方法

技术面试的本质,不在于知识点的机械记忆,而在于是否建立了完整的知识体系,能否将“用过”转化为“理解原理”。真正扎实的面试准备,需要从语言基础、系统机制到性能优化层层递进,形成可迁移的工程思维。例如,理解数据存储方案中SharedPreferences与Room的适用边界,或解决协调布局与Banner轮播时的嵌套滚动冲突,都属于对底层原理的实战运用。在Android开发岗位的考察中,面试官更关注候选人对事件分发、Handler消息循环、Binder通信等核心机制的理解深度,以及面对项目难点时的分析与取舍能力。

weixin_31534091的博客 96

LeakCanary使用详细教程(附Demo)

导语 在Android的性能优化中,内存优化是必不可少的点,而内存优化最重要的一点就是解决内存泄漏的问题,在Android的内存泄漏分析工具也不少,比如PC端的有:AndroidStudio自带的Android Profiler、MAT等工具;手机端也有,就是我们今天要介绍的LeakCanary LeakCanary简介 LeakCanary是Square公司为Android开发者提...

学如不及,犹恐失之 2万+

LeakCanary.rar

系统源码集成LeakCanary依赖包,这个是已经编译ok并且可以使用的。

LeakCanary接入MK编译App过程记录

因为工作需要,需要对固件中通过MK编译的系统应用接入LeakCanary以检查内存泄露。 几经折腾,终于成功。 接入的LeakCanary 最新版本 2.2版本,免写版本。 环境准备:使用的Android10 AOSP代码(tag: android-10.0.0_r10),接入leakcanary前已经全编译通过。 接入的APP路径:packages/apps/Gallery2 对比的独立应用接入LeakCanary: GcTest 来看看遇到的问题,及解决过程: 1、在MK中加入leakcanary2.2依赖编译不过 解决方法:修改Android.mk --- a/Android.mk +

LeakCanary的简单介绍及使用

利用ContentProvider进行初始化注册 5 种 Android 泄漏场景的监控Activity是利用application.registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback)监听全局Activity生命周期Fragment首先是通过接口监听 Activity.onCreate()事件,再通过接口监听 Fragment 的生命周期ViewModel是通过Hook。

缘来的精彩的博客 3386

Bentley二次开发教程28-交互窗口-案例实战2-EC属性赋值工具

该工具会根据用户输入的路径获取Schema文件的路径,然后通过路径读取对应的Schema文件并在树状图中显示EC类,当用户选择树状图上的节点时,此时会触发事件,根据EC属性添加用户自定义控件用于显示EC属性并可对其进行赋值操作。完成后选取元素,点击界面上的应用,此时元素会被赋予指定EC类,同时修改的EC属性值也会被同步赋予。该工具涉及到的WinForm控件有:TextBox,Button,TreeView,Panel,UserControl(用户自定义控件)。

不见不散的博客 1474

LeakCanary 详解

1.背景 LeakCanary 是一种方便的内存泄露检查工具,与相对于用dump 内存 然后用MAT工具去分析,要简单便捷很多,我们需要了解如何使用这个工具及其背后的原理 2.基本使用 2.1 加入远程引用 debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1' 2.2 Le

自古深情留不住,总是套路得人心 8561

无刷电机控制基础(2)——有感和无感驱动、调速

上一篇文章我们讲了一些无刷电机的基础知识,包括无刷电机的内部结构,驱动原理等,我们知道了只需要按照转子的当前位置,来按顺序给定子线圈通电,就能让电机转动起来。但是,上一篇中我们跳过了一个关键步骤,就是如何检测转子的位置。本篇我们就讲讲常用的位置检测方法,以及引出的一些相关问题。我们知道,获取磁铁的位置可以用霍尔传感器,无刷电机的转子就是永磁体,因此只要在合适的位置安装霍尔传感器,就能知道转子的旋转位置。在无刷电机中,一般用3个开关型霍尔器件就能检测转子的位置。

小白白学电子 2万+

kaggle注册以及数据集下载全流程

kaggle注册,数据集下载及常见问题

Arriteey的博客 2万+

别再手写选择器:Magnitude AI 浏览器代理,用自然语言完成端到端测试

browser-agent(Magnitude)是一个开源的视觉优先 AI 浏览器代理:它用视觉 AI 理解页面,用自然语言控制浏览器,支持网页自动化、数据提取和端到端测试,适合前端开发、QA 以及想减少测试维护成本工程师。 [![Magnitude AI 浏览器代理在真实网页上执行端到端测试的演示动画](https://raw.gitcode.com/gh_mirrors/magnit/bro

gitblog_00152的博客 250

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程

本文详细解析了企业微信群机器人的Webhook配置全流程,从创建机器人、获取关键Webhook地址,到使用代码发送文本、Markdown等多种消息类型。内容涵盖安全加固、错误处理及与CI/CD等系统集成的实战指南,帮助企业快速构建高效、稳定的内部通知系统。

ik678901的博客 1783

Android校招面经:字节快手小米美团四家大厂面试全复盘

在Android开发的学习与求职中,面试早已不是简单的八股文背诵,而是对工程思维、原理理解和实战能力的综合考察。从JVM、并发集合到Handler消息机制、Binder跨进程通信,再到ANR排查、内存泄漏优化以及OkHttp、Glide等开源框架的源码分析,面试官更关注你是否能把这些技术点放到真实场景中讲清楚。大厂校招通常有多轮技术面,每轮侧重不同,有的重算法,有的重项目深挖,有的重原理追问。本文以字节跳动、快手、小米、美团四家公司为例,梳理了高频考点与各家的面试风格差异,并总结了备考策略与避坑经验,为正在

weixin_30388677的博客 327

llama factory

NOTE] 除上述链接以外的其他网站均为未经许可的第三方网站,请小心甄别。

thesky123456的博客 1273

一站式讲解Wireshark网络抓包分析的若干场景、过滤条件及分析方法

本文详细讲解常用的抓包工具、抓包分析的网络场景、分析抓包时的多种过滤条件以及如何结合常用的协议去分析排查问题,给大家提供一个借鉴或参考。

dvlinker的技术专栏 5万+

6年老Android的阿里一日游,面试官不断挖坑如何从容应对(1)

(没有继续深挖虚拟机,这个考察的都是Android绘制的原理,看过原理的应该都知道,同步屏障,然后发送VSync脉冲信号包括掉帧的处理等等,还是比较简单的,就不一一叙述了)(还好还好,还是知道一点的,ART虚拟机是可以自动在安装的时候就去加载多dex的,但是dalvik虚拟机不会哦)(好吧,上一题留的坑,不过只是知道webp满足图片的清晰度要求及大小会很小,其他的了解的不多)(你们懂的,肯定是17题的时候,我引出的LeakCanary。(估计是感觉我有点疲惫和紧张,开始问一些简单的问题缓和下,哈哈)

2401_83916435的博客 573

配置适合Gurobi的机器硬件环境需要考虑的因素

介绍了在为gurobi选择合适的硬件环境时,需要考虑三个方面的因素;

Linshaodan520的博客 1064

6年老Android的阿里一日游,面试官不断挖坑如何从容应对

(没有继续深挖虚拟机,这个考察的都是Android绘制的原理,看过原理的应该都知道,同步屏障,然后发送VSync脉冲信号包括掉帧的处理等等,还是比较简单的,就不一一叙述了)

2301_76238237的博客 1041

c#仿热血江湖

Int32_1 = (int) table.Rows[i]["FLD_初级附魂"],Int32_2 = (int) table.Rows[i]["FLD_中级附魂"],Int32_3 = (int) table.Rows[i]["FLD_是否绑定"],Int32_0 = (int) table.Rows[i]["FLD_进化"],Form1.smethod_1(2, "加载百宝阁物品----没有百宝阁数据");Form1.smethod_1(2, "加载物品商店----没有物品数据");

weixin_42132177的博客 142

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

京东校招Android主观题全解析:Handler、Binder与性能优化核心考点

在Android技术面试中,考察重点逐渐从API使用转向对底层原理和工程架构的深度理解。Handler消息机制是理解主线程与异步通信的基石,其背后的Looper与MessageQueue设计体现了Android系统的核心调度思想;Binder作为跨进程通信的主力,则展现了系统在性能与安全间的平衡。掌握这些原理不仅有助于回答面试中的追问,更能指导日常开发中的性能优化,例如通过内存泄漏治理和卡顿排查提升应用稳定性。从自定义View的绘制流程到图片加载库的架构设计,再到MVP/MVVM的演进,这些知识点共同构成了

weixin_33894640的博客 347

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

搞懂这些直接来阿里入职,android面试2019

由于题目很多整理答案的工作量太大,所以仅限于提供知识点,详细的很多问题和参考答案我都整理成了 PDF文件!imageMo《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》**完整开源项目:docs.qq.com/doc/DSkNLaERkbnFoS0ZF **[外链图片转存中…(img-iC18yzm9-1709244805330)][外链图片转存中…(img-khhSwklu-1709244805330)]本文已被。

m0_61331407的博客 933

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

flutter项目实战视频,2024阿里、网易、京东等大厂最新Android面试题

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示。《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!

F1520080的博客 819
上一篇: 代理设计模式在Android开发中的应用
下一篇: 探索 Jetpack Compose
胡小明同学
博客等级 码龄10年 35粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值