leakcanary内存框架解决内存问题

在我们Android开发中随着功能的不断增加,页面的不断增多,我们就会面临一个难题,那就是内存泄漏那么好,那如何解决内存泄漏,我下面给出一篇,完全解析内存泄漏这一难题的文章,供大家开发者参考.

什么叫内存:

一、 基本概念

每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享。Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量。虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被创建的对象的本身,而在栈中分配的内存只是存放指向这个堆对象的引用而已。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。

具体的概念:JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method,也叫静态区):

堆区(stack):

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息(class的目的是得到操作指令) ;
2.jvm只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身和数组本身;

栈区(heap):

1.每个线程包含一个栈区,栈中只保存基础数据类型本身和自定义对象的引用
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问;
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令);

方法区(method)(静态区):

1.被所有的线程共享,方法区包含所有的class(class是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

什么会导致内存泄漏?

  • 定义:当activity销毁后,gc在回收该实例的时候,发现该activity的被其他对象持有引用,导致该activity不能被回收,出现内存泄漏。
常见案列:
  1. handler耗时引发的内存泄漏:
    当activity当中存在handler接收耗时的消息时,比如我们一般在网络请求切换线程时,经常使用到handler,假设消息还没有发送完成,但是页面已经被关闭,也就说activity已经执行了ondestroy方法。当gc回收时,会出现改activity不能被回收的情况,到时内存泄漏。
    解决办法:当activity销毁的时候,调用handler的removeCallbacksAndMessages方法,移除消息任务,然后将handler对象及线程置空。

  2. 内部类引发的内存泄漏(当然handler或子线程一般也作为内部类使用)
    因为java当中,内部类默认持有外部类的引用,当外部类销毁后,一旦gc回收该实例,发现内部类持有他的引用而导致不能回收该实例,出现内存泄漏的情况。
    解决方法:将内部类改为静态内部类,因为静态内部类生命周期和应用一样长,所以当退出程序的时候会一同回收该实例,并不会影响外部类的回收。

  3. 单例导致的内存泄漏
    因为在使用单例的时候,经常会传入一个本类的上下文对象,而单例是静态的,生命周期和application一样长,当activity销毁的时候,该单例持有activity的引用导致其不能被回收,出现内存泄漏。
    解决方法:在使用上下文的时候,传全局上下文。

  4. .资源未关闭
    Cursor,stream,database,Butterknife,broadcastreciver,bindservice,eventBus
    比如这些东西在使用完成后,需要进行close或者Unbind处理,以节省内存

  5. Timer计时器、动画
    因为这些涉及耗时问题,如果activity销毁,而该任务并未执行完成,会导致内存泄漏,所以一般在activity中如果使用到这些耗时任务,需要在activity销毁时,做对应处理,比如调用timer的cancel方法,或者动画的cancel方法并将对象置空

  6. 一些监听器的内存泄漏
    比如说我们给edittext设置输入文字监听时,当监听到文字发生变化,我们通过获取变化后的文字执行了耗时任务(比如获取到edittext里的内容上传服务器),当耗时任务未执行完成activity销毁了,会引发内存泄漏,所以在onDestory时,取消注册,比如说editText调用removeTextChangedListener方法

  7. Rxjava的内存泄漏:
    因为rxjava采用的是观察者模式,当请求到数据后会根据订阅关系将数据发送个订阅者,而如果这时订阅者已经销毁,就会出现引用该对象导致其不能被回收的情况,出现内存泄漏,rxjava2发布的时候也发现了这个问题,所以在回调当中,新增加了onSubcribe回调,同时返回了一个disposable对象,可以通过判断disposable里的isDisposed来确定当前的订阅关系,如果订阅关系中的订阅者已经不存在且当前订阅关系存在,解除订阅关系,并终止数据的发送。

  8. webView引发的内存泄漏:
    因为webview在使用的时候一般持有activity的引用,我们一般在activity的onDestroy方法中调用mWebView.destroy();来释放webview。如果在onDetachedFromWindow之前调用了destroy那就肯定会无法正常反注册了,也就会导致内存泄漏。所以在销毁webview前一定要先在onDetachedFromWindow中将webview从它的父view中移除,再调用destroy方法中调用webview的destroy,我开发的时候在5.1以上的手机上发现这种问题比较多,因为现在5.1以下适配的比较少了,基本没咋注意。

  9. 线程导致的内存泄漏:
    一般使用子线程都会创建一个内部类对象,而创建线程一般执行耗时任务,所以这个内部类默认持有外部类的引用,如果耗时任务在activity销毁的时候未执行完成,会因为持有外部类引用导致外部类不能被回收
    10.MVP内存泄漏:
    当我们使用MVP的模式进行写代码时,由于页面已经销毁但网络请求仍然在,会导致Presenter一直持有Activity的对象,造成内存泄漏。
    解决方法:在关闭页面时把网络请求给切断,把Prseenter引用置空,这样可以做到完全解耦而又不会导致内存泄漏.

  10. Bitmap:

  • 对象不在使用时调用recycle()释放内存
  • 尽量少使用图片
    客户在设计应用显示效果时候,为了达到界面美化的目的。很多地方会使用图片,有的应用整个的全屏背景都使用图片。图片的大量使用不仅会是的应用的APK的大小增大,而且会增大应用显示的内存。
    在设计界面的时候,请尽量多用颜色或者9png图片。如设置一个纯色的图片作为背景,此时可以用颜色代替。
  • 压缩Bitmap

如果图片像素过大,使用BitmapFactory类的方法实例化Bitmap的过程中,需要大于8M的内存空间,就必定会发生OutOfMemory异常。这个时候该如何处理呢?如果有这种情况,则可以将图片缩小,以减少载入图片过程中的内存的使用,避免异常发生。

     BitmapFactory提供了几种解码方式(decodeByteArray(),decodeFile(), decodeResource()等
   等), 以便从多种资源中创建一个Bitmap(位图)对象。可以根据你的图片数据来源选择最合适的解码方 式.
   每一种解码方式都有额外的特征,你可以通过BitmapFactory.Options类指定解码方法。
1BitmapFactory.Options options = new BitmapFactory.Options();
2options.inJustDecodeBounds = true;
3BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
4int imageHeight = options.outHeight;
5int imageHeight = options.outHeight;
6String imageType = options.outMimeType;

leakcanary内存框架

1.1 LeakCanart简单介绍

  • 由Square开源的一款轻量第三方内存泄漏检测工具
  • 原理:watch一个即将销毁的对象

1.2 原理:

  • Activity Destory之后将它放在一个WeakRefrenece
  • 这个WeakReference关联到ReferenceQueue
  • 查看ReferenceQueue是否存在Actiivty的引用
  • 如果该Activity泄露了,Dump出Heap信息,然后再去分析泄漏路径

1.3 4种引用

  • 强应用:不能被回收,jvm虚拟机宁愿抛出OOM也不愿意回收对象
  • 软应用:内存不足时会被回收
  • 弱应用:当促发Gc时会被回收
  • 虚应用:和没有引用是一样的
    软应用和弱应用相同点;对象被垃圾回收,java虚拟机就会把这个应用加入到与之关联的引用队列中

3.github官网:https://github.com/square/leakcanary
4.gradle依赖:

    implementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'

5.使用很简单: 在Application的onCreate 写入LeakCanary.install(this)
在这里插入图片描述

Leakcanary源码分析

1.install源码分析

  public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        //分析buildAndInstall
        .buildAndInstall();
  }

buildAndInstall源码分析

  public RefWatcher buildAndInstall() {
  //返回refWatcher
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      //弹出告诉你那里泄漏,表示开启activity
      LeakCanary.enableDisplayLeakActivity(context);
      //继续看这个
      ActivityRefWatcher.install((Application) context, refWatcher);
    }
    return refWatcher;
  }

ActivityRefWatcher.install源码分析

  public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  } 
 public void watchActivities() {
    // 反注册我们以前的activitycallback
    stopWatchingActivities();
    //重新注册  看lifecycleCallbacks源码
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }

lifecycleCallbacks源码很长只要关注一行就可以了

 @Override public void onActivityDestroyed(Activity activity) {
       //和Application的onDestory的生命周期关联
          ActivityRefWatcher.this.onActivityDestroyed(activity);
}

  void onActivityDestroyed(Activity activity) {
  // 首先看下refWatcher有哪些成员变量,然后看watch的源码
    refWatcher.watch(activity);
  }
//refWatcher成员变量
  //用于内存检测
  private final WatchExecutor watchExecutor;
  //查询是否正在调试中
  private final DebuggerControl debuggerControl;
  //用于判断泄漏之前,给gc最后一次机会
  private final GcTrigger gcTrigger;
  //内存泄漏堆文件
  private final HeapDumper heapDumper;
  //key
  private final Set<String> retainedKeys;
  //判断弱应用所持有的对象是否被已经被gc垃圾回收
  private final ReferenceQueue<Object> queue;
  private final HeapDump.Listener heapdumpListener;
  //排除一些系统bug
  private final ExcludedRefs excludedRefs;
  public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }
public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    //添加的唯一的key
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
     //看这个源码
    ensureGoneAsync(watchStartNanoTime, reference);
  }

ensureGoneAsync源码分析

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  //线程
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    //持续时间
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
     //移除已达到队列中的弱应用
    removeWeaklyReachableReferences();
    //如果是处于调试中则不进行分析
    if (debuggerControl.isDebuggerAttached()) {
       return RETRY;
    }
     if (gone(reference)) {
      return DONE;
    }
    //手动执行gc
    gcTrigger.runGc();
     //移除已达到队列中的弱应用
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

```
  File heapDumpFile = heapDumper.dumpHeap();
  if (heapDumpFile == RETRY_LATER) {
        return RETRY;
  }
  long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
  //最后在线程中取开始分析我们的泄漏
  heapdumpListener.analyze(
```

 new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

analyze分析我们的泄漏

 void analyze(HeapDump heapDump);

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

HeapAnalyzerService.runAnalysis源码分析,因为这个方法是继承于IntentService方法,所以会回调onHandlerIntent方法

  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }
@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);
    //排除一些系统bug
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
   //这个很重要
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    //回调
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

checkForLeak源码分析

    public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
        long analysisStartNanoTime = System.nanoTime();
        if (!heapDumpFile.exists()) {
            Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
            return failure(exception, since(analysisStartNanoTime));
        }


        try {
//heapDumpFile封装成对象
            HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
            //解释器解析上面的buffer
            HprofParser parser = new HprofParser(buffer);
            //具体的解析工作
            Snapshot snapshot = parser.parse();
            //对我们所检查的结果去重
            deduplicateGcRoots(snapshot);
            //获得解析的结果
            Instance leakingRef = findLeakingReference(referenceKey, snapshot);
            if (leakingRef == null) {//表示对象不存在,表示已经清楚
                return noLeak(since(analysisStartNanoTime));
            }
            //找出泄漏的对象/找出泄漏对象的最短路径
            return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
        } catch (Throwable e) {
            return failure(e, since(analysisStartNanoTime));
        }


    }
1.把.hprof转化为Snapshot
2.优化gcroots
3.找出泄漏的对象/找出泄漏对象的最短路径

findLeakingReference源码分析

 private Instance findLeakingReference(String key, Snapshot snapshot) {
 //根据弱应用查找类的对象
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    List<String> keysFound = new ArrayList<>();
    //遍历这个对象的所有实例
    for (Instance instance : refClass.getInstancesList()) {
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      String keyCandidate = asString(fieldValue(values, "key"));
      //如果key的值和最开始定义封装的key值相同,那么返回这个泄漏对象
      if (keyCandidate.equals(key)) {
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }

findLeakTrace源码分析:

 private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) {
        ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
        ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
        if (result.leakingNode == null) {
            return noLeak(since(analysisStartNanoTime));
        }
        //屏幕显示的就是这个
        LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

        String className = leakingRef.getClassObj().getClassName();

        snapshot.computeDominators();

        Instance leakingInstance = result.leakingNode.instance;
        //计算内存泄漏的空间大小的
        long retainedSize = leakingInstance.getTotalRetainedSize();

        // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
        if (SDK_INT <= N_MR1) {
            retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
        }
        return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime));
    }

知识共享许可协议
本作品采用知识共享署名 4.0 国际许可协议进行许可。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值